From f2595d7cbaafee4db17bf74e226e20c35326faf4 Mon Sep 17 00:00:00 2001 From: aj Date: Mon, 16 May 2016 22:09:19 -0400 Subject: [PATCH] First commit. --- .gitattributes | 22 + .gitignore | 200 +++ External/Ionic.Zip.dll | Bin 0 -> 462848 bytes External/netDxf.dll | Bin 0 -> 397312 bytes Installer/script.nsi | 138 ++ Source/OpenNest.Core/Align.cs | 186 +++ Source/OpenNest.Core/AlignType.cs | 15 + Source/OpenNest.Core/Angle.cs | 125 ++ Source/OpenNest.Core/BoundingBox.cs | 77 + Source/OpenNest.Core/Box.cs | 206 +++ Source/OpenNest.Core/BoxSplitter.cs | 57 + Source/OpenNest.Core/CNC/CircularMove.cs | 89 ++ Source/OpenNest.Core/CNC/CodeType.cs | 14 + Source/OpenNest.Core/CNC/Comment.cs | 31 + Source/OpenNest.Core/CNC/Feedrate.cs | 35 + Source/OpenNest.Core/CNC/ICode.cs | 9 + Source/OpenNest.Core/CNC/Kerf.cs | 39 + Source/OpenNest.Core/CNC/KerfType.cs | 10 + Source/OpenNest.Core/CNC/LayerType.cs | 12 + Source/OpenNest.Core/CNC/LinearMove.cs | 47 + Source/OpenNest.Core/CNC/Mode.cs | 9 + Source/OpenNest.Core/CNC/Motion.cs | 45 + Source/OpenNest.Core/CNC/Program.cs | 477 ++++++ Source/OpenNest.Core/CNC/RapidMove.cs | 42 + Source/OpenNest.Core/CNC/SubProgramCall.cs | 87 ++ .../Collections/DrawingCollection.cs | 8 + .../Collections/PartCollection.cs | 177 +++ .../Collections/PlateCollection.cs | 187 +++ Source/OpenNest.Core/ConvertGeometry.cs | 117 ++ Source/OpenNest.Core/ConvertMode.cs | 52 + Source/OpenNest.Core/ConvertProgram.cs | 137 ++ Source/OpenNest.Core/CutParameters.cs | 15 + Source/OpenNest.Core/Drawing.cs | 117 ++ Source/OpenNest.Core/EvenOdd.cs | 15 + Source/OpenNest.Core/Generic.cs | 12 + Source/OpenNest.Core/Geometry/Arc.cs | 538 +++++++ Source/OpenNest.Core/Geometry/Circle.cs | 415 ++++++ Source/OpenNest.Core/Geometry/DefinedShape.cs | 42 + Source/OpenNest.Core/Geometry/Entity.cs | 268 ++++ Source/OpenNest.Core/Geometry/EntityType.cs | 12 + Source/OpenNest.Core/Geometry/Layer.cs | 29 + Source/OpenNest.Core/Geometry/Line.cs | 552 +++++++ Source/OpenNest.Core/Geometry/Polygon.cs | 500 +++++++ Source/OpenNest.Core/Geometry/Shape.cs | 569 ++++++++ Source/OpenNest.Core/Helper.cs | 990 +++++++++++++ Source/OpenNest.Core/IBoundable.cs | 18 + Source/OpenNest.Core/IPostProcessor.cs | 17 + Source/OpenNest.Core/Material.cs | 33 + Source/OpenNest.Core/Nest.cs | 139 ++ Source/OpenNest.Core/NestConstraints.cs | 46 + Source/OpenNest.Core/OffsetSide.cs | 9 + Source/OpenNest.Core/OpenNest.Core.csproj | 109 ++ Source/OpenNest.Core/Part.cs | 190 +++ Source/OpenNest.Core/Plate.cs | 475 ++++++ .../OpenNest.Core/Properties/AssemblyInfo.cs | 35 + Source/OpenNest.Core/Quantity.cs | 14 + Source/OpenNest.Core/RelativePosition.cs | 13 + Source/OpenNest.Core/RotationType.cs | 16 + Source/OpenNest.Core/Sequence.cs | 67 + Source/OpenNest.Core/Size.cs | 55 + Source/OpenNest.Core/Spacing.cs | 27 + Source/OpenNest.Core/SpecialLayers.cs | 21 + Source/OpenNest.Core/Timing.cs | 91 ++ Source/OpenNest.Core/TimingInfo.cs | 57 + Source/OpenNest.Core/Tolerance.cs | 14 + Source/OpenNest.Core/Trigonometry.cs | 40 + Source/OpenNest.Core/Units.cs | 82 ++ Source/OpenNest.Core/Vector.cs | 213 +++ Source/OpenNest.Engine/BestCombination.cs | 80 + Source/OpenNest.Engine/CirclePacking/Bin.cs | 30 + .../CirclePacking/FillEndEven.cs | 79 + .../CirclePacking/FillEndOdd.cs | 100 ++ .../CirclePacking/FillEngine.cs | 17 + Source/OpenNest.Engine/CirclePacking/Item.cs | 19 + Source/OpenNest.Engine/NestDirection.cs | 9 + Source/OpenNest.Engine/NestEngine.cs | 176 +++ Source/OpenNest.Engine/NestItem.cs | 35 + Source/OpenNest.Engine/OpenNest.Engine.csproj | 70 + .../Properties/AssemblyInfo.cs | 35 + .../OpenNest.Engine/RectanglePacking/Bin.cs | 30 + .../RectanglePacking/FillBestFit.cs | 88 ++ .../RectanglePacking/FillEngine.cs | 82 ++ .../RectanglePacking/FillNoRotation.cs | 68 + .../RectanglePacking/FillSameRotation.cs | 65 + .../OpenNest.Engine/RectanglePacking/Item.cs | 52 + .../RectanglePacking/PackBottomLeft.cs | 115 ++ .../RectanglePacking/PackEngine.cs | 16 + .../PackFirstFitDecreasing.cs | 121 ++ Source/OpenNest.sln | 34 + Source/OpenNest/Actions/Action.cs | 20 + Source/OpenNest/Actions/ActionAddPart.cs | 136 ++ Source/OpenNest/Actions/ActionClone.cs | 120 ++ Source/OpenNest/Actions/ActionFillArea.cs | 43 + Source/OpenNest/Actions/ActionSelect.cs | 252 ++++ Source/OpenNest/Actions/ActionSelectArea.cs | 166 +++ Source/OpenNest/Actions/ActionSetSequence.cs | 155 ++ Source/OpenNest/Actions/ActionZoomWindow.cs | 205 +++ Source/OpenNest/ColorScheme.cs | 140 ++ Source/OpenNest/Controls/BottomPanel.cs | 36 + Source/OpenNest/Controls/DrawControl.cs | 247 ++++ Source/OpenNest/Controls/DrawingListBox.cs | 101 ++ Source/OpenNest/Controls/EntityView.cs | 214 +++ Source/OpenNest/Controls/HorizontalLine.cs | 38 + Source/OpenNest/Controls/LayoutViewGL.cs | 451 ++++++ Source/OpenNest/Controls/NumericUpDown.cs | 40 + Source/OpenNest/Controls/PlateView.cs | 880 +++++++++++ Source/OpenNest/Controls/QuadrantSelect.cs | 82 ++ Source/OpenNest/Controls/VerticalLine.cs | 38 + Source/OpenNest/Document.cs | 31 + .../OpenNest/Forms/AutoNestForm.Designer.cs | 127 ++ Source/OpenNest/Forms/AutoNestForm.cs | 108 ++ Source/OpenNest/Forms/AutoNestForm.resx | 120 ++ .../Forms/CadConverterForm.Designer.cs | 293 ++++ Source/OpenNest/Forms/CadConverterForm.cs | 388 +++++ Source/OpenNest/Forms/CadConverterForm.resx | 120 ++ .../Forms/CutParametersForm.Designer.cs | 241 ++++ Source/OpenNest/Forms/CutParametersForm.cs | 64 + Source/OpenNest/Forms/CutParametersForm.resx | 120 ++ .../Forms/EditDrawingForm.Designer.cs | 268 ++++ Source/OpenNest/Forms/EditDrawingForm.cs | 84 ++ Source/OpenNest/Forms/EditDrawingForm.resx | 123 ++ .../OpenNest/Forms/EditNestForm.Designer.cs | 308 ++++ Source/OpenNest/Forms/EditNestForm.cs | 688 +++++++++ Source/OpenNest/Forms/EditNestForm.resx | 706 +++++++++ .../Forms/EditNestInfoForm.Designer.cs | 709 +++++++++ Source/OpenNest/Forms/EditNestInfoForm.cs | 234 +++ Source/OpenNest/Forms/EditNestInfoForm.resx | 120 ++ .../OpenNest/Forms/EditPlateForm.Designer.cs | 450 ++++++ Source/OpenNest/Forms/EditPlateForm.cs | 198 +++ Source/OpenNest/Forms/EditPlateForm.resx | 120 ++ .../OpenNest/Forms/FillPlateForm.Designer.cs | 64 + Source/OpenNest/Forms/FillPlateForm.cs | 55 + Source/OpenNest/Forms/FillPlateForm.resx | 120 ++ Source/OpenNest/Forms/MainForm.Designer.cs | 1282 +++++++++++++++++ Source/OpenNest/Forms/MainForm.cs | 891 ++++++++++++ Source/OpenNest/Forms/MainForm.resx | 132 ++ Source/OpenNest/Forms/OptionsForm.Designer.cs | 256 ++++ Source/OpenNest/Forms/OptionsForm.cs | 50 + Source/OpenNest/Forms/OptionsForm.resx | 123 ++ .../OpenNest/Forms/SequenceForm.Designer.cs | 111 ++ Source/OpenNest/Forms/SequenceForm.cs | 18 + Source/OpenNest/Forms/SequenceForm.resx | 120 ++ .../OpenNest/Forms/SetValueForm.Designer.cs | 122 ++ Source/OpenNest/Forms/SetValueForm.cs | 48 + Source/OpenNest/Forms/SetValueForm.resx | 120 ++ Source/OpenNest/Forms/TimingForm.Designer.cs | 360 +++++ Source/OpenNest/Forms/TimingForm.cs | 72 + Source/OpenNest/Forms/TimingForm.resx | 120 ++ Source/OpenNest/GraphicsHelper.cs | 307 ++++ Source/OpenNest/IO/DxfExporter.cs | 287 ++++ Source/OpenNest/IO/DxfImporter.cs | 103 ++ Source/OpenNest/IO/Extensions.cs | 169 +++ Source/OpenNest/IO/NestReader.cs | 466 ++++++ Source/OpenNest/IO/NestWriter.cs | 398 +++++ Source/OpenNest/IO/ProgramReader.cs | 389 +++++ Source/OpenNest/LayoutPart.cs | 202 +++ Source/OpenNest/MainApp.cs | 27 + Source/OpenNest/MdiExtensions.cs | 43 + Source/OpenNest/OpenNest.csproj | 324 +++++ Source/OpenNest/Properties/AssemblyInfo.cs | 35 + .../OpenNest/Properties/Resources.Designer.cs | 196 +++ Source/OpenNest/Properties/Resources.resx | 178 +++ .../OpenNest/Properties/Settings.Designer.cs | 218 +++ Source/OpenNest/Properties/Settings.settings | 54 + Source/OpenNest/PushDirection.cs | 11 + Source/OpenNest/Resources/add.png | Bin 0 -> 3294 bytes Source/OpenNest/Resources/clear.png | Bin 0 -> 1289 bytes Source/OpenNest/Resources/clock.png | Bin 0 -> 1419 bytes Source/OpenNest/Resources/doc_new.png | Bin 0 -> 973 bytes Source/OpenNest/Resources/doc_open.png | Bin 0 -> 1078 bytes Source/OpenNest/Resources/edit_icon.ico | Bin 0 -> 34494 bytes Source/OpenNest/Resources/import.png | Bin 0 -> 1041 bytes Source/OpenNest/Resources/move_first.png | Bin 0 -> 1173 bytes Source/OpenNest/Resources/move_last.png | Bin 0 -> 1143 bytes Source/OpenNest/Resources/move_next.png | Bin 0 -> 992 bytes Source/OpenNest/Resources/move_previous.png | Bin 0 -> 1012 bytes Source/OpenNest/Resources/remove.png | Bin 0 -> 2945 bytes Source/OpenNest/Resources/rotate_ccw.png | Bin 0 -> 1316 bytes Source/OpenNest/Resources/rotate_cw.png | Bin 0 -> 1338 bytes Source/OpenNest/Resources/save.png | Bin 0 -> 1204 bytes Source/OpenNest/Resources/save_as.png | Bin 0 -> 1270 bytes Source/OpenNest/Resources/watermark.png | Bin 0 -> 8960 bytes Source/OpenNest/Resources/zoom_all.png | Bin 0 -> 1728 bytes Source/OpenNest/Resources/zoom_in.png | Bin 0 -> 1725 bytes Source/OpenNest/Resources/zoom_out.png | Bin 0 -> 1669 bytes Source/OpenNest/SelectionType.cs | 8 + Source/OpenNest/ToolStripRenderer.cs | 534 +++++++ Source/OpenNest/Win32.cs | 52 + Source/OpenNest/app.config | 69 + 189 files changed, 26944 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 External/Ionic.Zip.dll create mode 100644 External/netDxf.dll create mode 100644 Installer/script.nsi create mode 100644 Source/OpenNest.Core/Align.cs create mode 100644 Source/OpenNest.Core/AlignType.cs create mode 100644 Source/OpenNest.Core/Angle.cs create mode 100644 Source/OpenNest.Core/BoundingBox.cs create mode 100644 Source/OpenNest.Core/Box.cs create mode 100644 Source/OpenNest.Core/BoxSplitter.cs create mode 100644 Source/OpenNest.Core/CNC/CircularMove.cs create mode 100644 Source/OpenNest.Core/CNC/CodeType.cs create mode 100644 Source/OpenNest.Core/CNC/Comment.cs create mode 100644 Source/OpenNest.Core/CNC/Feedrate.cs create mode 100644 Source/OpenNest.Core/CNC/ICode.cs create mode 100644 Source/OpenNest.Core/CNC/Kerf.cs create mode 100644 Source/OpenNest.Core/CNC/KerfType.cs create mode 100644 Source/OpenNest.Core/CNC/LayerType.cs create mode 100644 Source/OpenNest.Core/CNC/LinearMove.cs create mode 100644 Source/OpenNest.Core/CNC/Mode.cs create mode 100644 Source/OpenNest.Core/CNC/Motion.cs create mode 100644 Source/OpenNest.Core/CNC/Program.cs create mode 100644 Source/OpenNest.Core/CNC/RapidMove.cs create mode 100644 Source/OpenNest.Core/CNC/SubProgramCall.cs create mode 100644 Source/OpenNest.Core/Collections/DrawingCollection.cs create mode 100644 Source/OpenNest.Core/Collections/PartCollection.cs create mode 100644 Source/OpenNest.Core/Collections/PlateCollection.cs create mode 100644 Source/OpenNest.Core/ConvertGeometry.cs create mode 100644 Source/OpenNest.Core/ConvertMode.cs create mode 100644 Source/OpenNest.Core/ConvertProgram.cs create mode 100644 Source/OpenNest.Core/CutParameters.cs create mode 100644 Source/OpenNest.Core/Drawing.cs create mode 100644 Source/OpenNest.Core/EvenOdd.cs create mode 100644 Source/OpenNest.Core/Generic.cs create mode 100644 Source/OpenNest.Core/Geometry/Arc.cs create mode 100644 Source/OpenNest.Core/Geometry/Circle.cs create mode 100644 Source/OpenNest.Core/Geometry/DefinedShape.cs create mode 100644 Source/OpenNest.Core/Geometry/Entity.cs create mode 100644 Source/OpenNest.Core/Geometry/EntityType.cs create mode 100644 Source/OpenNest.Core/Geometry/Layer.cs create mode 100644 Source/OpenNest.Core/Geometry/Line.cs create mode 100644 Source/OpenNest.Core/Geometry/Polygon.cs create mode 100644 Source/OpenNest.Core/Geometry/Shape.cs create mode 100644 Source/OpenNest.Core/Helper.cs create mode 100644 Source/OpenNest.Core/IBoundable.cs create mode 100644 Source/OpenNest.Core/IPostProcessor.cs create mode 100644 Source/OpenNest.Core/Material.cs create mode 100644 Source/OpenNest.Core/Nest.cs create mode 100644 Source/OpenNest.Core/NestConstraints.cs create mode 100644 Source/OpenNest.Core/OffsetSide.cs create mode 100644 Source/OpenNest.Core/OpenNest.Core.csproj create mode 100644 Source/OpenNest.Core/Part.cs create mode 100644 Source/OpenNest.Core/Plate.cs create mode 100644 Source/OpenNest.Core/Properties/AssemblyInfo.cs create mode 100644 Source/OpenNest.Core/Quantity.cs create mode 100644 Source/OpenNest.Core/RelativePosition.cs create mode 100644 Source/OpenNest.Core/RotationType.cs create mode 100644 Source/OpenNest.Core/Sequence.cs create mode 100644 Source/OpenNest.Core/Size.cs create mode 100644 Source/OpenNest.Core/Spacing.cs create mode 100644 Source/OpenNest.Core/SpecialLayers.cs create mode 100644 Source/OpenNest.Core/Timing.cs create mode 100644 Source/OpenNest.Core/TimingInfo.cs create mode 100644 Source/OpenNest.Core/Tolerance.cs create mode 100644 Source/OpenNest.Core/Trigonometry.cs create mode 100644 Source/OpenNest.Core/Units.cs create mode 100644 Source/OpenNest.Core/Vector.cs create mode 100644 Source/OpenNest.Engine/BestCombination.cs create mode 100644 Source/OpenNest.Engine/CirclePacking/Bin.cs create mode 100644 Source/OpenNest.Engine/CirclePacking/FillEndEven.cs create mode 100644 Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs create mode 100644 Source/OpenNest.Engine/CirclePacking/FillEngine.cs create mode 100644 Source/OpenNest.Engine/CirclePacking/Item.cs create mode 100644 Source/OpenNest.Engine/NestDirection.cs create mode 100644 Source/OpenNest.Engine/NestEngine.cs create mode 100644 Source/OpenNest.Engine/NestItem.cs create mode 100644 Source/OpenNest.Engine/OpenNest.Engine.csproj create mode 100644 Source/OpenNest.Engine/Properties/AssemblyInfo.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/Bin.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/FillEngine.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/Item.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/PackEngine.cs create mode 100644 Source/OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs create mode 100644 Source/OpenNest.sln create mode 100644 Source/OpenNest/Actions/Action.cs create mode 100644 Source/OpenNest/Actions/ActionAddPart.cs create mode 100644 Source/OpenNest/Actions/ActionClone.cs create mode 100644 Source/OpenNest/Actions/ActionFillArea.cs create mode 100644 Source/OpenNest/Actions/ActionSelect.cs create mode 100644 Source/OpenNest/Actions/ActionSelectArea.cs create mode 100644 Source/OpenNest/Actions/ActionSetSequence.cs create mode 100644 Source/OpenNest/Actions/ActionZoomWindow.cs create mode 100644 Source/OpenNest/ColorScheme.cs create mode 100644 Source/OpenNest/Controls/BottomPanel.cs create mode 100644 Source/OpenNest/Controls/DrawControl.cs create mode 100644 Source/OpenNest/Controls/DrawingListBox.cs create mode 100644 Source/OpenNest/Controls/EntityView.cs create mode 100644 Source/OpenNest/Controls/HorizontalLine.cs create mode 100644 Source/OpenNest/Controls/LayoutViewGL.cs create mode 100644 Source/OpenNest/Controls/NumericUpDown.cs create mode 100644 Source/OpenNest/Controls/PlateView.cs create mode 100644 Source/OpenNest/Controls/QuadrantSelect.cs create mode 100644 Source/OpenNest/Controls/VerticalLine.cs create mode 100644 Source/OpenNest/Document.cs create mode 100644 Source/OpenNest/Forms/AutoNestForm.Designer.cs create mode 100644 Source/OpenNest/Forms/AutoNestForm.cs create mode 100644 Source/OpenNest/Forms/AutoNestForm.resx create mode 100644 Source/OpenNest/Forms/CadConverterForm.Designer.cs create mode 100644 Source/OpenNest/Forms/CadConverterForm.cs create mode 100644 Source/OpenNest/Forms/CadConverterForm.resx create mode 100644 Source/OpenNest/Forms/CutParametersForm.Designer.cs create mode 100644 Source/OpenNest/Forms/CutParametersForm.cs create mode 100644 Source/OpenNest/Forms/CutParametersForm.resx create mode 100644 Source/OpenNest/Forms/EditDrawingForm.Designer.cs create mode 100644 Source/OpenNest/Forms/EditDrawingForm.cs create mode 100644 Source/OpenNest/Forms/EditDrawingForm.resx create mode 100644 Source/OpenNest/Forms/EditNestForm.Designer.cs create mode 100644 Source/OpenNest/Forms/EditNestForm.cs create mode 100644 Source/OpenNest/Forms/EditNestForm.resx create mode 100644 Source/OpenNest/Forms/EditNestInfoForm.Designer.cs create mode 100644 Source/OpenNest/Forms/EditNestInfoForm.cs create mode 100644 Source/OpenNest/Forms/EditNestInfoForm.resx create mode 100644 Source/OpenNest/Forms/EditPlateForm.Designer.cs create mode 100644 Source/OpenNest/Forms/EditPlateForm.cs create mode 100644 Source/OpenNest/Forms/EditPlateForm.resx create mode 100644 Source/OpenNest/Forms/FillPlateForm.Designer.cs create mode 100644 Source/OpenNest/Forms/FillPlateForm.cs create mode 100644 Source/OpenNest/Forms/FillPlateForm.resx create mode 100644 Source/OpenNest/Forms/MainForm.Designer.cs create mode 100644 Source/OpenNest/Forms/MainForm.cs create mode 100644 Source/OpenNest/Forms/MainForm.resx create mode 100644 Source/OpenNest/Forms/OptionsForm.Designer.cs create mode 100644 Source/OpenNest/Forms/OptionsForm.cs create mode 100644 Source/OpenNest/Forms/OptionsForm.resx create mode 100644 Source/OpenNest/Forms/SequenceForm.Designer.cs create mode 100644 Source/OpenNest/Forms/SequenceForm.cs create mode 100644 Source/OpenNest/Forms/SequenceForm.resx create mode 100644 Source/OpenNest/Forms/SetValueForm.Designer.cs create mode 100644 Source/OpenNest/Forms/SetValueForm.cs create mode 100644 Source/OpenNest/Forms/SetValueForm.resx create mode 100644 Source/OpenNest/Forms/TimingForm.Designer.cs create mode 100644 Source/OpenNest/Forms/TimingForm.cs create mode 100644 Source/OpenNest/Forms/TimingForm.resx create mode 100644 Source/OpenNest/GraphicsHelper.cs create mode 100644 Source/OpenNest/IO/DxfExporter.cs create mode 100644 Source/OpenNest/IO/DxfImporter.cs create mode 100644 Source/OpenNest/IO/Extensions.cs create mode 100644 Source/OpenNest/IO/NestReader.cs create mode 100644 Source/OpenNest/IO/NestWriter.cs create mode 100644 Source/OpenNest/IO/ProgramReader.cs create mode 100644 Source/OpenNest/LayoutPart.cs create mode 100644 Source/OpenNest/MainApp.cs create mode 100644 Source/OpenNest/MdiExtensions.cs create mode 100644 Source/OpenNest/OpenNest.csproj create mode 100644 Source/OpenNest/Properties/AssemblyInfo.cs create mode 100644 Source/OpenNest/Properties/Resources.Designer.cs create mode 100644 Source/OpenNest/Properties/Resources.resx create mode 100644 Source/OpenNest/Properties/Settings.Designer.cs create mode 100644 Source/OpenNest/Properties/Settings.settings create mode 100644 Source/OpenNest/PushDirection.cs create mode 100644 Source/OpenNest/Resources/add.png create mode 100644 Source/OpenNest/Resources/clear.png create mode 100644 Source/OpenNest/Resources/clock.png create mode 100644 Source/OpenNest/Resources/doc_new.png create mode 100644 Source/OpenNest/Resources/doc_open.png create mode 100644 Source/OpenNest/Resources/edit_icon.ico create mode 100644 Source/OpenNest/Resources/import.png create mode 100644 Source/OpenNest/Resources/move_first.png create mode 100644 Source/OpenNest/Resources/move_last.png create mode 100644 Source/OpenNest/Resources/move_next.png create mode 100644 Source/OpenNest/Resources/move_previous.png create mode 100644 Source/OpenNest/Resources/remove.png create mode 100644 Source/OpenNest/Resources/rotate_ccw.png create mode 100644 Source/OpenNest/Resources/rotate_cw.png create mode 100644 Source/OpenNest/Resources/save.png create mode 100644 Source/OpenNest/Resources/save_as.png create mode 100644 Source/OpenNest/Resources/watermark.png create mode 100644 Source/OpenNest/Resources/zoom_all.png create mode 100644 Source/OpenNest/Resources/zoom_in.png create mode 100644 Source/OpenNest/Resources/zoom_out.png create mode 100644 Source/OpenNest/SelectionType.cs create mode 100644 Source/OpenNest/ToolStripRenderer.cs create mode 100644 Source/OpenNest/Win32.cs create mode 100644 Source/OpenNest/app.config diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..928a5ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,200 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# NSIS installer directory +Installer/* +!*.nsi + +# Visual Studo 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.[Cc]ache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt diff --git a/External/Ionic.Zip.dll b/External/Ionic.Zip.dll new file mode 100644 index 0000000000000000000000000000000000000000..195af81d17ce6e6524c95c240b252e80b10c623b GIT binary patch literal 462848 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 MuP3#7jU`di~s-t literal 0 HcmV?d00001 diff --git a/External/netDxf.dll b/External/netDxf.dll new file mode 100644 index 0000000000000000000000000000000000000000..66b2c29e62072c27dc00f1609849ee710101ba07 GIT binary patch literal 397312 zcmeFa37lL-wLgC6-tODm%p`Yu(mh#ck}yN3XC@&DOA?kD_QePoP-Z5`9v~Pf=m`j! zGzqw41cQo#VG+gki74({5XBXA#NCGw^ts`NJ~vdt@B2MfckLdCh`;~)yx)I5N#Ckd z=bSoq&Z$#%>(;IN{ISN(+hy7F3oQl9%DR??fBgIU-bnNyF)V=TlD4(nET{bPw-x1p^Utw{ zUtw9(ff}4ZbH*5y`(ZT&oqZWBOccPO|Lj@;wkgQl3Trm`Hx{#&wr( zME=_u(=N8v$nB$n-8=A*8@;40W7uVKpP(Y-t7GxG)?G1Q`MaU$1 z7xjui%4S<>>;5-8*5+$%t3{yy{7-Qo$2u2ep!e*`QpjpMb!1ZZ1b@3rY8hbp?vC;0 zxE4yS{p}eyyIt*zTwk_4(wgn=SoWKY+7I~}C@!w-kJLb+wl~t_2Oz!4F4bvxyR!~^ zrK?9gN1tP%8oIfzO0`P=I!lp(N@#%bPY~ zU32ic&B5!NgD(oicxfm`b3I(XY%i^{o~xkyXTa{c8aM&LYxjU)Ta@5Udq8kol;Axv z0@yIWZ8)LFp7%xx?v4>OwNcN5QG(ya2%4&>ryU+1G`fsTsHNsLbVUi~?*YMrC_#UW z04|o_c4)HE10J&&z(`eADfcz z$D@2dw+DRhjS_rq4+!p$5_~5{(A@REO9EfhkC33Lrgufr9-R{H=Lr4Fduyj)kVuAq z>DLtm$^7^*el{7`{sSliUs{M!HT0$C0{?;pzQDgirn$hsrX*}iLtpq!l)>+(WUv=x z2?Sdl6RdemSOT2o!I+frliK5=5{{5ErZm^ip@DXa``Fe@L@p3`2A6RnuhXuK-c^(r z_eoiY;fiL&@Wuhm7y64CR~?R2l!ZI^Smg-7G759Pd?}-j!bKg8pXxF_cHaRxTKvq$ zC6+bY&)mWBFvxJk3o zXiTa?T~w_@FWsmPKlF&6!`5GPX6=feBSwyD%g>T_I~K?}4GZTt$Y5skI!fO=`MMOC z91A9BJPTZP95R@+q>;g-1@kSLtT4+-i{vqBA+Ewy)J$5aTg+Qd1{(h`ixPT4bJS|! zIi+PK2lExrPDzV6Q*u{y1I_8qvuJM?-NBYuFr9K&q@<|Aq5~5}zeMd$_dzT=L5iqv zPT$-WUzegW7M(3c)P4oCICsU}QgqJpxCA!~bX@}aDuJGxfLOk9F=KUC z>=Xo<8UR6N1w;t-b&DX#Q~(Gv3y4?2$)cl&ejv!GALnqYAjnV;1R3Sy2nPy+4DCRW zQJn}!Qb;vi_yR%3-8hHew@?o<>;geXTyh9qd{Pi(m<582uyGC_5Cj=ufgodRoWr$(AOk88 zWGsy%Tqp=KfC53rP9lW;cC{eLfC&T{E8{ZI1DqZiDuE!QWE>$c2(dvD9U&5Kl7pnv zBjY2u$

+ /// Number of radians equal to 1 degree. + /// + public const double RadPerDeg = Math.PI / 180.0; + + /// + /// Number of degrees equal to 1 radian. + /// + public const double DegPerRad = 180.0 / Math.PI; + + /// + /// Half of PI. + /// + public const double HalfPI = Math.PI * 0.5; + + /// + /// 2 x PI + /// + public const double TwoPI = Math.PI * 2.0; + + /// + /// Converts radians to degrees. + /// + /// + /// + public static double ToDegrees(double radians) + { + return DegPerRad * radians; + } + + /// + /// Converts degrees to radians. + /// + /// + /// + public static double ToRadians(double degrees) + { + return RadPerDeg * degrees; + } + + /// + /// Normalizes an angle. + /// The normalized angle will be in the range from 0 to TwoPI, + /// where TwoPI itself is not included. + /// + /// + /// + public static double NormalizeRad(double angle) + { + double r = angle % Angle.TwoPI; + return r < 0 ? Angle.TwoPI + r : r; + } + + /// + /// Normalizes an angle. + /// The normalized angle will be in the range from 0 to 360, + /// where 360 itself is not included. + /// + /// + /// + public static double NormalizeDeg(double angle) + { + double r = angle % 360.0; + return r < 0 ? 360.0 + r : r; + } + + /// + /// Whether the given angle is between the two specified angles (a1 & a2). + /// + /// + /// + /// + /// + /// + public static bool IsBetweenRad(double angle, double a1, double a2, bool reversed = false) + { + if (reversed) + Generic.Swap(ref a1, ref a2); + + var diff = Angle.NormalizeRad(a2 - a1); + + // full circle + if (a2.IsEqualTo(a1)) + return true; + + a1 = Angle.NormalizeRad(angle - a1); + a2 = Angle.NormalizeRad(a2 - angle); + + return diff >= a1 - Tolerance.Epsilon || + diff >= a2 - Tolerance.Epsilon; + } + + /// + /// Whether the given angle is between the two specified angles (a1 & a2). + /// + /// + /// + /// + /// + /// + public static bool IsBetweenDeg(double angle, double a1, double a2, bool reversed = false) + { + if (reversed) + Generic.Swap(ref a1, ref a2); + + var diff = Angle.NormalizeRad(a2 - a1); + + // full circle + if (a2.IsEqualTo(a1)) + return true; + + a1 = Angle.NormalizeRad(angle - a1); + a2 = Angle.NormalizeRad(a2 - angle); + + return diff >= a1 - Tolerance.Epsilon || + diff >= a2 - Tolerance.Epsilon; + } + } +} diff --git a/Source/OpenNest.Core/BoundingBox.cs b/Source/OpenNest.Core/BoundingBox.cs new file mode 100644 index 0000000..5c94b75 --- /dev/null +++ b/Source/OpenNest.Core/BoundingBox.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest +{ + public static class BoundingBox + { + public static Box GetBoundingBox(this IList boxes) + { + if (boxes.Count == 0) + return Box.Empty; + + double minX = boxes[0].X; + double minY = boxes[0].Y; + double maxX = boxes[0].X + boxes[0].Width; + double maxY = boxes[0].Y + boxes[0].Height; + + foreach (var box in boxes) + { + if (box.Left < minX) minX = box.Left; + if (box.Right > maxX) maxX = box.Right; + if (box.Bottom < minY) minY = box.Bottom; + if (box.Top > maxY) maxY = box.Top; + } + + return new Box(minX, minY, maxX - minX, maxY - minY); + } + + public static Box GetBoundingBox(this IList pts) + { + if (pts.Count == 0) + return Box.Empty; + + var first = pts[0]; + var minX = first.X; + var maxX = first.X; + var minY = first.Y; + var maxY = first.Y; + + for (int i = 1; i < pts.Count; ++i) + { + var vertex = pts[i]; + + if (vertex.X < minX) minX = vertex.X; + else if (vertex.X > maxX) maxX = vertex.X; + + if (vertex.Y < minY) minY = vertex.Y; + else if (vertex.Y > maxY) maxY = vertex.Y; + } + + return new Box(minX, minY, maxX - minX, maxY - minY); + } + + public static Box GetBoundingBox(this IEnumerable items) + { + var first = items.FirstOrDefault(); + + if (first == null) + return Box.Empty; + + double left = first.Left; + double bottom = first.Bottom; + double right = first.Right; + double top = first.Top; + + foreach (var box in items) + { + if (box.Left < left) left = box.Left; + if (box.Right > right) right = box.Right; + if (box.Bottom < bottom) bottom = box.Bottom; + if (box.Top > top) top = box.Top; + } + + return new Box(left, bottom, right - left, top - bottom); + } + } +} diff --git a/Source/OpenNest.Core/Box.cs b/Source/OpenNest.Core/Box.cs new file mode 100644 index 0000000..12710de --- /dev/null +++ b/Source/OpenNest.Core/Box.cs @@ -0,0 +1,206 @@ +namespace OpenNest +{ + public class Box + { + public static readonly Box Empty = new Box(); + + public Box() + : this(0, 0, 0, 0) + { + } + + public Box(double x, double y, double w, double h) + { + Location = new Vector(x, y); + Width = w; + Height = h; + } + + public Vector Location; + + public Vector Center + { + get { return new Vector(X + Width * 0.5, Y + Height * 0.5); } + } + + public Size Size; + + public double X + { + get { return Location.X; } + set { Location.X = value; } + } + + public double Y + { + get { return Location.Y; } + set { Location.Y = value; } + } + + public double Width + { + get { return Size.Width; } + set { Size.Width = value; } + } + + public double Height + { + get { return Size.Height; } + set { Size.Height = value; } + } + + public void MoveTo(double x, double y) + { + X = x; + Y = y; + } + + public void MoveTo(Vector pt) + { + X = pt.X; + Y = pt.Y; + } + + public void Offset(double x, double y) + { + X += x; + Y += y; + } + + public void Offset(Vector voffset) + { + Location += voffset; + } + + public double Left + { + get { return X; } + } + + public double Right + { + get { return X + Width; } + } + + public double Top + { + get { return Y + Height; } + } + + public double Bottom + { + get { return Y; } + } + + public double Area() + { + return Width * Height; + } + + public double Perimeter() + { + return Width * 2 + Height * 2; + } + + public bool Intersects(Box box) + { + if (Left >= box.Right) return false; + if (Right <= box.Left) return false; + if (Top <= box.Bottom) return false; + if (Bottom >= box.Top) return false; + + return true; + } + + public bool Intersects(Box box, out Box intersectingBox) + { + if (!Intersects(box)) + { + intersectingBox = Box.Empty; + return false; + } + + var left = Left < box.Left ? box.Left : Left; + var right = Right < box.Right ? Right : box.Right; + + var bottom = Bottom < box.Bottom ? box.Bottom : Bottom; + var top = Top < box.Top ? Top : box.Top; + + intersectingBox = new Box(left, bottom, right - left, top - bottom); + + return true; + } + + public bool Contains(Box box) + { + if (box.Top > Top) return false; + if (box.Left < Left) return false; + if (box.Right > Right) return false; + if (box.Bottom < Bottom) return false; + + return true; + } + + public bool Contains(Vector pt) + { + return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon + && pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon; + } + + public bool IsHorizontalTo(Box box) + { + return Bottom > box.Top || Top < box.Bottom; + } + + public bool IsHorizontalTo(Box box, out RelativePosition pos) + { + if (Bottom >= box.Top || Top <= box.Bottom) + { + pos = RelativePosition.None; + return false; + } + + if (Left >= box.Right) + pos = RelativePosition.Right; + else if (Right <= box.Left) + pos = RelativePosition.Left; + else + pos = RelativePosition.Intersecting; + + return true; + } + + public bool IsVerticalTo(Box box) + { + return Left > box.Right || Right < box.Left; + } + + public bool IsVerticalTo(Box box, out RelativePosition pos) + { + if (Left >= box.Right || Right <= box.Left) + { + pos = RelativePosition.None; + return false; + } + + if (Bottom >= box.Top) + pos = RelativePosition.Top; + else if (Top <= box.Bottom) + pos = RelativePosition.Bottom; + else + pos = RelativePosition.Intersecting; + + return true; + } + + public Box Offset(double d) + { + return new Box(X - d, Y - d, Width + d * 2, Height + d * 2); + } + + public override string ToString() + { + return string.Format("[Box: X={0}, Y={1}, Width={2}, Height={3}]", X, Y, Width, Height); + } + } +} diff --git a/Source/OpenNest.Core/BoxSplitter.cs b/Source/OpenNest.Core/BoxSplitter.cs new file mode 100644 index 0000000..30a2d7c --- /dev/null +++ b/Source/OpenNest.Core/BoxSplitter.cs @@ -0,0 +1,57 @@ +namespace OpenNest +{ + public static class BoxSplitter + { + public static Box SplitTop(Box large, Box small) + { + if (!large.Intersects(small)) + return Box.Empty; + + var x = large.Left; + var y = small.Top; + var w = large.Width; + var h = large.Top - y; + + return new Box(x, y, w, h); + } + + public static Box SplitLeft(Box large, Box small) + { + if (!large.Intersects(small)) + return Box.Empty; + + var x = large.Left; + var y = large.Bottom; + var w = small.Left - x; + var h = large.Height; + + return new Box(x, y, w, h); + } + + public static Box SplitBottom(Box large, Box small) + { + if (!large.Intersects(small)) + return Box.Empty; + + var x = large.Left; + var y = large.Bottom; + var w = large.Width; + var h = small.Top - y; + + return new Box(x, y, w, h); + } + + public static Box SplitRight(Box large, Box small) + { + if (!large.Intersects(small)) + return Box.Empty; + + var x = small.Right; + var y = large.Bottom; + var w = large.Right - x; + var h = large.Height; + + return new Box(x, y, w, h); + } + } +} diff --git a/Source/OpenNest.Core/CNC/CircularMove.cs b/Source/OpenNest.Core/CNC/CircularMove.cs new file mode 100644 index 0000000..d1a4044 --- /dev/null +++ b/Source/OpenNest.Core/CNC/CircularMove.cs @@ -0,0 +1,89 @@ + +namespace OpenNest.CNC +{ + public class CircularMove : Motion + { + public CircularMove() + { + } + + public CircularMove(double x, double y, double i, double j, RotationType rotation = RotationType.CCW) + : this(new Vector(x, y), new Vector(i, j), rotation) + { + } + + public CircularMove(Vector endPoint, Vector centerPoint, RotationType rotation = RotationType.CCW) + { + EndPoint = endPoint; + CenterPoint = centerPoint; + Rotation = rotation; + Layer = LayerType.Cut; + } + + public LayerType Layer { get; set; } + + public RotationType Rotation { get; set; } + + public Vector CenterPoint { get; set; } + + public double Radius + { + get { return CenterPoint.DistanceTo(EndPoint); } + } + + public override void Rotate(double angle) + { + base.Rotate(angle); + CenterPoint = CenterPoint.Rotate(angle); + } + + public override void Rotate(double angle, Vector origin) + { + base.Rotate(angle, origin); + CenterPoint = CenterPoint.Rotate(angle, origin); + } + + public override void Offset(double x, double y) + { + base.Offset(x, y); + CenterPoint = new Vector(CenterPoint.X + x, CenterPoint.Y + y); + } + + public override void Offset(Vector voffset) + { + base.Offset(voffset); + CenterPoint += voffset; + } + + public override CodeType Type + { + get { return CodeType.CircularMove; } + } + + public override ICode Clone() + { + return new CircularMove(EndPoint, CenterPoint, Rotation) + { + Layer = Layer + }; + } + + public override string ToString() + { + return ToString(DefaultDecimalPlaces); + } + + public override string ToString(int decimalPlaces) + { + var dp = "N" + decimalPlaces; + var x = EndPoint.X.ToString(dp); + var y = EndPoint.Y.ToString(dp); + var i = CenterPoint.X.ToString(dp); + var j = CenterPoint.Y.ToString(dp); + + return Rotation == RotationType.CW ? + string.Format("G02 X{0} Y{1} I{2} J{3}", x, y, i, j) : + string.Format("G03 X{0} Y{1} I{2} J{3}", x, y, i, j); + } + } +} diff --git a/Source/OpenNest.Core/CNC/CodeType.cs b/Source/OpenNest.Core/CNC/CodeType.cs new file mode 100644 index 0000000..f79b4c7 --- /dev/null +++ b/Source/OpenNest.Core/CNC/CodeType.cs @@ -0,0 +1,14 @@ + +namespace OpenNest.CNC +{ + public enum CodeType + { + CircularMove, + Comment, + LinearMove, + RapidMove, + SetFeedrate, + SetKerf, + SubProgramCall + } +} diff --git a/Source/OpenNest.Core/CNC/Comment.cs b/Source/OpenNest.Core/CNC/Comment.cs new file mode 100644 index 0000000..b2aa4dd --- /dev/null +++ b/Source/OpenNest.Core/CNC/Comment.cs @@ -0,0 +1,31 @@ +namespace OpenNest.CNC +{ + public class Comment : ICode + { + public Comment() + { + } + + public Comment(string value) + { + Value = value; + } + + public string Value { get; set; } + + public CodeType Type + { + get { return CodeType.Comment; } + } + + public ICode Clone() + { + return new Comment(Value); + } + + public override string ToString() + { + return ':' + Value; + } + } +} diff --git a/Source/OpenNest.Core/CNC/Feedrate.cs b/Source/OpenNest.Core/CNC/Feedrate.cs new file mode 100644 index 0000000..03e177c --- /dev/null +++ b/Source/OpenNest.Core/CNC/Feedrate.cs @@ -0,0 +1,35 @@ +namespace OpenNest.CNC +{ + public class Feedrate : ICode + { + public const int UseDefault = -1; + + public const int UseMax = -2; + + public Feedrate() + { + } + + public Feedrate(double value) + { + Value = value; + } + + public double Value { get; set; } + + public CodeType Type + { + get { return CodeType.SetFeedrate; } + } + + public ICode Clone() + { + return new Feedrate(Value); + } + + public override string ToString() + { + return string.Format("F{0}", Value); + } + } +} diff --git a/Source/OpenNest.Core/CNC/ICode.cs b/Source/OpenNest.Core/CNC/ICode.cs new file mode 100644 index 0000000..6705aa0 --- /dev/null +++ b/Source/OpenNest.Core/CNC/ICode.cs @@ -0,0 +1,9 @@ +namespace OpenNest.CNC +{ + public interface ICode + { + CodeType Type { get; } + + ICode Clone(); + } +} diff --git a/Source/OpenNest.Core/CNC/Kerf.cs b/Source/OpenNest.Core/CNC/Kerf.cs new file mode 100644 index 0000000..0294c82 --- /dev/null +++ b/Source/OpenNest.Core/CNC/Kerf.cs @@ -0,0 +1,39 @@ +namespace OpenNest.CNC +{ + public class Kerf : ICode + { + public Kerf(KerfType kerf = KerfType.Left) + { + Value = kerf; + } + + public KerfType Value { get; set; } + + public CodeType Type + { + get { return CodeType.SetKerf; } + } + + public ICode Clone() + { + return new Kerf(Value); + } + + public override string ToString() + { + switch (Value) + { + case KerfType.Left: + return "G41"; + + case KerfType.Right: + return "G42"; + + case KerfType.None: + return "G40"; + } + + return string.Empty; + } + } +} diff --git a/Source/OpenNest.Core/CNC/KerfType.cs b/Source/OpenNest.Core/CNC/KerfType.cs new file mode 100644 index 0000000..8341cfa --- /dev/null +++ b/Source/OpenNest.Core/CNC/KerfType.cs @@ -0,0 +1,10 @@ + +namespace OpenNest.CNC +{ + public enum KerfType + { + None, + Left, + Right + } +} diff --git a/Source/OpenNest.Core/CNC/LayerType.cs b/Source/OpenNest.Core/CNC/LayerType.cs new file mode 100644 index 0000000..1d0c0c0 --- /dev/null +++ b/Source/OpenNest.Core/CNC/LayerType.cs @@ -0,0 +1,12 @@ + +namespace OpenNest.CNC +{ + public enum LayerType + { + Display, + Scribe, + Cut, + Leadin, + Leadout + } +} diff --git a/Source/OpenNest.Core/CNC/LinearMove.cs b/Source/OpenNest.Core/CNC/LinearMove.cs new file mode 100644 index 0000000..37905ba --- /dev/null +++ b/Source/OpenNest.Core/CNC/LinearMove.cs @@ -0,0 +1,47 @@ +namespace OpenNest.CNC +{ + public class LinearMove : Motion + { + public LinearMove() + : this(new Vector()) + { + } + + public LinearMove(double x, double y) + : this(new Vector(x, y)) + { + } + + public LinearMove(Vector endPoint) + { + EndPoint = endPoint; + Layer = LayerType.Cut; + } + + public LayerType Layer { get; set; } + + public override CodeType Type + { + get { return CodeType.LinearMove; } + } + + public override ICode Clone() + { + return new LinearMove(EndPoint) + { + Layer = Layer + }; + } + + public override string ToString() + { + return ToString(DefaultDecimalPlaces); + } + + public override string ToString(int decimalPlaces) + { + var dp = "N" + decimalPlaces; + return string.Format("G01 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp)); + } + } +} diff --git a/Source/OpenNest.Core/CNC/Mode.cs b/Source/OpenNest.Core/CNC/Mode.cs new file mode 100644 index 0000000..eb59145 --- /dev/null +++ b/Source/OpenNest.Core/CNC/Mode.cs @@ -0,0 +1,9 @@ + +namespace OpenNest.CNC +{ + public enum Mode + { + Absolute, + Incremental + } +} diff --git a/Source/OpenNest.Core/CNC/Motion.cs b/Source/OpenNest.Core/CNC/Motion.cs new file mode 100644 index 0000000..427e0e0 --- /dev/null +++ b/Source/OpenNest.Core/CNC/Motion.cs @@ -0,0 +1,45 @@ + +namespace OpenNest.CNC +{ + public abstract class Motion : ICode + { + protected const int DefaultDecimalPlaces = 4; + + public Vector EndPoint { get; set; } + + public bool UseExactStop { get; set; } + + public int Feedrate { get; set; } + + protected Motion() + { + Feedrate = CNC.Feedrate.UseDefault; + } + + public virtual void Rotate(double angle) + { + EndPoint = EndPoint.Rotate(angle); + } + + public virtual void Rotate(double angle, Vector origin) + { + EndPoint = EndPoint.Rotate(angle, origin); + } + + public virtual void Offset(double x, double y) + { + EndPoint = new Vector(EndPoint.X + x, EndPoint.Y + y); + } + + public virtual void Offset(Vector voffset) + { + EndPoint += voffset; + } + + public abstract CodeType Type { get; } + + public abstract ICode Clone(); + + public abstract string ToString(int decimalPlaces); + } +} diff --git a/Source/OpenNest.Core/CNC/Program.cs b/Source/OpenNest.Core/CNC/Program.cs new file mode 100644 index 0000000..094894a --- /dev/null +++ b/Source/OpenNest.Core/CNC/Program.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest.CNC +{ + public class Program + { + public List Codes; + + private Mode mode; + + public Program(Mode mode = Mode.Absolute) + { + Codes = new List(); + Mode = mode; + } + + public Mode Mode + { + get { return mode; } + set + { + if (value == Mode.Absolute) + SetModeAbs(); + else + SetModeInc(); + } + } + + public double Rotation { get; protected set; } + + private void SetModeInc() + { + if (mode == Mode.Incremental) + return; + + ConvertMode.ToIncremental(this); + + mode = Mode.Incremental; + } + + private void SetModeAbs() + { + if (mode == Mode.Absolute) + return; + + ConvertMode.ToAbsolute(this); + + mode = Mode.Absolute; + } + + public virtual void Rotate(double angle) + { + var mode = Mode; + + SetModeAbs(); + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + + if (code.Type == CodeType.SubProgramCall) + { + var subpgm = (SubProgramCall)code; + + if (subpgm.Program != null) + subpgm.Program.Rotate(angle); + } + + if (code is Motion == false) + continue; + + var code2 = (Motion)code; + + code2.Rotate(angle); + } + + if (mode == Mode.Incremental) + SetModeInc(); + + Rotation = Angle.NormalizeRad(Rotation + angle); + } + + public virtual void Rotate(double angle, Vector origin) + { + var mode = Mode; + + SetModeAbs(); + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + + if (code.Type == CodeType.SubProgramCall) + { + var subpgm = (SubProgramCall)code; + + if (subpgm.Program != null) + subpgm.Program.Rotate(angle); + } + + if (code is Motion == false) + continue; + + var code2 = (Motion)code; + + code2.Rotate(angle, origin); + } + + if (mode == Mode.Incremental) + SetModeInc(); + + Rotation = Angle.NormalizeRad(Rotation + angle); + } + + public virtual void Offset(double x, double y) + { + var mode = Mode; + + SetModeAbs(); + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + + if (code is Motion == false) + continue; + + var code2 = (Motion)code; + + code2.Offset(x, y); + } + + if (mode == Mode.Incremental) + SetModeInc(); + } + + public virtual void Offset(Vector voffset) + { + var mode = Mode; + + SetModeAbs(); + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + + if (code is Motion == false) + continue; + + var code2 = (Motion)code; + + code2.Offset(voffset); + } + + if (mode == Mode.Incremental) + SetModeInc(); + } + + public void LineTo(double x, double y) + { + Codes.Add(new LinearMove(x, y)); + } + + public void LineTo(Vector pt) + { + Codes.Add(new LinearMove(pt)); + } + + public void MoveTo(double x, double y) + { + Codes.Add(new RapidMove(x, y)); + } + + public void MoveTo(Vector pt) + { + Codes.Add(new RapidMove(pt)); + } + + public void ArcTo(double x, double y, double i, double j, RotationType rotation) + { + Codes.Add(new CircularMove(x, y, i, j, rotation)); + } + + public void ArcTo(Vector endpt, Vector center, RotationType rotation) + { + Codes.Add(new CircularMove(endpt, center, rotation)); + } + + public void AddSubProgram(Program program) + { + Codes.Add(new SubProgramCall(program, program.Rotation)); + } + + public ICode this[int index] + { + get { return Codes[index]; } + set { Codes[index] = value; } + } + + public int Length + { + get { return Codes.Count; } + } + + public void Merge(Program pgm) + { + // Set the program to be merged to the same Mode as the current. + pgm.Mode = this.Mode; + + if (Mode == Mode.Absolute) + { + bool isRapid = false; + + // Check if the first motion code is a rapid move + foreach (var code in pgm.Codes) + { + if (code is Motion == false) + continue; + + var motion = (Motion)code; + + isRapid = motion.GetType() == typeof(RapidMove); + break; + } + + // If the first motion code is not a rapid, move to the origin. + if (!isRapid) + MoveTo(0, 0); + + Codes.AddRange(pgm.Codes); + } + else + { + Codes.AddRange(pgm.Codes); + } + } + + public Vector EndPoint() + { + switch (Mode) + { + case Mode.Absolute: + { + for (int i = Codes.Count; i >= 0; --i) + { + var code = Codes[i]; + var motion = code as Motion; + + if (motion == null) continue; + + return motion.EndPoint; + } + break; + } + + case Mode.Incremental: + { + var pos = new Vector(0, 0); + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + var motion = code as Motion; + + if (motion == null) continue; + + pos += motion.EndPoint; + } + + return pos; + } + } + + return new Vector(0, 0); + } + + public Box BoundingBox() + { + var origin = new Vector(0, 0); + return BoundingBox(ref origin); + } + + private Box BoundingBox(ref Vector pos) + { + double minX = 0.0; + double minY = 0.0; + double maxX = 0.0; + double maxY = 0.0; + + for (int i = 0; i < Codes.Count; ++i) + { + var code = Codes[i]; + + switch (code.Type) + { + case CodeType.LinearMove: + { + var line = (LinearMove)code; + var pt = Mode == Mode.Absolute ? + line.EndPoint : + line.EndPoint + pos; + + if (pt.X > maxX) + maxX = pt.X; + else if (pt.X < minX) + minX = pt.X; + + if (pt.Y > maxY) + maxY = pt.Y; + else if (pt.Y < minY) + minY = pt.Y; + + pos = pt; + + break; + } + + case CodeType.RapidMove: + { + var line = (RapidMove)code; + var pt = Mode == Mode.Absolute + ? line.EndPoint + : line.EndPoint + pos; + + if (pt.X > maxX) + maxX = pt.X; + else if (pt.X < minX) + minX = pt.X; + + if (pt.Y > maxY) + maxY = pt.Y; + else if (pt.Y < minY) + minY = pt.Y; + + pos = pt; + + break; + } + + case CodeType.CircularMove: + { + var arc = (CircularMove)code; + var radius = arc.CenterPoint.DistanceTo(arc.EndPoint); + + Vector endpt; + Vector centerpt; + + if (Mode == Mode.Incremental) + { + endpt = arc.EndPoint + pos; + centerpt = arc.CenterPoint + pos; + } + else + { + endpt = arc.EndPoint; + centerpt = arc.CenterPoint; + } + + double minX1; + double minY1; + double maxX1; + double maxY1; + + if (pos.X < endpt.X) + { + minX1 = pos.X; + maxX1 = endpt.X; + } + else + { + minX1 = endpt.X; + maxX1 = pos.X; + } + + if (pos.Y < endpt.Y) + { + minY1 = pos.Y; + maxY1 = endpt.Y; + } + else + { + minY1 = endpt.Y; + maxY1 = pos.Y; + } + + var startAngle = pos.AngleFrom(centerpt); + var endAngle = endpt.AngleFrom(centerpt); + + // switch the angle to counter clockwise. + if (arc.Rotation == RotationType.CW) + Generic.Swap(ref startAngle, ref endAngle); + + startAngle = Angle.NormalizeRad(startAngle); + endAngle = Angle.NormalizeRad(endAngle); + + if (Angle.IsBetweenRad(Angle.HalfPI, startAngle, endAngle)) + maxY1 = centerpt.Y + radius; + + if (Angle.IsBetweenRad(Math.PI, startAngle, endAngle)) + minX1 = centerpt.X - radius; + + const double oneHalfPI = Math.PI * 1.5; + + if (Angle.IsBetweenRad(oneHalfPI, startAngle, endAngle)) + minY1 = centerpt.Y - radius; + + if (Angle.IsBetweenRad(Angle.TwoPI, startAngle, endAngle)) + maxX1 = centerpt.X + radius; + + if (maxX1 > maxX) + maxX = maxX1; + + if (minX1 < minX) + minX = minX1; + + if (maxY1 > maxY) + maxY = maxY1; + + if (minY1 < minY) + minY = minY1; + + pos = endpt; + + break; + } + + case CodeType.SubProgramCall: + { + var subpgm = (SubProgramCall)code; + var box = subpgm.Program.BoundingBox(ref pos); + + if (box.Left < minX) + minX = box.Left; + + if (box.Right > maxX) + maxX = box.Right; + + if (box.Bottom < minY) + minY = box.Bottom; + + if (box.Top > maxY) + maxY = box.Top; + + break; + } + } + } + + return new Box(minX, minY, maxX - minX, maxY - minY); + } + + public object Clone() + { + var pgm = new Program() + { + mode = this.mode, + Rotation = this.Rotation + }; + + var codes = new ICode[Length]; + + for (int i = 0; i < Length; ++i) + codes[i] = this.Codes[i].Clone(); + + pgm.Codes.AddRange(codes); + + return pgm; + } + + public List ToGeometry() + { + return ConvertProgram.ToGeometry(this); + } + } +} diff --git a/Source/OpenNest.Core/CNC/RapidMove.cs b/Source/OpenNest.Core/CNC/RapidMove.cs new file mode 100644 index 0000000..24fe08e --- /dev/null +++ b/Source/OpenNest.Core/CNC/RapidMove.cs @@ -0,0 +1,42 @@ + +namespace OpenNest.CNC +{ + public class RapidMove : Motion + { + public RapidMove() + { + Feedrate = CNC.Feedrate.UseMax; + } + + public RapidMove(Vector endPoint) + { + EndPoint = endPoint; + } + + public RapidMove(double x, double y) + { + EndPoint = new Vector(x, y); + } + + public override CodeType Type + { + get { return CodeType.RapidMove; } + } + + public override ICode Clone() + { + return new RapidMove(EndPoint); + } + + public override string ToString() + { + return ToString(DefaultDecimalPlaces); + } + + public override string ToString(int decimalPlaces) + { + var dp = "N" + decimalPlaces; + return string.Format("G00 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp)); + } + } +} diff --git a/Source/OpenNest.Core/CNC/SubProgramCall.cs b/Source/OpenNest.Core/CNC/SubProgramCall.cs new file mode 100644 index 0000000..9988242 --- /dev/null +++ b/Source/OpenNest.Core/CNC/SubProgramCall.cs @@ -0,0 +1,87 @@ +namespace OpenNest.CNC +{ + public class SubProgramCall : ICode + { + private double rotation; + private Program program; + + public SubProgramCall() + { + } + + public SubProgramCall(Program program, double rotation) + { + this.program = program; + this.Rotation = rotation; + } + + /// + /// The program ID. + /// + public int Id { get; set; } + + /// + /// Gets or sets the program. + /// + public Program Program + { + get { return program; } + set + { + program = value; + UpdateProgramRotation(); + } + } + + /// + /// Gets or sets the rotation of the program in degrees. + /// + public double Rotation + { + get { return rotation; } + set + { + rotation = value; + UpdateProgramRotation(); + } + } + + /// + /// Rotates the program by the difference of the current + /// rotation set in the sub program call and the program. + /// + private void UpdateProgramRotation() + { + if (program != null) + { + var diffAngle = Angle.ToRadians(rotation) - program.Rotation; + + if (!diffAngle.IsEqualTo(0.0)) + program.Rotate(diffAngle); + } + } + + /// + /// Gets the code type. + /// + /// + public CodeType Type + { + get { return CodeType.SubProgramCall; } + } + + /// + /// Gets a shallow copy. + /// + /// + public ICode Clone() + { + return new SubProgramCall(program, Rotation); + } + + public override string ToString() + { + return string.Format("G65 P{0} R{1}", Id, Rotation); + } + } +} diff --git a/Source/OpenNest.Core/Collections/DrawingCollection.cs b/Source/OpenNest.Core/Collections/DrawingCollection.cs new file mode 100644 index 0000000..3b7969b --- /dev/null +++ b/Source/OpenNest.Core/Collections/DrawingCollection.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace OpenNest.Collections +{ + public class DrawingCollection : HashSet + { + } +} diff --git a/Source/OpenNest.Core/Collections/PartCollection.cs b/Source/OpenNest.Core/Collections/PartCollection.cs new file mode 100644 index 0000000..6a7c7ca --- /dev/null +++ b/Source/OpenNest.Core/Collections/PartCollection.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OpenNest.Collections +{ + public class PartCollection : IList, ICollection, IEnumerable + { + private readonly List parts; + + public event EventHandler PartAdded; + + public event EventHandler PartRemoved; + + public event EventHandler PartChanged; + + public PartCollection() + { + parts = new List(); + } + + public void Add(Part item) + { + var index = parts.Count; + parts.Add(item); + + if (PartAdded != null) + PartAdded.Invoke(this, new PartAddedEventArgs(item, index)); + } + + public void AddRange(IEnumerable collection) + { + var index = parts.Count; + parts.AddRange(collection); + + if (PartAdded != null) + { + foreach (var part in collection) + PartAdded.Invoke(this, new PartAddedEventArgs(part, index++)); + } + } + + public void Insert(int index, Part item) + { + parts.Insert(index, item); + + if (PartAdded != null) + PartAdded.Invoke(this, new PartAddedEventArgs(item, index)); + } + + public bool Remove(Part item) + { + var success = parts.Remove(item); + + if (PartRemoved != null) + PartRemoved.Invoke(this, new PartRemovedEventArgs(item, success)); + + return success; + } + + public void RemoveAt(int index) + { + if (PartRemoved != null) + { + var part = parts[index]; + + parts.RemoveAt(index); + PartRemoved.Invoke(this, new PartRemovedEventArgs(part, true)); + } + else + { + parts.RemoveAt(index); + } + } + + public void Clear() + { + for (int i = parts.Count - 1; i >= 0; --i) + RemoveAt(i); + } + + public int IndexOf(Part item) + { + return parts.IndexOf(item); + } + + public Part this[int index] + { + get + { + return parts[index]; + } + set + { + var old = parts[index]; + + parts[index] = value; + + if (PartChanged != null) + { + var eventArgs = new PartChangedEventArgs(old, value, index); + PartChanged.Invoke(this, eventArgs); + } + } + } + + public bool Contains(Part item) + { + return parts.Contains(item); + } + + public void CopyTo(Part[] array, int arrayIndex) + { + parts.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return parts.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public object ForEach { get; set; } + + public IEnumerator GetEnumerator() + { + return parts.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return parts.GetEnumerator(); + } + } + + public class PartAddedEventArgs : EventArgs + { + public readonly Part Part; + public readonly int Index; + + public PartAddedEventArgs(Part part, int index) + { + Part = part; + Index = index; + } + } + + public class PartRemovedEventArgs : EventArgs + { + public readonly Part Part; + public readonly bool Succeeded; + + public PartRemovedEventArgs(Part part, bool succeeded) + { + Part = part; + Succeeded = succeeded; + } + } + + public class PartChangedEventArgs : EventArgs + { + public readonly Part OldPart; + public readonly Part NewPart; + public readonly int Index; + + public PartChangedEventArgs(Part oldPart, Part newPart, int index) + { + OldPart = oldPart; + NewPart = newPart; + Index = index; + } + } +} diff --git a/Source/OpenNest.Core/Collections/PlateCollection.cs b/Source/OpenNest.Core/Collections/PlateCollection.cs new file mode 100644 index 0000000..c40375e --- /dev/null +++ b/Source/OpenNest.Core/Collections/PlateCollection.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.Collections +{ + public class PlateCollection : IList, ICollection, IEnumerable + { + private List plates; + + public event EventHandler PlateAdded; + + public event EventHandler PlateRemoved; + + public event EventHandler PlateChanged; + + public PlateCollection() + { + plates = new List(); + } + + public void Add(Plate item) + { + var index = plates.Count; + plates.Add(item); + + if (PlateAdded != null) + PlateAdded.Invoke(this, new PlateAddedEventArgs(item, index)); + } + + public void AddRange(IEnumerable collection) + { + var index = plates.Count; + plates.AddRange(collection); + + if (PlateAdded != null) + { + foreach (var plate in collection) + PlateAdded.Invoke(this, new PlateAddedEventArgs(plate, index++)); + } + } + + public void Insert(int index, Plate item) + { + plates.Insert(index, item); + + if (PlateAdded != null) + PlateAdded.Invoke(this, new PlateAddedEventArgs(item, index)); + } + + public bool Remove(Plate item) + { + var success = plates.Remove(item); + + if (PlateRemoved != null) + PlateRemoved.Invoke(this, new PlateRemovedEventArgs(item, success)); + + return success; + } + + public void RemoveAt(int index) + { + var plate = plates[index]; + plates.RemoveAt(index); + + if (PlateRemoved != null) + PlateRemoved.Invoke(this, new PlateRemovedEventArgs(plate, true)); + } + + public void Clear() + { + for (int i = plates.Count - 1; i >= 0; --i) + RemoveAt(i); + } + + public int IndexOf(Plate item) + { + return plates.IndexOf(item); + } + + public Plate this[int index] + { + get + { + return plates[index]; + } + set + { + var old = plates[index]; + + plates[index] = value; + + if (PlateChanged != null) + { + var eventArgs = new PlateChangedEventArgs(old, value, index); + PlateChanged.Invoke(this, eventArgs); + } + } + } + + public bool Contains(Plate item) + { + return plates.Contains(item); + } + + public void CopyTo(Plate[] array, int arrayIndex) + { + plates.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return plates.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public IEnumerator GetEnumerator() + { + return plates.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return plates.GetEnumerator(); + } + + public void RemoveEmptyPlates() + { + if (Count < 2) + return; + + for (int i = Count - 1; i >= 0; --i) + { + if (this[i].Parts.Count == 0) + RemoveAt(i); + } + } + + public int TotalCount + { + get { return plates.Sum(plate => plate.Quantity); } + } + } + + public class PlateAddedEventArgs : EventArgs + { + public readonly Plate Plate; + public readonly int Index; + + public PlateAddedEventArgs(Plate plate, int index) + { + Plate = plate; + Index = index; + } + } + + public class PlateChangedEventArgs : EventArgs + { + public readonly Plate OldPlate; + public readonly Plate NewPlate; + public readonly int Index; + + public PlateChangedEventArgs(Plate oldPlate, Plate newPlate, int index) + { + OldPlate = oldPlate; + NewPlate = newPlate; + Index = index; + } + } + + public class PlateRemovedEventArgs : EventArgs + { + public readonly Plate Plate; + public readonly bool Succeeded; + + public PlateRemovedEventArgs(Plate plate, bool succeeded) + { + Plate = plate; + Succeeded = succeeded; + } + } +} diff --git a/Source/OpenNest.Core/ConvertGeometry.cs b/Source/OpenNest.Core/ConvertGeometry.cs new file mode 100644 index 0000000..e984a29 --- /dev/null +++ b/Source/OpenNest.Core/ConvertGeometry.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class ConvertGeometry + { + public static Program ToProgram(IList geometry) + { + var shapes = Helper.GetShapes(geometry); + + if (shapes.Count == 0) + return null; + + var perimeter = shapes[0]; + var area = perimeter.BoundingBox.Area(); + var index = 0; + + for (int i = 1; i < shapes.Count; ++i) + { + var program = shapes[i]; + var area2 = program.BoundingBox.Area(); + if (area2 > area) + { + perimeter = program; + area = area2; + index = i; + } + } + + shapes.RemoveAt(index); + + var pgm = new Program(); + + foreach (var shape in shapes) + { + var subpgm = ToProgram(shape); + pgm.Merge(subpgm); + } + + pgm.Merge(ToProgram(perimeter)); + pgm.Mode = Mode.Incremental; + + return pgm; + } + + public static Program ToProgram(Shape shape) + { + var pgm = new Program(); + var lastpt = new Vector(); + + for (int i = 0; i < shape.Entities.Count; i++) + lastpt = AddEntity(pgm, lastpt, shape.Entities[i]); + + return pgm; + } + + private static Vector AddEntity(Program pgm, Vector lastpt, Entity geo) + { + switch (geo.Type) + { + case EntityType.Arc: + lastpt = AddArc(pgm, lastpt, (Arc)geo); + break; + + case EntityType.Circle: + lastpt = AddCircle(pgm, lastpt, (Circle)geo); + break; + + case EntityType.Line: + lastpt = AddLine(pgm, lastpt, (Line)geo); + break; + } + + return lastpt; + } + + private static Vector AddArc(Program pgm, Vector lastpt, Arc arc) + { + var startpt = arc.StartPoint(); + var endpt = arc.EndPoint(); + + if (startpt != lastpt) + pgm.MoveTo(startpt); + + lastpt = endpt; + + pgm.ArcTo(endpt, arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW); + return lastpt; + } + + private static Vector AddCircle(Program pgm, Vector lastpt, Circle circle) + { + var startpt = new Vector(circle.Center.X + circle.Radius, circle.Center.Y); + + if (startpt != lastpt) + pgm.MoveTo(startpt); + + pgm.ArcTo(startpt, circle.Center, RotationType.CCW); + + lastpt = startpt; + return lastpt; + } + + private static Vector AddLine(Program pgm, Vector lastpt, Line line) + { + if (line.StartPoint != lastpt) + pgm.MoveTo(line.StartPoint); + + pgm.LineTo(line.EndPoint); + + lastpt = line.EndPoint; + return lastpt; + } + } +} diff --git a/Source/OpenNest.Core/ConvertMode.cs b/Source/OpenNest.Core/ConvertMode.cs new file mode 100644 index 0000000..8e98859 --- /dev/null +++ b/Source/OpenNest.Core/ConvertMode.cs @@ -0,0 +1,52 @@ +using OpenNest.CNC; + +namespace OpenNest +{ + public static class ConvertMode + { + /// + /// Converts the program to absolute coordinates. + /// Does NOT check program mode before converting. + /// + /// + public static void ToAbsolute(Program pgm) + { + var pos = new Vector(0, 0); + + for (int i = 0; i < pgm.Codes.Count; ++i) + { + var code = pgm.Codes[i]; + var motion = code as Motion; + + if (motion != null) + { + motion.Offset(pos); + pos = motion.EndPoint; + } + } + } + + /// + /// Converts the program to intermental coordinates. + /// Does NOT check program mode before converting. + /// + /// + public static void ToIncremental(Program pgm) + { + var pos = new Vector(0, 0); + + for (int i = 0; i < pgm.Codes.Count; ++i) + { + var code = pgm.Codes[i]; + var motion = code as Motion; + + if (motion != null) + { + var pos2 = motion.EndPoint; + motion.Offset(-pos.X, -pos.Y); + pos = pos2; + } + } + } + } +} diff --git a/Source/OpenNest.Core/ConvertProgram.cs b/Source/OpenNest.Core/ConvertProgram.cs new file mode 100644 index 0000000..8be5f57 --- /dev/null +++ b/Source/OpenNest.Core/ConvertProgram.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class ConvertProgram + { + public static List ToGeometry(Program pgm) + { + var geometry = new List(); + var curpos = new Vector(); + var mode = Mode.Absolute; + + AddProgram(pgm, ref mode, ref curpos, ref geometry); + + return geometry; + } + + private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List geometry) + { + mode = program.Mode; + + for (int i = 0; i < program.Length; ++i) + { + var code = program[i]; + + switch (code.Type) + { + case CodeType.CircularMove: + AddCircularMove((CircularMove)code, ref mode, ref curpos, ref geometry); + break; + + case CodeType.LinearMove: + AddLinearMove((LinearMove)code, ref mode, ref curpos, ref geometry); + break; + + case CodeType.RapidMove: + AddRapidMove((RapidMove)code, ref mode, ref curpos, ref geometry); + break; + + case CodeType.SubProgramCall: + var tmpmode = mode; + var subpgm = (SubProgramCall)code; + var geoProgram = new Shape(); + AddProgram(subpgm.Program, ref mode, ref curpos, ref geoProgram.Entities); + geometry.Add(geoProgram); + mode = tmpmode; + break; + } + } + } + + private static void AddLinearMove(LinearMove linearMove, ref Mode mode, ref Vector curpos, ref List geometry) + { + var pt = linearMove.EndPoint; + + if (mode == Mode.Incremental) + pt += curpos; + + var line = new Line(curpos, pt) + { + Layer = ConvertLayer(linearMove.Layer) + }; + geometry.Add(line); + curpos = pt; + } + + private static void AddRapidMove(RapidMove rapidMove, ref Mode mode, ref Vector curpos, ref List geometry) + { + var pt = rapidMove.EndPoint; + + if (mode == Mode.Incremental) + pt += curpos; + + var line = new Line(curpos, pt) + { + Layer = SpecialLayers.Rapid + }; + geometry.Add(line); + curpos = pt; + } + + private static void AddCircularMove(CircularMove circularMove, ref Mode mode, ref Vector curpos, ref List geometry) + { + var center = circularMove.CenterPoint; + var endpt = circularMove.EndPoint; + + if (mode == Mode.Incremental) + { + endpt += curpos; + center += curpos; + } + + var startAngle = center.AngleTo(curpos); + var endAngle = center.AngleTo(endpt); + + var dx = endpt.X - center.X; + var dy = endpt.Y - center.Y; + + var radius = Math.Sqrt(dx * dx + dy * dy); + var layer = ConvertLayer(circularMove.Layer); + + if (startAngle.IsEqualTo(endAngle)) + geometry.Add(new Circle(center, radius) { Layer = layer }); + else + geometry.Add(new Arc(center, radius, startAngle, endAngle, circularMove.Rotation == RotationType.CW) { Layer = layer }); + + curpos = endpt; + } + + private static Layer ConvertLayer(LayerType layer) + { + switch (layer) + { + case LayerType.Cut: + return SpecialLayers.Cut; + + case LayerType.Display: + return SpecialLayers.Display; + + case LayerType.Leadin: + return SpecialLayers.Leadin; + + case LayerType.Leadout: + return SpecialLayers.Leadout; + + case LayerType.Scribe: + return SpecialLayers.Scribe; + + default: + return new Layer(layer.ToString()); + } + } + } +} diff --git a/Source/OpenNest.Core/CutParameters.cs b/Source/OpenNest.Core/CutParameters.cs new file mode 100644 index 0000000..0f39b78 --- /dev/null +++ b/Source/OpenNest.Core/CutParameters.cs @@ -0,0 +1,15 @@ +using System; + +namespace OpenNest +{ + public class CutParameters + { + public double Feedrate { get; set; } + + public double RapidTravelRate { get; set; } + + public TimeSpan PierceTime { get; set; } + + public Units Units { get; set; } + } +} diff --git a/Source/OpenNest.Core/Drawing.cs b/Source/OpenNest.Core/Drawing.cs new file mode 100644 index 0000000..db6841a --- /dev/null +++ b/Source/OpenNest.Core/Drawing.cs @@ -0,0 +1,117 @@ +using System.Drawing; +using System.Linq; +using OpenNest.CNC; + +namespace OpenNest +{ + public class Drawing + { + private Program program; + + public Drawing() + : this(string.Empty, new Program()) + { + } + + public Drawing(string name) + : this(name, new Program()) + { + } + + public Drawing(string name, Program pgm) + { + Name = name; + Material = new Material(); + Program = pgm; + Constraints = new NestConstraints(); + Source = new SourceInfo(); + } + + public string Name { get; set; } + + public string Customer { get; set; } + + public int Priority { get; set; } + + public Quantity Quantity; + + public Material Material { get; set; } + + public Program Program + { + get { return program; } + set + { + program = value; + UpdateArea(); + } + } + + public Color Color { get; set; } + + public NestConstraints Constraints { get; set; } + + public SourceInfo Source { get; set; } + + public double Area { get; protected set; } + + public void UpdateArea() + { + var geometry = ConvertProgram.ToGeometry(Program).Where(entity => entity.Layer != SpecialLayers.Rapid); + var shapes = Helper.GetShapes(geometry); + + if (shapes.Count == 0) + return; + + var areas = new double[shapes.Count]; + + for (int i = 0; i < shapes.Count; i++) + { + var shape = shapes[i]; + areas[i] = shape.Area(); + } + + int largestAreaIndex = 0; + + for (int i = 1; i < areas.Length; i++) + { + var area = areas[i]; + + if (area > areas[largestAreaIndex]) + largestAreaIndex = i; + } + + var outerArea = areas[largestAreaIndex]; + + Area = outerArea - (areas.Sum() - outerArea); + } + + public override bool Equals(object obj) + { + if (obj is Drawing == false) + return false; + + var dwg = (Drawing)obj; + + return Name == dwg.Name; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + } + + public class SourceInfo + { + /// + /// Path to the source file. + /// + public string Path { get; set; } + + /// + /// Offset distances to the original location. + /// + public Vector Offset { get; set; } + } +} diff --git a/Source/OpenNest.Core/EvenOdd.cs b/Source/OpenNest.Core/EvenOdd.cs new file mode 100644 index 0000000..247c043 --- /dev/null +++ b/Source/OpenNest.Core/EvenOdd.cs @@ -0,0 +1,15 @@ +namespace OpenNest +{ + public static class EvenOdd + { + public static bool IsEven(this int i) + { + return (i % 2) == 0; + } + + public static bool IsOdd(this int i) + { + return (i % 2) == 1; + } + } +} diff --git a/Source/OpenNest.Core/Generic.cs b/Source/OpenNest.Core/Generic.cs new file mode 100644 index 0000000..86f0301 --- /dev/null +++ b/Source/OpenNest.Core/Generic.cs @@ -0,0 +1,12 @@ +namespace OpenNest +{ + public static class Generic + { + public static void Swap(ref T a, ref T b) + { + T c = a; + a = b; + b = c; + } + } +} diff --git a/Source/OpenNest.Core/Geometry/Arc.cs b/Source/OpenNest.Core/Geometry/Arc.cs new file mode 100644 index 0000000..31b0cb2 --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Arc.cs @@ -0,0 +1,538 @@ +using System; +using System.Collections.Generic; + +namespace OpenNest.Geometry +{ + public class Arc : Entity + { + private double radius; + private double startAngle; + private double endAngle; + private Vector center; + private bool reversed; + + public Arc() + { + } + + public Arc(double x, double y, double r, double a1, double a2, bool reversed = false) + : this(new Vector(x, y), r, a1, a2, reversed) + { + } + + public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false) + { + this.center = center; + this.radius = radius; + this.startAngle = startAngle; + this.endAngle = endAngle; + this.reversed = reversed; + UpdateBounds(); + } + + /// + /// Center point. + /// + public Vector Center + { + get { return center; } + set + { + var offset = value - center; + boundingBox.Offset(offset); + center = value; + } + } + + /// + /// Arc radius. + /// + public double Radius + { + get { return radius; } + set + { + radius = value; + UpdateBounds(); + } + } + + /// + /// Arc radius * 2. Value NOT stored. + /// + public double Diameter + { + get { return Radius * 2.0; } + set { Radius = value / 2.0; } + } + + /// + /// Start angle in radians. + /// + public double StartAngle + { + get { return startAngle; } + set + { + startAngle = Angle.NormalizeRad(value); + UpdateBounds(); + } + } + + /// + /// End angle in radians. + /// + public double EndAngle + { + get { return endAngle; } + set + { + endAngle = Angle.NormalizeRad(value); + UpdateBounds(); + } + } + + /// + /// Angle in radians between start and end angles. + /// + /// + public double SweepAngle() + { + var startAngle = StartAngle; + var endAngle = EndAngle; + + if (IsReversed) + Generic.Swap(ref startAngle, ref endAngle); + + if (startAngle > endAngle) + startAngle -= Angle.TwoPI; + + return endAngle - startAngle; + } + + /// + /// Gets or sets if the arc direction is reversed (clockwise). + /// + public bool IsReversed + { + get { return reversed; } + set + { + if (reversed != value) + Reverse(); + } + } + + public RotationType Rotation + { + get { return IsReversed ? RotationType.CW : RotationType.CCW; } + set + { + IsReversed = (value == RotationType.CW); + } + } + + /// + /// Start point of the arc. + /// + /// + public Vector StartPoint() + { + return new Vector( + Center.X + Radius * Math.Cos(StartAngle), + Center.Y + Radius * Math.Sin(StartAngle)); + } + + /// + /// End point of the arc. + /// + /// + public Vector EndPoint() + { + return new Vector( + Center.X + Radius * Math.Cos(EndAngle), + Center.Y + Radius * Math.Sin(EndAngle)); + } + + /// + /// Returns true if the given arc has the same center point and radius as this. + /// + /// + /// + public bool IsCoradialTo(Arc arc) + { + return center == arc.Center && Radius.IsEqualTo(arc.Radius); + } + + /// + /// Returns true if the given arc has the same radius as this. + /// + /// + /// + public bool IsConcentricTo(Arc arc) + { + return center == arc.center; + } + + /// + /// Returns true if the given circle has the same radius as this. + /// + /// + /// + public bool IsConcentricTo(Circle circle) + { + return center == circle.Center; + } + + /// + /// Converts the arc to a group of points. + /// + /// Number of parts to divide the arc into. + /// + public List ToPoints(int segments = 1000) + { + var points = new List(); + var stepAngle = reversed + ? -SweepAngle() / segments + : SweepAngle() / segments; + + for (int i = 0; i <= segments; ++i) + { + var angle = stepAngle * i + StartAngle; + + points.Add(new Vector( + Math.Cos(angle) * Radius + Center.X, + Math.Sin(angle) * Radius + Center.Y)); + } + + return points; + } + + /// + /// Linear distance of the arc. + /// + public override double Length + { + get { return Diameter * Math.PI * SweepAngle() / Angle.TwoPI; } + } + + /// + /// Reverses the rotation direction. + /// + public override void Reverse() + { + reversed = !reversed; + Generic.Swap(ref startAngle, ref endAngle); + } + + /// + /// Moves the center point to the given coordinates. + /// + /// The x-coordinate + /// The y-coordinate + public override void MoveTo(double x, double y) + { + Center = new Vector(x, y); + } + + /// + /// Moves the center point to the given point. + /// + /// The new center point location. + public override void MoveTo(Vector pt) + { + Center = pt; + } + + /// + /// Offsets the center point by the given distances. + /// + /// The x-axis offset distance. + /// The y-axis offset distance. + public override void Offset(double x, double y) + { + Center = new Vector(Center.X + x, Center.Y + y); + } + + /// + /// Offsets the center point by the given distances. + /// + /// + public override void Offset(Vector voffset) + { + Center += voffset; + } + + /// + /// Scales the arc from the zero point. + /// + /// + public override void Scale(double factor) + { + center *= factor; + radius *= factor; + UpdateBounds(); + } + + /// + /// Scales the arc from the origin. + /// + /// + /// + public override void Scale(double factor, Vector origin) + { + center = center.Scale(factor, origin); + radius *= factor; + UpdateBounds(); + } + + /// + /// Rotates the arc from the zero point. + /// + /// + public override void Rotate(double angle) + { + startAngle += angle; + endAngle += angle; + center = center.Rotate(angle); + UpdateBounds(); + } + + /// + /// Rotates the arc from the origin. + /// + /// + /// + public override void Rotate(double angle, Vector origin) + { + startAngle += angle; + endAngle += angle; + center = center.Rotate(angle, origin); + UpdateBounds(); + } + + /// + /// Updates the bounding box. + /// + public override void UpdateBounds() + { + var startpt = StartPoint(); + var endpt = EndPoint(); + + double minX; + double minY; + double maxX; + double maxY; + + if (startpt.X < endpt.X) + { + minX = startpt.X; + maxX = endpt.X; + } + else + { + minX = endpt.X; + maxX = startpt.X; + } + + if (startpt.Y < endpt.Y) + { + minY = startpt.Y; + maxY = endpt.Y; + } + else + { + minY = endpt.Y; + maxY = startpt.Y; + } + + var angle1 = StartAngle; + var angle2 = EndAngle; + + // switch the angle to counter clockwise. + if (IsReversed) + Generic.Swap(ref angle1, ref angle2); + + if (Angle.IsBetweenRad(Angle.HalfPI, angle1, angle2)) + maxY = Center.Y + Radius; + + if (Angle.IsBetweenRad(Math.PI, angle1, angle2)) + minX = Center.X - Radius; + + const double oneHalfPI = Math.PI * 1.5; + + if (Angle.IsBetweenRad(oneHalfPI, angle1, angle2)) + minY = Center.Y - Radius; + + if (Angle.IsBetweenRad(Angle.TwoPI, angle1, angle2)) + maxX = Center.X + Radius; + + boundingBox.X = minX; + boundingBox.Y = minY; + boundingBox.Width = maxX - minX; + boundingBox.Height = maxY - minY; + } + + public override Entity OffsetEntity(double distance, OffsetSide side) + { + if (side == OffsetSide.Left && reversed) + { + return new Arc(center, radius + distance, startAngle, endAngle, reversed); + } + else + { + if (distance >= radius) + return null; + + return new Arc(center, radius - distance, startAngle, endAngle, reversed); + } + } + + public override Entity OffsetEntity(double distance, Vector pt) + { + throw new NotImplementedException(); + } + + /// + /// Gets the closest point on the arc to the given point. + /// + /// + /// + public override Vector ClosestPointTo(Vector pt) + { + var angle = Center.AngleTo(pt); + + if (Angle.IsBetweenRad(angle, StartAngle, EndAngle, IsReversed)) + { + return new Vector( + Math.Cos(angle) * Radius + Center.X, + Math.Sin(angle) * Radius + Center.Y); + } + else + { + var sp = StartPoint(); + var ep = EndPoint(); + + return pt.DistanceTo(sp) <= pt.DistanceTo(ep) ? sp : ep; + } + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public override bool Intersects(Arc arc) + { + List pts; + return Helper.Intersects(this, arc, out pts); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Arc arc, out List pts) + { + return Helper.Intersects(this, arc, out pts); ; + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public override bool Intersects(Circle circle) + { + List pts; + return Helper.Intersects(this, circle, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Circle circle, out List pts) + { + return Helper.Intersects(this, circle, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public override bool Intersects(Line line) + { + List pts; + return Helper.Intersects(this, line, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Line line, out List pts) + { + return Helper.Intersects(this, line, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public override bool Intersects(Polygon polygon) + { + List pts; + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Polygon polygon, out List pts) + { + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public override bool Intersects(Shape shape) + { + List pts; + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Shape shape, out List pts) + { + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Type of entity. + /// + public override EntityType Type + { + get { return EntityType.Arc; } + } + } +} diff --git a/Source/OpenNest.Core/Geometry/Circle.cs b/Source/OpenNest.Core/Geometry/Circle.cs new file mode 100644 index 0000000..1ee74de --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Circle.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Generic; + +namespace OpenNest.Geometry +{ + public class Circle : Entity + { + private Vector center; + private double radius; + + public Circle() + { + } + + public Circle(double x, double y, double radius) + : this(new Vector(x, y), radius) + { + } + + public Circle(Vector center, double radius) + { + this.center = center; + this.radius = radius; + this.Rotation = RotationType.CCW; + UpdateBounds(); + } + + /// + /// Creates a circle from two points. + /// + /// + /// + /// + public static Circle CreateFrom2Points(Vector pt1, Vector pt2) + { + var line = new Line(pt1, pt2); + return new Circle(line.MidPoint, line.Length * 0.5); + } + + /// + /// Center point of the circle. + /// + public Vector Center + { + get { return center; } + set + { + var offset = value - center; + boundingBox.Offset(offset); + center = value; + } + } + + /// + /// Radius of the circle. + /// + public double Radius + { + get { return radius; } + set + { + radius = value; + UpdateBounds(); + } + } + + /// + /// Radius * 2. Value NOT stored. + /// + public double Diameter + { + get { return Radius * 2.0; } + set { Radius = value / 2.0; } + } + + /// + /// Rotation direction. + /// + public RotationType Rotation { get; set; } + + /// + /// Area of the circle. + /// + /// + public double Area() + { + return Math.PI * Radius * Radius; + } + + /// + /// Linear distance around the circle. + /// + /// + public double Circumference() + { + return Math.PI * Diameter; + } + + /// + /// Returns true if the given circle has the same radius as this. + /// + /// + /// + public bool IsConcentricTo(Circle circle) + { + return center == circle.center; + } + + /// + /// Returns true if the given circle has the same radius as this. + /// + /// + /// + public bool IsConcentricTo(Arc arc) + { + return center == arc.Center; + } + + public bool ContainsPoint(Vector pt) + { + return Center.DistanceTo(pt) <= Radius; + } + + public List ToPoints(int segments = 1000) + { + var points = new List(); + var stepAngle = Angle.TwoPI / segments; + + for (int i = 0; i <= segments; ++i) + { + var angle = stepAngle * i; + + points.Add(new Vector( + Math.Cos(angle) * Radius + Center.X, + Math.Sin(angle) * Radius + Center.Y)); + } + + return points; + } + + /// + /// Linear distance around the circle. + /// + public override double Length + { + get { return Circumference(); } + } + + /// + /// Reverses the rotation direction. + /// + public override void Reverse() + { + if (Rotation == RotationType.CCW) + Rotation = RotationType.CW; + else + Rotation = RotationType.CCW; + } + + /// + /// Moves the center point to the given coordinates. + /// + /// + /// + public override void MoveTo(double x, double y) + { + Center = new Vector(x, y); + } + + /// + /// Moves the center point to the given point. + /// + /// + public override void MoveTo(Vector pt) + { + Center = pt; + } + + /// + /// Offsets the center point by the given distances. + /// + /// + /// + public override void Offset(double x, double y) + { + Center = new Vector(Center.X + x, Center.Y + y); + } + + /// + /// Offsets the center point by the given distances. + /// + /// + public override void Offset(Vector voffset) + { + Center += voffset; + } + + /// + /// Scales the circle from the zero point. + /// + /// + public override void Scale(double factor) + { + center *= factor; + radius *= factor; + UpdateBounds(); + } + + /// + /// Scales the circle from the origin. + /// + /// + /// + public override void Scale(double factor, Vector origin) + { + center = center.Scale(factor, origin); + radius *= factor; + UpdateBounds(); + } + + /// + /// Rotates the circle from the zero point. + /// + /// + public override void Rotate(double angle) + { + Center = Center.Rotate(angle); + } + + /// + /// /// Rotates the circle from the origin. + /// + /// + /// + public override void Rotate(double angle, Vector origin) + { + Center = Center.Rotate(angle, origin); + } + + /// + /// Updates the bounding box. + /// + public override void UpdateBounds() + { + boundingBox.X = Center.X - Radius; + boundingBox.Y = Center.Y - Radius; + boundingBox.Width = Diameter; + boundingBox.Height = Diameter; + } + + public override Entity OffsetEntity(double distance, OffsetSide side) + { + if (side == OffsetSide.Left && Rotation == RotationType.CCW) + { + return Radius <= distance ? null : new Circle(center, Radius - distance) + { + Layer = Layer, + Rotation = Rotation + }; + } + else + { + return new Circle(center, Radius + distance) { Layer = Layer }; + } + } + + public override Entity OffsetEntity(double distance, Vector pt) + { + if (ContainsPoint(pt)) + { + return Radius <= distance ? null : new Circle(center, Radius - distance) + { + Layer = Layer, + Rotation = Rotation + }; + } + else + { + return new Circle(center, Radius + distance) { Layer = Layer }; + } + } + + /// + /// Gets the closest point on the circle to the specified point. + /// + /// + /// + public override Vector ClosestPointTo(Vector pt) + { + var angle = Center.AngleTo(pt); + + return new Vector( + Math.Cos(angle) * Radius + Center.X, + Math.Sin(angle) * Radius + Center.Y); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public override bool Intersects(Arc arc) + { + List pts; + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Arc arc, out List pts) + { + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public override bool Intersects(Circle circle) + { + var dist = Center.DistanceTo(circle.Center); + return (dist < (Radius + circle.Radius) && dist > Math.Abs(Radius - circle.Radius)); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Circle circle, out List pts) + { + return Helper.Intersects(this, circle, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public override bool Intersects(Line line) + { + List pts; + return Helper.Intersects(this, line, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Line line, out List pts) + { + return Helper.Intersects(this, line, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public override bool Intersects(Polygon polygon) + { + List pts; + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Polygon polygon, out List pts) + { + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public override bool Intersects(Shape shape) + { + List pts; + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// Points of intersection. + /// + public override bool Intersects(Shape shape, out List pts) + { + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Type of entity. + /// + public override EntityType Type + { + get { return EntityType.Circle; } + } + } +} diff --git a/Source/OpenNest.Core/Geometry/DefinedShape.cs b/Source/OpenNest.Core/Geometry/DefinedShape.cs new file mode 100644 index 0000000..369f651 --- /dev/null +++ b/Source/OpenNest.Core/Geometry/DefinedShape.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace OpenNest.Geometry +{ + public class DefinedShape + { + public DefinedShape(Shape shape) + { + Update(shape.Entities); + } + + public DefinedShape(List entities) + { + Update(entities); + } + + private void Update(List entities) + { + var shapes = Helper.GetShapes(entities); + + Perimeter = shapes[0]; + Cutouts = new List(); + + for (int i = 1; i < shapes.Count; i++) + { + if (shapes[i].Left < Perimeter.Left) + { + Cutouts.Add(Perimeter); + Perimeter = shapes[i]; + } + else + { + Cutouts.Add(shapes[i]); + } + } + } + + public Shape Perimeter { get; set; } + + public List Cutouts { get; set; } + } +} diff --git a/Source/OpenNest.Core/Geometry/Entity.cs b/Source/OpenNest.Core/Geometry/Entity.cs new file mode 100644 index 0000000..20aef49 --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Entity.cs @@ -0,0 +1,268 @@ +using System.Collections.Generic; + +namespace OpenNest.Geometry +{ + public abstract class Entity : IBoundable + { + protected Box boundingBox; + + protected Entity() + { + Layer = OpenNest.Geometry.Layer.Default; + boundingBox = new Box(); + } + + /// + /// Smallest box that contains the entity. + /// + public Box BoundingBox + { + get { return boundingBox; } + } + + /// + /// Entity layer type. + /// + public Layer Layer { get; set; } + + /// + /// X-Coordinate of the left-most point. + /// + public virtual double Left + { + get { return boundingBox.Left; } + } + + /// + /// X-Coordinate of the right-most point. + /// + public virtual double Right + { + get { return boundingBox.Right; } + } + + /// + /// Y-Coordinate of the highest point. + /// + public virtual double Top + { + get { return boundingBox.Top; } + } + + /// + /// Y-Coordinate of the lowest point. + /// + public virtual double Bottom + { + get { return boundingBox.Bottom; } + } + + /// + /// Length of the entity. + /// + public abstract double Length { get; } + + /// + /// Reverses the entity. + /// + public abstract void Reverse(); + + /// + /// Moves the entity location to the given coordinates. + /// + /// + /// + public abstract void MoveTo(double x, double y); + + /// + /// Moves the entity location to the given point. + /// + /// + public abstract void MoveTo(Vector pt); + + /// + /// Offsets the entity location by the given distances. + /// + /// + /// + public abstract void Offset(double x, double y); + + /// + /// Offsets the entity location by the given distances. + /// + /// + public abstract void Offset(Vector voffset); + + /// + /// Scales the entity from the zero point. + /// + /// + public abstract void Scale(double factor); + + /// + /// Scales the entity from the origin. + /// + /// + /// + public abstract void Scale(double factor, Vector origin); + + /// + /// Rotates the entity from the zero point. + /// + /// + public abstract void Rotate(double angle); + + /// + /// Rotates the entity from the origin. + /// + /// + /// + public abstract void Rotate(double angle, Vector origin); + + /// + /// Updates the bounding box. + /// + public abstract void UpdateBounds(); + + /// + /// Gets a new entity offset the given distance from this entity. + /// + /// + /// + /// + public abstract Entity OffsetEntity(double distance, OffsetSide side); + + /// + /// Gets a new entity offset the given distance from this entity. Offset side determined by point. + /// + /// + /// + /// + public abstract Entity OffsetEntity(double distance, Vector pt); + + /// + /// Gets the closest point on the entity to the given point. + /// + /// + /// + public abstract Vector ClosestPointTo(Vector pt); + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public abstract bool Intersects(Arc arc); + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// List to store the points of intersection. + /// + public abstract bool Intersects(Arc arc, out List pts); + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public abstract bool Intersects(Circle circle); + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + /// + public abstract bool Intersects(Circle circle, out List pts); + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public abstract bool Intersects(Line line); + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + /// + public abstract bool Intersects(Line line, out List pts); + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public abstract bool Intersects(Polygon polygon); + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + /// + public abstract bool Intersects(Polygon polygon, out List pts); + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public abstract bool Intersects(Shape shape); + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + /// + public abstract bool Intersects(Shape shape, out List pts); + + /// + /// Type of entity. + /// + public abstract EntityType Type { get; } + } + + public static class EntityExtensions + { + public static double FindBestRotation(this List entities, double stepAngle, double startAngle = 0, double endAngle = Angle.TwoPI) + { + startAngle = Angle.NormalizeRad(startAngle); + + if (!endAngle.IsEqualTo(Angle.TwoPI)) + endAngle = Angle.NormalizeRad(endAngle); + + if (stepAngle.IsEqualTo(0.0)) + return startAngle; + + entities.ForEach(e => e.Rotate(startAngle)); + + var bestAngle = startAngle; + var bestArea = entities.GetBoundingBox().Area(); + + var steps = startAngle < endAngle + ? (endAngle - startAngle) / stepAngle + : (endAngle + Angle.TwoPI) - startAngle / stepAngle; + + for (int i = 1; i <= steps; ++i) + { + entities.ForEach(e => e.Rotate(stepAngle)); + + var area = entities.GetBoundingBox().Area(); + + if (area < bestArea) + { + bestArea = area; + bestAngle = startAngle + stepAngle * i; + } + } + + return bestAngle; + } + } +} diff --git a/Source/OpenNest.Core/Geometry/EntityType.cs b/Source/OpenNest.Core/Geometry/EntityType.cs new file mode 100644 index 0000000..f0cb14e --- /dev/null +++ b/Source/OpenNest.Core/Geometry/EntityType.cs @@ -0,0 +1,12 @@ + +namespace OpenNest.Geometry +{ + public enum EntityType + { + Arc, + Circle, + Line, + Shape, + Polygon + } +} diff --git a/Source/OpenNest.Core/Geometry/Layer.cs b/Source/OpenNest.Core/Geometry/Layer.cs new file mode 100644 index 0000000..7147a4a --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Layer.cs @@ -0,0 +1,29 @@ +using System.Drawing; + +namespace OpenNest.Geometry +{ + public class Layer + { + public static readonly Layer Default = new Layer("0") + { + Color = Color.White, + IsVisible = true + }; + + public Layer(string name) + { + Name = name; + } + + public string Name { get; set; } + + public bool IsVisible { get; set; } + + public Color Color { get; set; } + + public override string ToString() + { + return Name; + } + } +} diff --git a/Source/OpenNest.Core/Geometry/Line.cs b/Source/OpenNest.Core/Geometry/Line.cs new file mode 100644 index 0000000..e411cea --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Line.cs @@ -0,0 +1,552 @@ +using System; +using System.Collections.Generic; + +namespace OpenNest.Geometry +{ + public class Line : Entity + { + private Vector pt1; + private Vector pt2; + + public Line() + { + } + + public Line(double x1, double y1, double x2, double y2) + : this(new Vector(x1, y1), new Vector(x2, y2)) + { + } + + public Line(Vector startPoint, Vector endPoint) + { + pt1 = startPoint; + pt2 = endPoint; + UpdateBounds(); + } + + /// + /// Start point of the line. + /// + public Vector StartPoint + { + get { return pt1; } + set + { + pt1 = value; + UpdateBounds(); + } + } + + /// + /// Mid-point of the line. + /// + public Vector MidPoint + { + get + { + var x = (pt1.X + pt2.X) * 0.5; + var y = (pt1.Y + pt2.Y) * 0.5; + return new Vector(x, y); + } + } + + /// + /// End point of the line. + /// + public Vector EndPoint + { + get { return pt2; } + set + { + pt2 = value; + UpdateBounds(); + } + } + + /// + /// Gets the point on the line that is perpendicular from the given point. + /// + /// + /// + public Vector PointPerpendicularFrom(Vector pt) + { + var diff1 = pt - StartPoint; + var diff2 = EndPoint - StartPoint; + var dotProduct = diff1.X * diff2.X + diff1.Y * diff2.Y; + var lengthSquared = diff2.X * diff2.X + diff2.Y * diff2.Y; + var param = dotProduct / lengthSquared; + + if (param < 0) + return StartPoint; + else if (param > 1) + return EndPoint; + else + { + return new Vector( + StartPoint.X + param * diff2.X, + StartPoint.Y + param * diff2.Y); + } + } + + /// + /// Returns true if the given line is parallel to this. + /// + /// + /// + public bool IsParallelTo(Line line) + { + bool line1Vertical = IsVertical(); + bool line2Vertical = line.IsVertical(); + + if (line1Vertical) + return line2Vertical; + else if (line2Vertical) + return false; + + return Slope().IsEqualTo(line.Slope()); + } + + /// + /// Returns true if the given line is perpendicular to this. + /// + /// + /// + public bool IsPerpendicularTo(Line line) + { + bool line1Vertical = IsVertical(); + bool line2Vertical = line.IsVertical(); + + if (line1Vertical) + return line.IsHorizontal(); + else if (line.IsVertical()) + return IsHorizontal(); + + return Slope().IsEqualTo(-1 / line.Slope()); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// Point of intersection. + /// + public bool Intersects(Line line, out Vector pt) + { + var a1 = EndPoint.Y - StartPoint.Y; + var b1 = StartPoint.X - EndPoint.X; + var c1 = a1 * StartPoint.X + b1 * StartPoint.Y; + + var a2 = line.EndPoint.Y - line.StartPoint.Y; + var b2 = line.StartPoint.X - line.EndPoint.X; + var c2 = a2 * line.StartPoint.X + b2 * line.StartPoint.Y; + + var d = a1 * b2 - a2 * b1; + + if (d.IsEqualTo(0.0)) + { + pt = new Vector(); + return false; + } + else + { + var x = (b2 * c1 - b1 * c2) / d; + var y = (a1 * c2 - a2 * c1) / d; + + pt = new Vector(x, y); + return boundingBox.Contains(pt) && line.boundingBox.Contains(pt); + } + } + + /// + /// Returns true if this is vertical. + /// + /// + public bool IsVertical() + { + return pt1.X.IsEqualTo(pt2.X); + } + + /// + /// Returns true if this is horizontal. + /// + /// + public bool IsHorizontal() + { + return pt1.Y.IsEqualTo(pt2.Y); + } + + /// + /// Returns true if the given line is collinear to this. + /// + /// + /// + public bool IsCollinearTo(Line line) + { + if (IsVertical()) + { + if (!line.IsVertical()) + return false; + + return StartPoint.X.IsEqualTo(line.StartPoint.X); + } + else if (line.IsVertical()) + return false; + + if (!YIntercept().IsEqualTo(line.YIntercept())) + return false; + + if (!Slope().IsEqualTo(line.Slope())) + return false; + + return true; + } + + /// + /// Angle of the line from start point to end point. + /// + /// + public double Angle() + { + return StartPoint.AngleTo(EndPoint); + } + + /// + /// Returns the angle between the two lines in radians. + /// + /// + /// + public double AngleBetween(Line line) + { + var m1 = Slope(); + var m2 = line.Slope(); + return Math.Atan(Math.Abs((m2 - m1) / (1 + m2 * m1))); + } + + /// + /// Slope of the line. + /// + /// + public double Slope() + { + if (IsVertical()) + throw new DivideByZeroException(); + + return (EndPoint.Y - StartPoint.Y) / (EndPoint.X - StartPoint.X); + } + + /// + /// Gets the y-axis intersection coordinate. + /// + /// + public double YIntercept() + { + return StartPoint.Y - Slope() * StartPoint.X; + } + + /// + /// Length of the line from start point to end point. + /// + public override double Length + { + get + { + var x = EndPoint.X - StartPoint.X; + var y = EndPoint.Y - StartPoint.Y; + return Math.Sqrt(x * x + y * y); + } + } + + /// + /// Reversed the line. + /// + public override void Reverse() + { + Generic.Swap(ref pt1, ref pt2); + } + + /// + /// Moves the start point to the given coordinates. + /// + /// + /// + public override void MoveTo(double x, double y) + { + var xoffset = pt1.X - x; + var yoffset = pt1.Y - y; + + pt2.X += xoffset; + pt2.Y += yoffset; + pt1.X = x; + pt1.Y = y; + boundingBox.Offset(xoffset, yoffset); + } + + /// + /// Moves the start point to the given point. + /// + /// + public override void MoveTo(Vector pt) + { + var offset = pt1 - pt; + + pt2 += offset; + pt1 = pt; + boundingBox.Offset(offset); + } + + /// + /// Offsets the line location by the given distances. + /// + /// + /// + public override void Offset(double x, double y) + { + pt2.X += x; + pt2.Y += y; + pt1.X += x; + pt1.Y += y; + boundingBox.Offset(x, y); + } + + /// + /// Offsets the line location by the given distances. + /// + /// + public override void Offset(Vector voffset) + { + pt1 += voffset; + pt2 += voffset; + boundingBox.Offset(voffset); + } + + /// + /// Scales the line from the zero point. + /// + /// + public override void Scale(double factor) + { + pt1 *= factor; + pt2 *= factor; + } + + /// + /// Scales the line from the origin. + /// + /// + /// + public override void Scale(double factor, Vector origin) + { + pt1 = (pt1 - origin) * factor + origin; + pt2 = (pt2 - origin) * factor + origin; + } + + /// + /// Rotates the line from the zero point. + /// + /// + public override void Rotate(double angle) + { + StartPoint = StartPoint.Rotate(angle); + EndPoint = EndPoint.Rotate(angle); + } + + /// + /// Rotates the line from the origin. + /// + /// + /// + public override void Rotate(double angle, Vector origin) + { + StartPoint = StartPoint.Rotate(angle, origin); + EndPoint = EndPoint.Rotate(angle, origin); + } + + /// + /// Updates the bounding box. + /// + public override sealed void UpdateBounds() + { + if (StartPoint.X < EndPoint.X) + { + boundingBox.X = StartPoint.X; + boundingBox.Width = EndPoint.X - StartPoint.X; + } + else + { + boundingBox.X = EndPoint.X; + boundingBox.Width = StartPoint.X - EndPoint.X; + } + + if (StartPoint.Y < EndPoint.Y) + { + boundingBox.Y = StartPoint.Y; + boundingBox.Height = EndPoint.Y - StartPoint.Y; + } + else + { + boundingBox.Y = EndPoint.Y; + boundingBox.Height = StartPoint.Y - EndPoint.Y; + } + } + + public override Entity OffsetEntity(double distance, OffsetSide side) + { + var angle = OpenNest.Angle.NormalizeRad(Angle() + OpenNest.Angle.HalfPI); + + var x = Math.Cos(angle) * distance; + var y = Math.Sin(angle) * distance; + + var pt = new Vector(x, y); + + return side == OffsetSide.Left + ? new Line(StartPoint + pt, EndPoint + pt) + : new Line(EndPoint + pt, StartPoint + pt); + } + + public override Entity OffsetEntity(double distance, Vector pt) + { + var a = pt - StartPoint; + var b = EndPoint - StartPoint; + var c = a.DotProduct(b); + var side = c < 0 ? OffsetSide.Left : OffsetSide.Right; + + return OffsetEntity(distance, side); + } + + /// + /// Gets the closest point on the line to the given point. + /// + /// + /// + public override Vector ClosestPointTo(Vector pt) + { + var perpendicularPt = PointPerpendicularFrom(pt); + + if (BoundingBox.Contains(perpendicularPt)) + return perpendicularPt; + else + return pt.DistanceTo(StartPoint) <= pt.DistanceTo(EndPoint) ? StartPoint : EndPoint; + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public override bool Intersects(Arc arc) + { + List pts; + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Arc arc, out List pts) + { + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public override bool Intersects(Circle circle) + { + List pts; + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Circle circle, out List pts) + { + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public override bool Intersects(Line line) + { + Vector pt; + return Intersects(line, out pt); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Line line, out List pts) + { + Vector pt; + var success = Helper.Intersects(this, line, out pt); + pts = new List(new[] { pt }); + return success; + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public override bool Intersects(Polygon polygon) + { + List pts; + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Polygon polygon, out List pts) + { + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public override bool Intersects(Shape shape) + { + List pts; + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Shape shape, out List pts) + { + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Type of entity. + /// + public override EntityType Type + { + get { return EntityType.Line; } + } + } +} diff --git a/Source/OpenNest.Core/Geometry/Polygon.cs b/Source/OpenNest.Core/Geometry/Polygon.cs new file mode 100644 index 0000000..6eec98f --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Polygon.cs @@ -0,0 +1,500 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.Geometry +{ + public class Polygon : Entity + { + public List Vertices; + + public Polygon() + { + Vertices = new List(); + } + + /// + /// Closes the polygon if it's not already. + /// + public void Close() + { + if (Vertices.Count < 3) + return; + + var first = Vertices.First(); + var last = Vertices.Last(); + + if (first != last) + Vertices.Add(first); + } + + /// + /// Returns true if the polygon is closed. + /// + /// + public bool IsClosed() + { + if (Vertices.Count < 3) + return false; + + return (Vertices.First() == Vertices.Last()); + } + + /// + /// Returns true if the polygon is self intersecting. + /// + /// + public bool IsComplex() + { + var lines = ToLines(); + + for (int i = 0; i < lines.Count; ++i) + { + var line1 = lines[i]; + + for (int j = i; j < lines.Count; ++j) + { + var line2 = lines[j]; + + if (line1.Intersects(line2)) + return true; + } + } + + return false; + } + + /// + /// Area of the polygon. + /// + /// Returns the area or 0 if the polygon is NOT closed. + public double Area() + { + if (Vertices.Count < 3) + return 0.0; + + return Math.Abs(CalculateArea()); + } + + /// + /// Distance around the polygon. + /// + /// + public double Perimeter() + { + if (Vertices.Count < 3) + return 0.0; + + double sum = 0.0; + + var last = Vertices[0]; + + for (int i = 1; i < Vertices.Count; ++i) + { + var current = Vertices[i]; + sum += last.DistanceTo(current); + last = current; + } + + return sum; + } + + /// + /// Gets the rotation direction of the polygon. + /// + /// + public RotationType RotationDirection() + { + if (Vertices.Count < 3) + throw new Exception("Not enough points to determine direction. Must have at least 3 points."); + + return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW; + } + + /// + /// Converts the polygon to a group of lines. + /// + /// + public List ToLines() + { + var list = new List(); + + if (Vertices.Count < 2) + return list; + + var last = Vertices[0]; + + for (int i = 1; i < Vertices.Count; ++i) + { + var current = Vertices[i]; + list.Add(new Line(last, current)); + last = current; + } + + return list; + } + + /// + /// Gets the area of the polygon. + /// + /// + /// Returns the area of the polygon. + /// * Positive number = counter-clockwise rotation + /// * Negative number = clockwise rotation + /// + private double CalculateArea() + { + double xsum = 0; + double ysum = 0; + + for (int i = 0; i < Vertices.Count - 1; ++i) + { + var current = Vertices[i]; + var next = Vertices[i + 1]; + + xsum += current.X * next.Y; + ysum += current.Y * next.X; + } + + return (xsum - ysum) * 0.5; + } + + /// + /// Distance around the polygon. + /// + public override double Length + { + get { return Perimeter(); } + } + + /// + /// Reverses the rotation direction of the polygon. + /// + public override void Reverse() + { + Vertices.Reverse(); + } + + /// + /// Moves the start point to the given coordinates. + /// + /// + /// + public override void MoveTo(double x, double y) + { + if (Vertices.Count == 0) + return; + + var first = Vertices[0]; + var offset = new Vector(x - first.X, y - first.Y); + + Vertices.ForEach(vertex => vertex += offset); + boundingBox.Offset(offset); + } + + /// + /// Moves the start point to the given point. + /// + /// + public override void MoveTo(Vector pt) + { + if (Vertices.Count == 0) + return; + + var first = Vertices[0]; + var offset = pt - first; + + Vertices.ForEach(vertex => vertex += offset); + boundingBox.Offset(offset); + } + + /// + /// Offsets the location by the given distances. + /// + /// + /// + public override void Offset(double x, double y) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] = Vertices[i].Offset(x, y); + + boundingBox.Offset(x, y); + } + + /// + /// Offsets the location by the given distances. + /// + /// + public override void Offset(Vector voffset) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] = Vertices[i].Offset(voffset); + + boundingBox.Offset(voffset); + } + + /// + /// Scales the polygon from the zero point. + /// + /// + public override void Scale(double factor) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] *= factor; + + UpdateBounds(); + } + + /// + /// Scales the polygon from the zero point. + /// + /// + /// + public override void Scale(double factor, Vector origin) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] = (Vertices[i] - origin) * factor + origin; + + UpdateBounds(); + } + + /// + /// Rotates the polygon from the zero point. + /// + /// + public override void Rotate(double angle) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] = Vertices[i].Rotate(angle); + + UpdateBounds(); + } + + /// + /// Rotates the polygon from the origin. + /// + /// + /// + public override void Rotate(double angle, Vector origin) + { + for (int i = 0; i < Vertices.Count; i++) + Vertices[i] = Vertices[i].Rotate(angle, origin); + + UpdateBounds(); + } + + /// + /// Updates the bounding box. + /// + public override void UpdateBounds() + { + if (Vertices.Count == 0) + return; + + var first = Vertices[0]; + var minX = first.X; + var maxX = first.X; + var minY = first.Y; + var maxY = first.Y; + + for (int i = 1; i < Vertices.Count; ++i) + { + var vertex = Vertices[i]; + + if (vertex.X < minX) minX = vertex.X; + else if (vertex.X > maxX) maxX = vertex.X; + + if (vertex.Y < minY) minY = vertex.Y; + else if (vertex.Y > maxY) maxY = vertex.Y; + } + + boundingBox.X = minX; + boundingBox.Y = minY; + boundingBox.Width = maxX - minX; + boundingBox.Height = maxY - minY; + } + + public override Entity OffsetEntity(double distance, OffsetSide side) + { + throw new NotImplementedException(); + } + + public override Entity OffsetEntity(double distance, Vector pt) + { + throw new NotImplementedException(); + } + + /// + /// Gets the closest point on the polygon to the given point. + /// + /// + /// + public override Vector ClosestPointTo(Vector pt) + { + var lines = ToLines(); + + if (lines.Count == 0) + return Vector.Invalid; + + Vector closestPt = lines[0].ClosestPointTo(pt); + double distance = closestPt.DistanceTo(pt); + + for (int i = 1; i < lines.Count; i++) + { + var line = lines[i]; + var closestPt2 = line.ClosestPointTo(pt); + var distance2 = closestPt2.DistanceTo(pt); + + if (distance2 < distance) + { + closestPt = closestPt2; + distance = distance2; + } + } + + return closestPt; + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public override bool Intersects(Arc arc) + { + List pts; + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Arc arc, out List pts) + { + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public override bool Intersects(Circle circle) + { + List pts; + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Circle circle, out List pts) + { + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public override bool Intersects(Line line) + { + List pts; + return Helper.Intersects(line, this, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Line line, out List pts) + { + return Helper.Intersects(line, this, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public override bool Intersects(Polygon polygon) + { + List pts; + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Polygon polygon, out List pts) + { + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public override bool Intersects(Shape shape) + { + List pts; + return Helper.Intersects(shape, this, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Shape shape, out List pts) + { + return Helper.Intersects(shape, this, out pts); + } + + /// + /// Type of entity. + /// + public override EntityType Type + { + get { return EntityType.Polygon; } + } + + internal void Cleanup() + { + for (int i = Vertices.Count - 1; i > 0; i--) + { + var vertex = Vertices[i]; + var nextVertex = Vertices[i - 1]; + + if (vertex == nextVertex) + Vertices.RemoveAt(i); + } + } + + public double FindBestRotation(double stepAngle) + { + var entities = new List(ToLines()); + return entities.FindBestRotation(stepAngle); + } + + public double FindBestRotation(double stepAngle, double startAngle, double endAngle) + { + var entities = new List(ToLines()); + return entities.FindBestRotation(stepAngle, startAngle, endAngle); + } + } +} diff --git a/Source/OpenNest.Core/Geometry/Shape.cs b/Source/OpenNest.Core/Geometry/Shape.cs new file mode 100644 index 0000000..f910a06 --- /dev/null +++ b/Source/OpenNest.Core/Geometry/Shape.cs @@ -0,0 +1,569 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace OpenNest.Geometry +{ + public class Shape : Entity + { + /// + /// Entities that make up the shape. + /// + public List Entities; + + public Shape() + { + Entities = new List(); + } + + /// + /// Returns true if the shape is closed. + /// + /// + public bool IsClosed() + { + if (Entities.Count == 0) + return false; + + var first = Entities[0]; + Vector firstStartPoint; + Vector firstEndPoint; + + switch (first.Type) + { + case EntityType.Arc: + var arc = (Arc)first; + firstStartPoint = arc.StartPoint(); + firstEndPoint = arc.EndPoint(); + break; + + case EntityType.Circle: + return Entities.Count == 1; + + case EntityType.Line: + var line = (Line)first; + firstStartPoint = line.StartPoint; + firstEndPoint = line.EndPoint; + break; + + default: + Debug.Fail("Unhandled geometry type"); + return false; + } + + var endpt = firstEndPoint; + + Entity geo = null; + + for (int i = 1; i < Entities.Count; ++i) + { + geo = Entities[i]; + + switch (geo.Type) + { + case EntityType.Arc: + var arc = (Arc)geo; + + if (arc.StartPoint() != endpt) + return false; + + endpt = arc.EndPoint(); + break; + + case EntityType.Circle: + return Entities.Count == 1; + + case EntityType.Line: + var line = (Line)geo; + + if (line.StartPoint != endpt) + return false; + + endpt = line.EndPoint; + break; + + default: + Debug.Fail("Unhandled geometry type"); + return false; + } + } + + if (geo == null) + return false; + + var last = geo; + Vector lastEndPoint; + + switch (last.Type) + { + case EntityType.Arc: + var arc = (Arc)last; + lastEndPoint = arc.EndPoint(); + break; + + case EntityType.Line: + var line = (Line)last; + lastEndPoint = line.EndPoint; + break; + + default: + Debug.Fail("Unhandled geometry type"); + return false; + } + + return lastEndPoint == firstStartPoint; + } + + /// + /// Gets the area. + /// + /// Returns the area or 0 if the shape is NOT closed. + public double Area() + { + // Check if the shape is closed so we can get the area. + if (!IsClosed()) + return 0; + + // If the shape is closed and only one entity in the geometry + // then that entity would have to be a circle. + if (Entities.Count == 1) + { + var circle = Entities[0] as Circle; + return circle == null ? 0 : circle.Area(); + } + + return ToPolygon().Area(); + } + + /// + /// Joins all overlapping lines and arcs. + /// + public void Optimize() + { + var lines = new List(); + var arcs = new List(); + + foreach (var geo in Entities) + { + switch (geo.Type) + { + case EntityType.Arc: + arcs.Add((Arc)geo); + break; + + case EntityType.Line: + lines.Add((Line)geo); + break; + } + } + + Helper.Optimize(lines); + Helper.Optimize(arcs); + } + + /// + /// Gets the closest point on the shape to the given point. + /// + /// + /// Entity that contains the point. + /// + public Vector ClosestPointTo(Vector pt, out Entity entity) + { + if (Entities.Count == 0) + { + entity = null; + return Vector.Invalid; + } + + var first = Entities[0]; + + Vector closestPt = first.ClosestPointTo(pt); + double distance = closestPt.DistanceTo(pt); + + entity = first; + + for (int i = 1; i < Entities.Count; i++) + { + var entity2 = Entities[i]; + var closestPt2 = entity2.ClosestPointTo(pt); + var distance2 = closestPt2.DistanceTo(pt); + + if (distance2 < distance) + { + closestPt = closestPt2; + distance = distance2; + entity = entity2; + } + } + + return closestPt; + } + + /// + /// Converts the shape to a polygon. + /// + /// + public Polygon ToPolygon(int arcSegments = 1000) + { + var polygon = new Polygon(); + + foreach (var entity in Entities) + { + switch (entity.Type) + { + case EntityType.Arc: + var arc = (Arc)entity; + polygon.Vertices.AddRange(arc.ToPoints(arcSegments)); + break; + + case EntityType.Line: + var line = (Line)entity; + polygon.Vertices.AddRange(new[] + { + line.StartPoint, + line.EndPoint + }); + break; + + case EntityType.Circle: + var circle = (Circle)entity; + polygon.Vertices.AddRange(circle.ToPoints(arcSegments)); + break; + + default: + Debug.Fail("Unhandled geometry type"); + break; + } + } + + polygon.Close(); + polygon.Cleanup(); + + return polygon; + } + + /// + /// Reverses the rotation direction of the shape. + /// + public override void Reverse() + { + Entities.ForEach(e => e.Reverse()); + Entities.Reverse(); + } + + /// + /// Linear distance of the shape. + /// + public override double Length + { + get { return Entities.Sum(geo => geo.Length); } + } + + /// + /// Moves the start point to the given coordinates. + /// + /// + /// + public override void MoveTo(double x, double y) + { + throw new NotImplementedException(); + } + + /// + /// Moves the start point to the given point. + /// + /// + public override void MoveTo(Vector pt) + { + throw new NotImplementedException(); + } + + /// + /// Offsets the shape location by the given distances. + /// + /// + /// + public override void Offset(double x, double y) + { + Entities.ForEach(e => e.Offset(x, y)); + boundingBox.Offset(x, y); + } + + /// + /// Offsets the shape location by the given distances. + /// + /// + public override void Offset(Vector voffset) + { + Entities.ForEach(e => e.Offset(voffset)); + boundingBox.Offset(voffset); + } + + /// + /// Scales the shape from the zero point. + /// + /// + public override void Scale(double factor) + { + Entities.ForEach(e => e.Scale(factor)); + UpdateBounds(); + } + + /// + /// Scales the shape from the origin. + /// + /// + /// + public override void Scale(double factor, Vector origin) + { + Entities.ForEach(e => e.Scale(factor, origin)); + UpdateBounds(); + } + + /// + /// Rotates the shape from the zero point. + /// + /// + public override void Rotate(double angle) + { + Entities.ForEach(e => e.Rotate(angle)); + UpdateBounds(); + } + + /// + /// Rotates the shape from the origin. + /// + /// + /// + public override void Rotate(double angle, Vector origin) + { + Entities.ForEach(e => e.Rotate(angle, origin)); + UpdateBounds(); + } + + /// + /// Updates the bounding box. + /// + public override void UpdateBounds() + { + boundingBox = Entities.Select(geo => geo.BoundingBox) + .ToList() + .GetBoundingBox(); + } + + public override Entity OffsetEntity(double distance, OffsetSide side) + { + var offsetShape = new Shape(); + var definedShape = new DefinedShape(this); + + Entity lastEntity = null; + Entity lastOffsetEntity = null; + + foreach (var entity in definedShape.Perimeter.Entities) + { + var offsetEntity = entity.OffsetEntity(distance, side); + + if (offsetEntity == null) + continue; + + switch (entity.Type) + { + case EntityType.Line: + { + var line = (Line)entity; + var offsetLine = (Line)offsetEntity; + + if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line) + { + var lastLine = lastEntity as Line; + var lastOffsetLine = lastOffsetEntity as Line; + + if (lastLine == null || lastOffsetLine == null) + continue; + + Vector intersection; + + if (Helper.Intersects(offsetLine, lastOffsetLine, out intersection)) + { + offsetLine.StartPoint = intersection; + lastOffsetLine.EndPoint = intersection; + } + else + { + var arc = new Arc( + line.StartPoint, + distance, + line.StartPoint.AngleTo(lastOffsetLine.EndPoint), + line.StartPoint.AngleTo(offsetLine.StartPoint), + side == OffsetSide.Left + ); + + offsetShape.Entities.Add(arc); + } + } + + offsetShape.Entities.Add(offsetLine); + break; + } + + default: + offsetShape.Entities.Add(offsetEntity); + break; + } + + lastOffsetEntity = offsetEntity; + lastEntity = entity; + } + + foreach (var cutout in definedShape.Cutouts) + offsetShape.Entities.AddRange(((Shape)cutout.OffsetEntity(distance, side)).Entities); + + return offsetShape; + } + + public override Entity OffsetEntity(double distance, Vector pt) + { + throw new NotImplementedException(); + } + + /// + /// Gets the closest point on the shape to the given point. + /// + /// + /// + public override Vector ClosestPointTo(Vector pt) + { + Entity entity; + return ClosestPointTo(pt, out entity); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + public override bool Intersects(Arc arc) + { + List pts; + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given arc is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Arc arc, out List pts) + { + return Helper.Intersects(arc, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + public override bool Intersects(Circle circle) + { + List pts; + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given circle is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Circle circle, out List pts) + { + return Helper.Intersects(circle, this, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + public override bool Intersects(Line line) + { + List pts; + return Helper.Intersects(line, this, out pts); + } + + /// + /// Returns true if the given line is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Line line, out List pts) + { + return Helper.Intersects(line, this, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + public override bool Intersects(Polygon polygon) + { + List pts; + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given polygon is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Polygon polygon, out List pts) + { + return Helper.Intersects(this, polygon, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + public override bool Intersects(Shape shape) + { + List pts; + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Returns true if the given shape is intersecting this. + /// + /// + /// + /// + public override bool Intersects(Shape shape, out List pts) + { + return Helper.Intersects(this, shape, out pts); + } + + /// + /// Type of entity. + /// + public override EntityType Type + { + get { return EntityType.Shape; } + } + + public double FindBestRotation(double stepAngle) + { + return Entities.FindBestRotation(stepAngle); + } + + public double FindBestRotation(double stepAngle, double startAngle, double endAngle) + { + return Entities.FindBestRotation(stepAngle, startAngle, endAngle); + } + } +} diff --git a/Source/OpenNest.Core/Helper.cs b/Source/OpenNest.Core/Helper.cs new file mode 100644 index 0000000..61b43d5 --- /dev/null +++ b/Source/OpenNest.Core/Helper.cs @@ -0,0 +1,990 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class Helper + { + /// + /// Rounds a number down to the nearest factor. + /// + /// + /// + /// + public static double RoundDownToNearest(double num, double factor) + { + return factor.IsEqualTo(0) ? num : Math.Floor(num / factor) * factor; + } + + /// + /// Rounds a number up to the nearest factor. + /// + /// + /// + /// + public static double RoundUpToNearest(double num, double factor) + { + return factor.IsEqualTo(0) ? num : Math.Ceiling(num / factor) * factor; + } + + /// + /// Rounds a number to the nearest factor using midpoint rounding convention. + /// + /// + /// + /// + public static double RoundToNearest(double num, double factor) + { + return factor.IsEqualTo(0) ? num : Math.Round(num / factor) * factor; + } + + public static void Optimize(IList arcs) + { + for (int i = 0; i < arcs.Count; ++i) + { + var arc = arcs[i]; + + var coradialArcs = arcs.GetCoradialArs(arc, i); + int index = 0; + + while (index < coradialArcs.Count) + { + Arc arc2 = coradialArcs[index]; + Arc joinArc; + + if (!TryJoinArcs(arc, arc2, out joinArc)) + { + index++; + continue; + } + + coradialArcs.Remove(arc2); + arcs.Remove(arc2); + + arc = joinArc; + index = 0; + } + + arcs[i] = arc; + } + } + + public static void Optimize(IList lines) + { + for (int i = 0; i < lines.Count; ++i) + { + var line = lines[i]; + + var collinearLines = lines.GetCollinearLines(line, i); + var index = 0; + + while (index < collinearLines.Count) + { + Line line2 = collinearLines[index]; + Line joinLine; + + if (!TryJoinLines(line, line2, out joinLine)) + { + index++; + continue; + } + + collinearLines.Remove(line2); + lines.Remove(line2); + + line = joinLine; + index = 0; + } + + lines[i] = line; + } + } + + public static bool TryJoinLines(Line line1, Line line2, out Line lineOut) + { + lineOut = null; + + if (line1 == line2) + return false; + + if (!line1.IsCollinearTo(line2)) + return false; + + bool onPoint = false; + + if (line1.StartPoint == line2.StartPoint) + onPoint = true; + else if (line1.StartPoint == line2.EndPoint) + onPoint = true; + else if (line1.EndPoint == line2.StartPoint) + onPoint = true; + else if (line1.EndPoint == line2.EndPoint) + onPoint = true; + + var t1 = line1.StartPoint.Y > line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y; + var t2 = line2.StartPoint.Y > line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y; + var b1 = line1.StartPoint.Y < line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y; + var b2 = line2.StartPoint.Y < line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y; + var l1 = line1.StartPoint.X < line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X; + var l2 = line2.StartPoint.X < line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X; + var r1 = line1.StartPoint.X > line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X; + var r2 = line2.StartPoint.X > line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X; + + if (!onPoint) + { + if (t1 < b2 - Tolerance.Epsilon) return false; + if (b1 > t2 + Tolerance.Epsilon) return false; + if (l1 > r2 + Tolerance.Epsilon) return false; + if (r1 < l2 - Tolerance.Epsilon) return false; + } + + var l = l1 < l2 ? l1 : l2; + var r = r1 > r2 ? r1 : r2; + var t = t1 > t2 ? t1 : t2; + var b = b1 < b2 ? b1 : b2; + + if (!line1.IsVertical() && line1.Slope() < 0) + lineOut = new Line(new Vector(l, t), new Vector(r, b)); + else + lineOut = new Line(new Vector(l, b), new Vector(r, t)); + + return true; + } + + public static bool TryJoinArcs(Arc arc1, Arc arc2, out Arc arcOut) + { + arcOut = null; + + if (arc1 == arc2) + return false; + + if (arc1.Center != arc2.Center) + return false; + + if (!arc1.Radius.IsEqualTo(arc2.Radius)) + return false; + + if (arc1.StartAngle > arc1.EndAngle) + arc1.StartAngle -= Angle.TwoPI; + + if (arc2.StartAngle > arc2.EndAngle) + arc2.StartAngle -= Angle.TwoPI; + + if (arc1.EndAngle < arc2.StartAngle || arc1.StartAngle > arc2.EndAngle) + return false; + + var startAngle = arc1.StartAngle < arc2.StartAngle ? arc1.StartAngle : arc2.StartAngle; + var endAngle = arc1.EndAngle > arc2.EndAngle ? arc1.EndAngle : arc2.EndAngle; + + if (startAngle < 0) startAngle += Angle.TwoPI; + if (endAngle < 0) endAngle += Angle.TwoPI; + + arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle); + + return true; + } + + private static List GetCollinearLines(this IList lines, Line line, int startIndex) + { + var collinearLines = new List(); + + Parallel.For(startIndex, lines.Count, index => + { + var compareLine = lines[index]; + + if (Object.ReferenceEquals(line, compareLine)) + return; + + if (!line.IsCollinearTo(compareLine)) + return; + + lock (collinearLines) + { + collinearLines.Add(compareLine); + } + }); + + return collinearLines; + } + + private static List GetCoradialArs(this IList arcs, Arc arc, int startIndex) + { + var coradialArcs = new List(); + + Parallel.For(startIndex, arcs.Count, index => + { + var compareArc = arcs[index]; + + if (Object.ReferenceEquals(arc, compareArc)) + return; + + if (!arc.IsCoradialTo(compareArc)) + return; + + lock (coradialArcs) + { + coradialArcs.Add(compareArc); + } + }); + + return coradialArcs; + } + + public static List GetShapes(IEnumerable entities) + { + var lines = new List(); + var arcs = new List(); + var circles = new List(); + var shapes = new List(); + + var entities2 = new Queue(entities); + + while (entities2.Count > 0) + { + var entity = entities2.Dequeue(); + + switch (entity.Type) + { + case EntityType.Arc: + arcs.Add((Arc)entity); + break; + + case EntityType.Circle: + circles.Add((Circle)entity); + break; + + case EntityType.Line: + lines.Add((Line)entity); + break; + + case EntityType.Shape: + var shape = (Shape)entity; + shape.Entities.ForEach(e => entities2.Enqueue(e)); + break; + + default: + Debug.Fail("Unhandled geometry type"); + break; + } + } + + foreach (var circle in circles) + { + var shape = new Shape(); + shape.Entities.Add(circle); + shape.UpdateBounds(); + shapes.Add(shape); + } + + var entityList = new List(); + + entityList.AddRange(lines); + entityList.AddRange(arcs); + + while (entityList.Count > 0) + { + var next = entityList[0]; + var shape = new Shape(); + shape.Entities.Add(next); + + entityList.RemoveAt(0); + + Vector startPoint = new Vector(); + Entity connected; + + switch (next.Type) + { + case EntityType.Arc: + var arc = (Arc)next; + startPoint = arc.EndPoint(); + break; + + case EntityType.Line: + var line = (Line)next; + startPoint = line.EndPoint; + break; + } + + while ((connected = GetConnected(startPoint, entityList)) != null) + { + shape.Entities.Add(connected); + entityList.Remove(connected); + + switch (connected.Type) + { + case EntityType.Arc: + var arc = (Arc)connected; + startPoint = arc.EndPoint(); + break; + + case EntityType.Line: + var line = (Line)connected; + startPoint = line.EndPoint; + break; + } + } + + shape.UpdateBounds(); + shapes.Add(shape); + } + + return shapes; + } + + internal static Entity GetConnected(Vector pt, IEnumerable geometry) + { + foreach (var geo in geometry) + { + switch (geo.Type) + { + case EntityType.Arc: + var arc = (Arc)geo; + + if (arc.StartPoint() == pt) + return arc; + + if (arc.EndPoint() == pt) + { + arc.Reverse(); + return arc; + } + + break; + + case EntityType.Line: + var line = (Line)geo; + + if (line.StartPoint == pt) + return line; + + if (line.EndPoint == pt) + { + line.Reverse(); + return line; + } + break; + } + } + + return null; + } + + internal static bool Intersects(Arc arc1, Arc arc2, out List pts) + { + var c1 = new Circle(arc1.Center, arc1.Radius); + var c2 = new Circle(arc2.Center, arc2.Radius); + + if (!Intersects(c1, c2, out pts)) + { + pts = new List(); + return false; + } + + pts = pts.Where(pt => + Angle.IsBetweenRad(arc1.Center.AngleTo(pt), arc1.StartAngle, arc1.EndAngle, arc1.IsReversed) && + Angle.IsBetweenRad(arc2.Center.AngleTo(pt), arc2.StartAngle, arc2.EndAngle, arc2.IsReversed)) + .ToList(); + + return pts.Count > 0; + } + + internal static bool Intersects(Arc arc, Circle circle, out List pts) + { + var c1 = new Circle(arc.Center, arc.Radius); + + if (!Intersects(c1, circle, out pts)) + { + pts = new List(); + return false; + } + + pts = pts.Where(pt => Angle.IsBetweenRad( + arc.Center.AngleTo(pt), + arc.StartAngle, + arc.EndAngle, + arc.IsReversed)).ToList(); + + return pts.Count > 0; + } + + internal static bool Intersects(Arc arc, Line line, out List pts) + { + var c1 = new Circle(arc.Center, arc.Radius); + + if (!Intersects(c1, line, out pts)) + { + pts = new List(); + return false; + } + + pts = pts.Where(pt => Angle.IsBetweenRad( + arc.Center.AngleTo(pt), + arc.StartAngle, + arc.EndAngle, + arc.IsReversed)).ToList(); + + return pts.Count > 0; + } + + internal static bool Intersects(Arc arc, Shape shape, out List pts) + { + var pts2 = new List(); + + foreach (var geo in shape.Entities) + { + List pts3; + geo.Intersects(arc, out pts3); + pts2.AddRange(pts3); + } + + pts = pts2.Where(pt => Angle.IsBetweenRad( + arc.Center.AngleTo(pt), + arc.StartAngle, + arc.EndAngle, + arc.IsReversed)).ToList(); + + return pts.Count > 0; + } + + internal static bool Intersects(Arc arc, Polygon polygon, out List pts) + { + var pts2 = new List(); + var lines = polygon.ToLines(); + + foreach (var line in lines) + { + List pts3; + Intersects(arc, line, out pts3); + pts2.AddRange(pts3); + } + + pts = pts2.Where(pt => Angle.IsBetweenRad( + arc.Center.AngleTo(pt), + arc.StartAngle, + arc.EndAngle, + arc.IsReversed)).ToList(); + + return pts.Count > 0; + } + + internal static bool Intersects(Circle circle1, Circle circle2, out List pts) + { + var distance = circle1.Center.DistanceTo(circle2.Center); + + // check if circles are too far apart + if (distance > circle1.Radius + circle2.Radius) + { + pts = new List(); + return false; + } + + // check if one circle contains the other + if (distance < Math.Abs(circle1.Radius - circle2.Radius)) + { + pts = new List(); + return false; + } + + var d = circle2.Center - circle1.Center; + var a = (circle1.Radius * circle1.Radius - circle2.Radius * circle2.Radius + distance * distance) / (2.0 * distance); + var h = Math.Sqrt(circle1.Radius * circle1.Radius - a * a); + + var pt = new Vector( + circle1.Center.X + (a * d.X) / distance, + circle1.Center.Y + (a * d.Y) / distance); + + var i1 = new Vector( + pt.X + (h * d.Y) / distance, + pt.Y - (h * d.X) / distance); + + var i2 = new Vector( + pt.X - (h * d.Y) / distance, + pt.Y + (h * d.X) / distance); + + pts = i1 != i2 ? new List { i1, i2 } : new List { i1 }; + + return true; + } + + internal static bool Intersects(Circle circle, Line line, out List pts) + { + var d1 = line.EndPoint - line.StartPoint; + var d2 = line.StartPoint - circle.Center; + + var a = d1.X * d1.X + d1.Y * d1.Y; + var b = (d1.X * d2.X + d1.Y * d2.Y) * 2; + var c = (d2.X * d2.X + d2.Y * d2.Y) - circle.Radius * circle.Radius; + + var det = b * b - 4 * a * c; + + if ((a <= Tolerance.Epsilon) || (det < 0)) + { + pts = new List(); + return false; + } + + double t; + pts = new List(); + + if (det.IsEqualTo(0)) + { + t = -b / (2 * a); + var pt1 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y); + + if (line.BoundingBox.Contains(pt1)) + pts.Add(pt1); + + return true; + } + + t = (-b + Math.Sqrt(det)) / (2 * a); + var pt2 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y); + + if (line.BoundingBox.Contains(pt2)) + pts.Add(pt2); + + t = (-b - Math.Sqrt(det)) / (2 * a); + var pt3 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y); + + if (line.BoundingBox.Contains(pt3)) + pts.Add(pt3); + + return true; + } + + internal static bool Intersects(Circle circle, Shape shape, out List pts) + { + pts = new List(); + + foreach (var geo in shape.Entities) + { + List pts3; + geo.Intersects(circle, out pts3); + pts.AddRange(pts3); + } + + return pts.Count > 0; + } + + internal static bool Intersects(Circle circle, Polygon polygon, out List pts) + { + pts = new List(); + var lines = polygon.ToLines(); + + foreach (var line in lines) + { + List pts3; + Intersects(circle, line, out pts3); + pts.AddRange(pts3); + } + + return pts.Count > 0; + } + + internal static bool Intersects(Line line1, Line line2, out Vector pt) + { + var a1 = line1.EndPoint.Y - line1.StartPoint.Y; + var b1 = line1.StartPoint.X - line1.EndPoint.X; + var c1 = a1 * line1.StartPoint.X + b1 * line1.StartPoint.Y; + + var a2 = line2.EndPoint.Y - line2.StartPoint.Y; + var b2 = line2.StartPoint.X - line2.EndPoint.X; + var c2 = a2 * line2.StartPoint.X + b2 * line2.StartPoint.Y; + + var d = a1 * b2 - a2 * b1; + + if (d.IsEqualTo(0.0)) + { + pt = Vector.Zero; + return false; + } + + var x = (b2 * c1 - b1 * c2) / d; + var y = (a1 * c2 - a2 * c1) / d; + + pt = new Vector(x, y); + return line1.BoundingBox.Contains(pt) && line2.BoundingBox.Contains(pt); + } + + internal static bool Intersects(Line line, Shape shape, out List pts) + { + pts = new List(); + + foreach (var geo in shape.Entities) + { + List pts3; + geo.Intersects(line, out pts3); + pts.AddRange(pts3); + } + + return pts.Count > 0; + } + + internal static bool Intersects(Line line, Polygon polygon, out List pts) + { + pts = new List(); + var lines = polygon.ToLines(); + + foreach (var line2 in lines) + { + Vector pt; + + if (Intersects(line, line2, out pt)) + pts.Add(pt); + } + + return pts.Count > 0; + } + + internal static bool Intersects(Shape shape1, Shape shape2, out List pts) + { + pts = new List(); + + for (int i = 0; i < shape1.Entities.Count; i++) + { + var geo1 = shape1.Entities[i]; + + for (int j = 0; j < shape2.Entities.Count; j++) + { + List pts2; + bool success = false; + + var geo2 = shape2.Entities[j]; + + switch (geo2.Type) + { + case EntityType.Arc: + success = geo1.Intersects((Arc)geo2, out pts2); + break; + + case EntityType.Circle: + success = geo1.Intersects((Circle)geo2, out pts2); + break; + + case EntityType.Line: + success = geo1.Intersects((Line)geo2, out pts2); + break; + + case EntityType.Shape: + success = geo1.Intersects((Shape)geo2, out pts2); + break; + + case EntityType.Polygon: + success = geo1.Intersects((Polygon)geo2, out pts2); + break; + + default: + continue; + } + + if (success) + pts.AddRange(pts2); + } + } + + return pts.Count > 0; + } + + internal static bool Intersects(Shape shape, Polygon polygon, out List pts) + { + pts = new List(); + + var lines = polygon.ToLines(); + + for (int i = 0; i < shape.Entities.Count; i++) + { + var geo = shape.Entities[i]; + + for (int j = 0; j < lines.Count; j++) + { + var line = lines[j]; + + List pts2; + + if (geo.Intersects(line, out pts2)) + pts.AddRange(pts2); + } + } + + return pts.Count > 0; + } + + internal static bool Intersects(Polygon polygon1, Polygon polygon2, out List pts) + { + pts = new List(); + + var lines1 = polygon1.ToLines(); + var lines2 = polygon2.ToLines(); + + for (int i = 0; i < lines1.Count; i++) + { + var line1 = lines1[i]; + + for (int j = 0; j < lines2.Count; j++) + { + var line2 = lines2[j]; + Vector pt; + + if (Intersects(line1, line2, out pt)) + pts.Add(pt); + } + } + + return pts.Count > 0; + } + + public static double ClosestDistanceLeft(Box box, List boxes) + { + var closestDistance = double.MaxValue; + + for (int i = 0; i < boxes.Count; i++) + { + var compareBox = boxes[i]; + + RelativePosition pos; + + if (!box.IsHorizontalTo(compareBox, out pos)) + continue; + + if (pos != RelativePosition.Right) + continue; + + var distance = box.Left - compareBox.Right; + + if (distance < closestDistance) + closestDistance = distance; + } + + return closestDistance == double.MaxValue ? double.NaN : closestDistance; + } + + public static double ClosestDistanceRight(Box box, List boxes) + { + var closestDistance = double.MaxValue; + + for (int i = 0; i < boxes.Count; i++) + { + var compareBox = boxes[i]; + + RelativePosition pos; + + if (!box.IsHorizontalTo(compareBox, out pos)) + continue; + + if (pos != RelativePosition.Left) + continue; + + var distance = compareBox.Left - box.Right; + + if (distance < closestDistance) + closestDistance = distance; + } + + return closestDistance == double.MaxValue ? double.NaN : closestDistance; + } + + public static double ClosestDistanceUp(Box box, List boxes) + { + var closestDistance = double.MaxValue; + + for (int i = 0; i < boxes.Count; i++) + { + var compareBox = boxes[i]; + + RelativePosition pos; + + if (!box.IsVerticalTo(compareBox, out pos)) + continue; + + if (pos != RelativePosition.Bottom) + continue; + + var distance = compareBox.Bottom - box.Top; + + if (distance < closestDistance) + closestDistance = distance; + } + + return closestDistance == double.MaxValue ? double.NaN : closestDistance; + } + + public static double ClosestDistanceDown(Box box, List boxes) + { + var closestDistance = double.MaxValue; + + for (int i = 0; i < boxes.Count; i++) + { + var compareBox = boxes[i]; + + RelativePosition pos; + + if (!box.IsVerticalTo(compareBox, out pos)) + continue; + + if (pos != RelativePosition.Top) + continue; + + var distance = box.Bottom - compareBox.Top; + + if (distance < closestDistance) + closestDistance = distance; + } + + return closestDistance == double.MaxValue ? double.NaN : closestDistance; + } + + public static Box GetLargestBoxVertically(Vector pt, Box bounds, IEnumerable boxes) + { + var verticalBoxes = boxes.Where(b => !(b.Left > pt.X || b.Right < pt.X)).ToList(); + + #region Find Top/Bottom Limits + + var top = double.MaxValue; + var btm = double.MinValue; + + foreach (var box in verticalBoxes) + { + var boxBtm = box.Bottom; + var boxTop = box.Top; + + if (boxBtm > pt.Y && boxBtm < top) + top = boxBtm; + + else if (box.Top < pt.Y && boxTop > btm) + btm = boxTop; + } + + if (top == double.MaxValue) + { + if (bounds.Top > pt.Y) + top = bounds.Top; + else return Box.Empty; + } + + if (btm == double.MinValue) + { + if (bounds.Bottom < pt.Y) + btm = bounds.Bottom; + else return Box.Empty; + } + + #endregion + + var horizontalBoxes = boxes.Where(b => !(b.Bottom >= top || b.Top <= btm)).ToList(); + + #region Find Left/Right Limits + + var lft = double.MinValue; + var rgt = double.MaxValue; + + foreach (var box in horizontalBoxes) + { + var boxLft = box.Left; + var boxRgt = box.Right; + + if (boxLft > pt.X && boxLft < rgt) + rgt = boxLft; + + else if (boxRgt < pt.X && boxRgt > lft) + lft = boxRgt; + } + + if (rgt == double.MaxValue) + { + if (bounds.Right > pt.X) + rgt = bounds.Right; + else return Box.Empty; + } + + if (lft == double.MinValue) + { + if (bounds.Left < pt.X) + lft = bounds.Left; + else return Box.Empty; + } + + #endregion + + return new Box(lft, btm, rgt - lft, top - btm); + } + + public static Box GetLargestBoxHorizontally(Vector pt, Box bounds, IEnumerable boxes) + { + var horizontalBoxes = boxes.Where(b => !(b.Bottom > pt.Y || b.Top < pt.Y)).ToList(); + + #region Find Left/Right Limits + + var lft = double.MinValue; + var rgt = double.MaxValue; + + foreach (var box in horizontalBoxes) + { + var boxLft = box.Left; + var boxRgt = box.Right; + + if (boxLft > pt.X && boxLft < rgt) + rgt = boxLft; + + else if (boxRgt < pt.X && boxRgt > lft) + lft = boxRgt; + } + + if (rgt == double.MaxValue) + { + if (bounds.Right > pt.X) + rgt = bounds.Right; + else return Box.Empty; + } + + if (lft == double.MinValue) + { + if (bounds.Left < pt.X) + lft = bounds.Left; + else return Box.Empty; + } + + #endregion + + var verticalBoxes = boxes.Where(b => !(b.Left >= rgt || b.Right <= lft)).ToList(); + + #region Find Top/Bottom Limits + + var top = double.MaxValue; + var btm = double.MinValue; + + foreach (var box in verticalBoxes) + { + var boxBtm = box.Bottom; + var boxTop = box.Top; + + if (boxBtm > pt.Y && boxBtm < top) + top = boxBtm; + + else if (box.Top < pt.Y && boxTop > btm) + btm = boxTop; + } + + if (top == double.MaxValue) + { + if (bounds.Top > pt.Y) + top = bounds.Top; + else return Box.Empty; + } + + if (btm == double.MinValue) + { + if (bounds.Bottom < pt.Y) + btm = bounds.Bottom; + else return Box.Empty; + } + + #endregion + + return new Box(lft, btm, rgt - lft, top - btm); + } + } +} diff --git a/Source/OpenNest.Core/IBoundable.cs b/Source/OpenNest.Core/IBoundable.cs new file mode 100644 index 0000000..2b95ead --- /dev/null +++ b/Source/OpenNest.Core/IBoundable.cs @@ -0,0 +1,18 @@ + +namespace OpenNest +{ + public interface IBoundable + { + Box BoundingBox { get; } + + double Left { get; } + + double Right { get; } + + double Top { get; } + + double Bottom { get; } + + void UpdateBounds(); + } +} diff --git a/Source/OpenNest.Core/IPostProcessor.cs b/Source/OpenNest.Core/IPostProcessor.cs new file mode 100644 index 0000000..f34a65c --- /dev/null +++ b/Source/OpenNest.Core/IPostProcessor.cs @@ -0,0 +1,17 @@ +using System.IO; + +namespace OpenNest +{ + public interface IPostProcessor + { + string Name { get; } + + string Author { get; } + + string Description { get; } + + void Post(Nest nest, Stream outputStream); + + void Post(Nest nest, string outputFile); + } +} diff --git a/Source/OpenNest.Core/Material.cs b/Source/OpenNest.Core/Material.cs new file mode 100644 index 0000000..c033d2b --- /dev/null +++ b/Source/OpenNest.Core/Material.cs @@ -0,0 +1,33 @@ +namespace OpenNest +{ + public class Material + { + public Material() + { + } + + public Material(string name) + { + Name = name; + } + + public Material(string name, string grade) + { + Name = name; + Grade = grade; + } + + public Material(string name, string grade, double density) + { + Name = name; + Grade = grade; + Density = density; + } + + public string Name { get; set; } + + public string Grade { get; set; } + + public double Density { get; set; } + } +} diff --git a/Source/OpenNest.Core/Nest.cs b/Source/OpenNest.Core/Nest.cs new file mode 100644 index 0000000..0b20dce --- /dev/null +++ b/Source/OpenNest.Core/Nest.cs @@ -0,0 +1,139 @@ +using System; +using OpenNest.Collections; + +namespace OpenNest +{ + public class Nest + { + public PlateCollection Plates; + public DrawingCollection Drawings; + + public Nest() + : this(string.Empty) + { + } + + public Nest(string name) + { + Name = name; + Plates = new PlateCollection(); + Plates.PlateRemoved += Plates_PlateRemoved; + Drawings = new DrawingCollection(); + PlateDefaults = new PlateSettings(); + Customer = string.Empty; + Notes = string.Empty; + } + + private static void Plates_PlateRemoved(object sender, PlateRemovedEventArgs e) + { + e.Plate.Parts.Clear(); + } + + public string Name { get; set; } + + public string Customer { get; set; } + + public string Notes { get; set; } + + public Units Units { get; set; } + + public DateTime DateCreated { get; set; } + + public DateTime DateLastModified { get; set; } + + public PlateSettings PlateDefaults { get; set; } + + public Plate CreatePlate() + { + var plate = PlateDefaults.CreateNew(); + Plates.Add(plate); + return plate; + } + + public void UpdateDrawingQuantities() + { + foreach (var drawing in Drawings) + { + drawing.Quantity.Nested = 0; + } + + foreach (var plate in Plates) + { + foreach (var part in plate.Parts) + { + part.BaseDrawing.Quantity.Nested += plate.Quantity; + } + } + } + + public class PlateSettings + { + private readonly Plate plate; + + public PlateSettings() + { + plate = new Plate(); + } + + public int Quadrant + { + get { return plate.Quadrant; } + set { plate.Quadrant = value; } + } + + public double Thickness + { + get { return plate.Thickness; } + set { plate.Thickness = value; } + } + + public Material Material + { + get { return plate.Material; } + set { plate.Material = value; } + } + + public Size Size + { + get { return plate.Size; } + set { plate.Size = value; } + } + + public Spacing EdgeSpacing + { + get { return plate.EdgeSpacing; } + set { plate.EdgeSpacing = value; } + } + + public double PartSpacing + { + get { return plate.PartSpacing; } + set { plate.PartSpacing = value; } + } + + public void SetFromExisting(Plate plate) + { + Thickness = plate.Thickness; + Quadrant = plate.Quadrant; + Material = plate.Material; + Size = plate.Size; + EdgeSpacing = plate.EdgeSpacing; + PartSpacing = plate.PartSpacing; + } + + public Plate CreateNew() + { + return new Plate() + { + Thickness = Thickness, + Size = Size, + EdgeSpacing = EdgeSpacing, + PartSpacing = PartSpacing, + Material = Material, + Quadrant = Quadrant, + Quantity = 1 + }; + } + } + } +} diff --git a/Source/OpenNest.Core/NestConstraints.cs b/Source/OpenNest.Core/NestConstraints.cs new file mode 100644 index 0000000..f35e468 --- /dev/null +++ b/Source/OpenNest.Core/NestConstraints.cs @@ -0,0 +1,46 @@ +using System; + +namespace OpenNest +{ + public class NestConstraints + { + /// + /// The rotation step in radians. + /// + public double StepAngle { get; set; } + + /// + /// The rotation start angle in radians. + /// + public double StartAngle { get; set; } + + /// + /// The rotation end angle in radians. + /// + public double EndAngle { get; set; } + + public bool Allow180Equivalent { get; set; } + + /// + /// Sets the StartAngle and EndAngle to allow 360 degree rotation. + /// + public void AllowAnyRotation() + { + StartAngle = 0; + EndAngle = Angle.TwoPI; + } + + public bool HasLimitedRotation() + { + var diff = EndAngle - StartAngle; + + if (diff.IsEqualTo(Angle.TwoPI)) + return false; + + if ((diff > Math.PI || diff.IsEqualTo(Math.PI)) && Allow180Equivalent) + return false; + + return true; + } + } +} diff --git a/Source/OpenNest.Core/OffsetSide.cs b/Source/OpenNest.Core/OffsetSide.cs new file mode 100644 index 0000000..fb86c1a --- /dev/null +++ b/Source/OpenNest.Core/OffsetSide.cs @@ -0,0 +1,9 @@ + +namespace OpenNest +{ + public enum OffsetSide + { + Left, + Right + } +} diff --git a/Source/OpenNest.Core/OpenNest.Core.csproj b/Source/OpenNest.Core/OpenNest.Core.csproj new file mode 100644 index 0000000..bfe629b --- /dev/null +++ b/Source/OpenNest.Core/OpenNest.Core.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {5A5FDE8D-F8DB-440E-866C-C4807E1686CF} + Library + Properties + OpenNest + OpenNest.Core + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/OpenNest.Core/Part.cs b/Source/OpenNest.Core/Part.cs new file mode 100644 index 0000000..2a8ac44 --- /dev/null +++ b/Source/OpenNest.Core/Part.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using OpenNest.CNC; + +namespace OpenNest +{ + public interface IPart : IBoundable + { + Vector Location { get; set; } + double Rotation { get; } + void Rotate(double angle); + void Rotate(double angle, Vector origin); + void Offset(double x, double y); + void Offset(Vector voffset); + void Update(); + } + + public class Part : IPart, IBoundable + { + private Vector location; + + public readonly Drawing BaseDrawing; + + public Part(Drawing baseDrawing) + : this(baseDrawing, new Vector()) + { + } + + public Part(Drawing baseDrawing, Vector location) + { + BaseDrawing = baseDrawing; + Program = baseDrawing.Program.Clone() as Program; + this.location = location; + UpdateBounds(); + } + + /// + /// Location of the part. + /// + public Vector Location + { + get { return location; } + set + { + BoundingBox.Offset(value - location); + location = value; + } + } + + public Program Program { get; private set; } + + /// + /// Gets the rotation of the part in radians. + /// + public double Rotation + { + get { return Program.Rotation; } + } + + /// + /// Rotates the part. + /// + /// Angle of rotation in radians. + public void Rotate(double angle) + { + Program.Rotate(angle); + location = Location.Rotate(angle); + UpdateBounds(); + } + + /// + /// Rotates the part around the specified origin. + /// + /// Angle of rotation in radians. + /// The origin to rotate the part around. + public void Rotate(double angle, Vector origin) + { + Program.Rotate(angle); + location = Location.Rotate(angle, origin); + UpdateBounds(); + } + + /// + /// Offsets the part. + /// + /// The x-axis offset distance. + /// The y-axis offset distance. + public void Offset(double x, double y) + { + location = new Vector(location.X + x, location.Y + y); + BoundingBox.Offset(x, y); + } + + /// + /// Offsets the part. + /// + /// The vector containing the x-axis & y-axis offset distances. + public void Offset(Vector voffset) + { + location += voffset; + BoundingBox.Offset(voffset); + } + + /// + /// Updates the bounding box of the part. + /// + public void UpdateBounds() + { + BoundingBox = Program.BoundingBox(); + BoundingBox.Offset(Location); + } + + /// + /// Updates the part from the drawing it was derived from. + /// + public void Update() + { + var rotation = Rotation; + Program = BaseDrawing.Program.Clone() as Program; + Program.Rotate(Program.Rotation - rotation); + } + + /// + /// The smallest box that contains the part. + /// + public Box BoundingBox { get; protected set; } + + public bool Intersects(Part part, out List pts) + { + pts = new List(); + + var entities1 = ConvertProgram.ToGeometry(Program); + var entities2 = ConvertProgram.ToGeometry(part.Program); + + var shapes1 = Helper.GetShapes(entities1); + var shapes2 = Helper.GetShapes(entities2); + + shapes1.ForEach(shape => shape.Offset(Location)); + shapes2.ForEach(shape => shape.Offset(part.Location)); + + for (int i = 0; i < shapes1.Count; i++) + { + var shape1 = shapes1[i]; + + for (int j = 0; j < shapes2.Count; j++) + { + var shape2 = shapes2[j]; + List pts2; + + if (shape1.Intersects(shape2, out pts2)) + pts.AddRange(pts2); + } + } + + return pts.Count > 0; + } + + public double Left + { + get { return BoundingBox.Left; } + } + + public double Right + { + get { return BoundingBox.Right; } + } + + public double Top + { + get { return BoundingBox.Top; } + } + + public double Bottom + { + get { return BoundingBox.Bottom; } + } + + /// + /// Gets a deep copy of the part. + /// + /// + public object Clone() + { + var part = new Part(BaseDrawing); + part.Rotate(Rotation); + part.Location = Location; + + return part; + } + } +} diff --git a/Source/OpenNest.Core/Plate.cs b/Source/OpenNest.Core/Plate.cs new file mode 100644 index 0000000..8562fea --- /dev/null +++ b/Source/OpenNest.Core/Plate.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OpenNest.Collections; + +namespace OpenNest +{ + public class Plate + { + private int quadrant; + + public event EventHandler PartAdded + { + add { Parts.PartAdded += value; } + remove { Parts.PartAdded -= value; } + } + + public event EventHandler PartRemoved + { + add { Parts.PartRemoved += value; } + remove { Parts.PartRemoved -= value; } + } + + public event EventHandler PartChanged + { + add { Parts.PartChanged += value; } + remove { Parts.PartChanged -= value; } + } + + public Plate() + : this(60, 120) + { + } + + public Plate(double width, double length) + : this(new Size(length, width)) + { + } + + public Plate(Size size) + { + EdgeSpacing = new Spacing(); + Size = size; + Material = new Material(); + Parts = new PartCollection(); + Parts.PartAdded += Parts_PartAdded; + Parts.PartRemoved += Parts_PartRemoved; + Quadrant = 1; + } + + private void Parts_PartAdded(object sender, PartAddedEventArgs e) + { + e.Part.BaseDrawing.Quantity.Nested += Quantity; + } + + private void Parts_PartRemoved(object sender, PartRemovedEventArgs e) + { + e.Part.BaseDrawing.Quantity.Nested -= Quantity; + } + + /// + /// Thickness of the plate. + /// + public double Thickness { get; set; } + + /// + /// The spacing between parts. + /// + public double PartSpacing { get; set; } + + /// + /// The spacing along the edges of the plate. + /// + public Spacing EdgeSpacing; + + /// + /// The size of the plate. + /// + public Size Size { get; set; } + + /// + /// Material the plate is made out of. + /// + public Material Material { get; set; } + + /// + /// The parts that the plate contains. + /// + public PartCollection Parts { get; set; } + + /// + /// The number of times to cut the plate. + /// + public int Quantity { get; set; } + + /// + /// The quadrant the plate is located in. + /// 1 = TopRight + /// 2 = TopLeft + /// 3 = BottomLeft + /// 4 = BottomRight + /// + public int Quadrant + { + get { return quadrant; } + set { quadrant = value <= 4 && value > 0 ? value : 1; } + } + + /// + /// Rotates the plate clockwise or counter-clockwise along with all parts. + /// + /// + /// + public void Rotate90(RotationType rotationDirection, bool keepSameQuadrant = true) + { + const double oneAndHalfPI = Math.PI * 1.5; + + Size = new Size(Size.Height, Size.Width); + + if (rotationDirection == RotationType.CW) + { + Rotate(oneAndHalfPI); + + if (keepSameQuadrant) + { + switch (Quadrant) + { + case 1: + Offset(0, Size.Height); + break; + + case 2: + Offset(-Size.Width, 0); + break; + + case 3: + Offset(0, -Size.Height); + break; + + case 4: + Offset(Size.Width, 0); + break; + + default: + return; + } + } + else + { + Quadrant = Quadrant < 2 ? 4 : Quadrant - 1; + } + } + else + { + Rotate(Angle.HalfPI); + + if (keepSameQuadrant) + { + switch (Quadrant) + { + case 1: + Offset(Size.Width, 0); + break; + + case 2: + Offset(0, Size.Height); + break; + + case 3: + Offset(-Size.Width, 0); + break; + + case 4: + Offset(0, -Size.Height); + break; + + default: + return; + } + } + else + { + Quadrant = Quadrant > 3 ? 1 : Quadrant + 1; + } + } + } + + /// + /// Rotates the plate 180 degrees along with all parts. + /// + /// + public void Rotate180(bool keepSameQuadrant = true) + { + if (keepSameQuadrant) + { + Vector centerpt; + + switch (Quadrant) + { + case 1: + centerpt = new Vector(Size.Width * 0.5, Size.Height * 0.5); + break; + + case 2: + centerpt = new Vector(-Size.Width * 0.5, Size.Height * 0.5); + break; + + case 3: + centerpt = new Vector(-Size.Width * 0.5, -Size.Height * 0.5); + break; + + case 4: + centerpt = new Vector(Size.Width * 0.5, -Size.Height * 0.5); + break; + + default: + return; + } + + Rotate(Math.PI, centerpt); + } + else + { + Rotate(Math.PI); + Quadrant = (Quadrant + 2) % 4; + + if (Quadrant == 0) + Quadrant = 4; + } + } + + /// + /// Rotates the parts on the plate. + /// + /// + public void Rotate(double angle) + { + for (int i = 0; i < Parts.Count; ++i) + { + var part = Parts[i]; + part.Rotate(angle); + } + } + + /// + /// Rotates the parts on the plate around the specified origin. + /// + /// + /// + public void Rotate(double angle, Vector origin) + { + for (int i = 0; i < Parts.Count; ++i) + { + var part = Parts[i]; + part.Rotate(angle, origin); + } + } + + /// + /// Offsets the parts on the plate. + /// + /// + /// + public void Offset(double x, double y) + { + for (int i = 0; i < Parts.Count; ++i) + { + var part = Parts[i]; + part.Offset(x, y); + } + } + + /// + /// Offsets the parts on the plate. + /// + /// + public void Offset(Vector voffset) + { + for (int i = 0; i < Parts.Count; ++i) + { + var part = Parts[i]; + part.Offset(voffset); + } + } + + /// + /// The smallest box that contains the plate. + /// + /// + /// + public Box BoundingBox(bool includeParts = true) + { + var plateBox = new Box(); + + switch (Quadrant) + { + case 1: + plateBox.X = 0; + plateBox.Y = 0; + break; + + case 2: + plateBox.X = (float)-Size.Width; + plateBox.Y = 0; + break; + + case 3: + plateBox.X = (float)-Size.Width; + plateBox.Y = (float)-Size.Height; + break; + + case 4: + plateBox.X = 0; + plateBox.Y = (float)-Size.Height; + break; + + default: + return new Box(); + } + + plateBox.Width = Size.Width; + plateBox.Height = Size.Height; + + if (!includeParts) + return plateBox; + + var boundingBox = new Box(); + var partsBox = Parts.GetBoundingBox(); + + boundingBox.X = partsBox.Left < plateBox.Left + ? partsBox.Left + : plateBox.Left; + + boundingBox.Y = partsBox.Bottom < plateBox.Bottom + ? partsBox.Bottom + : plateBox.Bottom; + + boundingBox.Width = partsBox.Right > plateBox.Right + ? partsBox.Right - boundingBox.X + : plateBox.Right - boundingBox.X; + + boundingBox.Height = partsBox.Top > plateBox.Top + ? partsBox.Top - boundingBox.Y + : plateBox.Top - boundingBox.Y; + + return boundingBox; + } + + /// + /// The area within the edge spacing. + /// + /// + public Box WorkArea() + { + var box = BoundingBox(false); + + box.X += EdgeSpacing.Left; + box.Y += EdgeSpacing.Bottom; + box.Width -= EdgeSpacing.Left + EdgeSpacing.Right; + box.Height -= EdgeSpacing.Top + EdgeSpacing.Bottom; + + return box; + } + + /// + /// Automatically sizes the plate to fit the parts. + /// + /// The factor to round the actual size up to. + /// + /// AutoSize 9.7 x 10.1 + /// * roundingFactor=1.0 new Size=10 x 11 + /// * roundingFactor=0.5 new Size=10 x 10.5 + /// * roundingFactor=0.25 new Size=9.75 x 10.25 + /// * roundingFactor=0.0 new Size=9.7 x 10.1 + /// + public void AutoSize(double roundingFactor = 1.0) + { + if (Parts.Count == 0) + return; + + var bounds = Parts.GetBoundingBox(); + + double width; + double height; + + switch (Quadrant) + { + case 1: + width = Math.Abs(bounds.Right) + EdgeSpacing.Right; + height = Math.Abs(bounds.Top) + EdgeSpacing.Top; + break; + + case 2: + width = Math.Abs(bounds.Left) + EdgeSpacing.Left; + height = Math.Abs(bounds.Top) + EdgeSpacing.Top; + break; + + case 3: + width = Math.Abs(bounds.Left) + EdgeSpacing.Left; + height = Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom; + break; + + case 4: + width = Math.Abs(bounds.Right) + EdgeSpacing.Right; + height = Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom; + break; + + default: + return; + } + + Size = new Size( + Helper.RoundUpToNearest(width, roundingFactor), + Helper.RoundUpToNearest(height, roundingFactor)); + } + + /// + /// Gets the area of the top surface of the plate. + /// + /// + public double Area() + { + return Size.Width * Size.Height; + } + + /// + /// Gets the volume of the plate. + /// + /// + public double Volume() + { + return Area() * Thickness; + } + + /// + /// Gets the weight of the plate. + /// + /// + public double Weight() + { + return Volume() * Material.Density; + } + + /// + /// Percentage of the material used. + /// + /// Returns a number between 0.0 and 1.0 + public double Utilization() + { + return Parts.Sum(part => part.BaseDrawing.Area) / Area(); + } + + public bool HasOverlappingParts(out List pts) + { + pts = new List(); + + for (int i = 0; i < Parts.Count; i++) + { + var part1 = Parts[i]; + + for (int j = i + 1; j < Parts.Count; j++) + { + var part2 = Parts[j]; + + List pts2; + + if (part1.Intersects(part2, out pts2)) + pts.AddRange(pts2); + } + } + + return pts.Count > 0; + } + } +} diff --git a/Source/OpenNest.Core/Properties/AssemblyInfo.cs b/Source/OpenNest.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bd5d103 --- /dev/null +++ b/Source/OpenNest.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenNest.Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OpenNest.Core")] +[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("13d3c141-e430-4f27-9098-60d6f93e2a7b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] diff --git a/Source/OpenNest.Core/Quantity.cs b/Source/OpenNest.Core/Quantity.cs new file mode 100644 index 0000000..2a022a4 --- /dev/null +++ b/Source/OpenNest.Core/Quantity.cs @@ -0,0 +1,14 @@ +namespace OpenNest +{ + public struct Quantity + { + public int Nested { get; internal set; } + + public int Required { get; set; } + + public int Remaining + { + get { return Required - Nested; } + } + } +} diff --git a/Source/OpenNest.Core/RelativePosition.cs b/Source/OpenNest.Core/RelativePosition.cs new file mode 100644 index 0000000..7982cda --- /dev/null +++ b/Source/OpenNest.Core/RelativePosition.cs @@ -0,0 +1,13 @@ + +namespace OpenNest +{ + public enum RelativePosition + { + Intersecting, + Left, + Right, + Top, + Bottom, + None + } +} diff --git a/Source/OpenNest.Core/RotationType.cs b/Source/OpenNest.Core/RotationType.cs new file mode 100644 index 0000000..03c106f --- /dev/null +++ b/Source/OpenNest.Core/RotationType.cs @@ -0,0 +1,16 @@ + +namespace OpenNest +{ + public enum RotationType + { + /// + /// Clockwise + /// + CW, + + /// + /// Counter-Clockwise + /// + CCW + } +} diff --git a/Source/OpenNest.Core/Sequence.cs b/Source/OpenNest.Core/Sequence.cs new file mode 100644 index 0000000..e1f1833 --- /dev/null +++ b/Source/OpenNest.Core/Sequence.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace OpenNest +{ + public interface ISequencer + { + List SequenceParts(IList parts); + List SequenceParts(IList parts, Vector origin); + } + + public class SequenceByNearest : ISequencer + { + public List SequenceParts(IList parts) + { + return SequenceParts(parts, Vector.Zero); + } + + public List SequenceParts(IList parts, Vector origin) + { + if (parts.Count == 0) + return new List(); + + var dupList = new List(parts); + var seqList = new List(parts.Count); + + var lastPart = GetClosestPart(origin, dupList); + seqList.Add(lastPart); + dupList.Remove(lastPart); + + for (int i = 0; i < parts.Count - 1 /*STOP BEFORE LAST PART*/; i++) + { + var nextPart = GetClosestPart(lastPart.Location, dupList); + + if (nextPart == null) + break; + + seqList.Add(nextPart); + dupList.Remove(nextPart); + lastPart = nextPart; + } + + return seqList; + } + + private Part GetClosestPart(Vector pt, IList parts) + { + if (parts.Count == 0) + return null; + + var closestPart = parts[0]; + var closestDistance = parts[0].Location.DistanceTo(pt); + + for (int i = 1; i < parts.Count; i++) + { + var distance = parts[i].Location.DistanceTo(pt); + + if (distance < closestDistance) + { + closestPart = parts[i]; + closestDistance = distance; + } + } + + return closestPart; + } + } +} diff --git a/Source/OpenNest.Core/Size.cs b/Source/OpenNest.Core/Size.cs new file mode 100644 index 0000000..1fb89c2 --- /dev/null +++ b/Source/OpenNest.Core/Size.cs @@ -0,0 +1,55 @@ +using System; + +namespace OpenNest +{ + public struct Size + { + public Size(double width, double height) + { + Height = height; + Width = width; + } + + public double Height; + + public double Width; + + public static Size Parse(string size) + { + var a = size.ToUpper().Split('X'); + + if (a.Length > 2) + throw new FormatException("Invalid size format."); + + var height = double.Parse(a[0]); + var width = double.Parse(a[1]); + + return new Size(width, height); + } + + public static bool TryParse(string s, out Size size) + { + try + { + size = Parse(s); + } + catch + { + size = new Size(0, 0); + return false; + } + + return true; + } + + public override string ToString() + { + return string.Format("{0} x {1}", Height, Width); + } + + public string ToString(int decimalPlaces) + { + return string.Format("{0} x {1}", Math.Round(Height, decimalPlaces), Math.Round(Width, decimalPlaces)); + } + } +} diff --git a/Source/OpenNest.Core/Spacing.cs b/Source/OpenNest.Core/Spacing.cs new file mode 100644 index 0000000..918fa7b --- /dev/null +++ b/Source/OpenNest.Core/Spacing.cs @@ -0,0 +1,27 @@ +namespace OpenNest +{ + public struct Spacing + { + public Spacing(double topBottom, double leftRight) + { + Top = Bottom = topBottom; + Left = Right = leftRight; + } + + public Spacing(double left, double bottom, double right, double top) + { + Left = left; + Bottom = bottom; + Right = right; + Top = top; + } + + public double Left; + + public double Bottom; + + public double Right; + + public double Top; + } +} diff --git a/Source/OpenNest.Core/SpecialLayers.cs b/Source/OpenNest.Core/SpecialLayers.cs new file mode 100644 index 0000000..9931fae --- /dev/null +++ b/Source/OpenNest.Core/SpecialLayers.cs @@ -0,0 +1,21 @@ +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class SpecialLayers + { + public static readonly Layer Default = new Layer("0"); + + public static readonly Layer Cut = new Layer("CUT"); + + public static readonly Layer Rapid = new Layer("RAPID"); + + public static readonly Layer Display = new Layer("DISPLAY"); + + public static readonly Layer Leadin = new Layer("LEADIN"); + + public static readonly Layer Leadout = new Layer("LEADOUT"); + + public static readonly Layer Scribe = new Layer("SCRIBE"); + } +} diff --git a/Source/OpenNest.Core/Timing.cs b/Source/OpenNest.Core/Timing.cs new file mode 100644 index 0000000..9ab5565 --- /dev/null +++ b/Source/OpenNest.Core/Timing.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class Timing + { + public static TimingInfo GetTimingInfo(Program pgm) + { + var entities = ConvertProgram.ToGeometry(pgm); + var shapes = Helper.GetShapes(entities.Where(entity => entity.Layer != SpecialLayers.Rapid)); + var info = new TimingInfo { PierceCount = shapes.Count }; + + var last = entities[0]; + ProcessEntity(info, last, null); + + for (int i = 1; i < entities.Count; i++) + { + var entity = entities[i]; + ProcessEntity(info, entity, last); + last = entity; + } + + return info; + } + + public static TimingInfo GetTimingInfo(Plate plate) + { + var info = new TimingInfo(); + var pos = new Vector(0, 0); + + foreach (var part in plate.Parts) + { + var endpt = part.Program.EndPoint() + part.Location; + info += GetTimingInfo(part.Program); + info.TravelDistance += pos.DistanceTo(endpt); + pos = endpt; + } + + info *= plate.Quantity; + + return info; + } + + public static TimingInfo GetTimingInfo(Nest nest) + { + var info = new TimingInfo(); + return nest.Plates.Aggregate(info, (current, plate) => current + GetTimingInfo(plate)); + } + + private static void ProcessEntity(TimingInfo info, Entity entity, Entity lastEntity) + { + if (entity.Layer == SpecialLayers.Cut) + { + info.CutDistance += entity.Length; + + if (entity.Type == EntityType.Line && + lastEntity != null && + lastEntity.Type == EntityType.Line && + lastEntity.Layer == SpecialLayers.Cut) + info.IntersectionCount++; + } + else if (entity.Layer == SpecialLayers.Rapid) + info.TravelDistance += entity.Length; + } + + public static TimeSpan CalculateTime(TimingInfo info, CutParameters cutParams) + { + var time = new TimeSpan(); + + switch (cutParams.Units) + { + case Units.Inches: + time += TimeSpan.FromMinutes(info.CutDistance / cutParams.Feedrate); + time += TimeSpan.FromMinutes(info.TravelDistance / cutParams.RapidTravelRate); + break; + + case Units.Millimeters: + time += TimeSpan.FromSeconds(info.CutDistance / cutParams.Feedrate); + time += TimeSpan.FromSeconds(info.TravelDistance / cutParams.RapidTravelRate); + break; + } + + time += TimeSpan.FromTicks(info.PierceCount * cutParams.PierceTime.Ticks); + + return time; + } + } +} diff --git a/Source/OpenNest.Core/TimingInfo.cs b/Source/OpenNest.Core/TimingInfo.cs new file mode 100644 index 0000000..e0f5854 --- /dev/null +++ b/Source/OpenNest.Core/TimingInfo.cs @@ -0,0 +1,57 @@ +namespace OpenNest +{ + public class TimingInfo + { + public int PierceCount; + + public int IntersectionCount; + + public double TravelDistance; + + public double CutDistance; + + public static TimingInfo operator +(TimingInfo info1, TimingInfo info2) + { + return new TimingInfo + { + CutDistance = info1.CutDistance + info2.CutDistance, + IntersectionCount = info1.IntersectionCount + info2.IntersectionCount, + TravelDistance = info1.TravelDistance + info2.TravelDistance, + PierceCount = info1.PierceCount + info2.PierceCount + }; + } + + public static TimingInfo operator -(TimingInfo info1, TimingInfo info2) + { + return new TimingInfo + { + CutDistance = info1.CutDistance - info2.CutDistance, + IntersectionCount = info1.IntersectionCount - info2.IntersectionCount, + TravelDistance = info1.TravelDistance - info2.TravelDistance, + PierceCount = info1.PierceCount - info2.PierceCount + }; + } + + public static TimingInfo operator *(TimingInfo info1, TimingInfo info2) + { + return new TimingInfo + { + CutDistance = info1.CutDistance * info2.CutDistance, + IntersectionCount = info1.IntersectionCount * info2.IntersectionCount, + TravelDistance = info1.TravelDistance * info2.TravelDistance, + PierceCount = info1.PierceCount * info2.PierceCount + }; + } + + public static TimingInfo operator *(TimingInfo info1, int factor) + { + return new TimingInfo + { + CutDistance = info1.CutDistance * factor, + IntersectionCount = info1.IntersectionCount * factor, + TravelDistance = info1.TravelDistance * factor, + PierceCount = info1.PierceCount * factor + }; + } + } +} diff --git a/Source/OpenNest.Core/Tolerance.cs b/Source/OpenNest.Core/Tolerance.cs new file mode 100644 index 0000000..15924b0 --- /dev/null +++ b/Source/OpenNest.Core/Tolerance.cs @@ -0,0 +1,14 @@ +using System; + +namespace OpenNest +{ + public static class Tolerance + { + public const double Epsilon = 0.00001; + + public static bool IsEqualTo(this double a, double b, double tolerance = Epsilon) + { + return Math.Abs(b - a) <= tolerance; + } + } +} diff --git a/Source/OpenNest.Core/Trigonometry.cs b/Source/OpenNest.Core/Trigonometry.cs new file mode 100644 index 0000000..b3c3778 --- /dev/null +++ b/Source/OpenNest.Core/Trigonometry.cs @@ -0,0 +1,40 @@ +using System; + +namespace OpenNest +{ + public static class Trigonometry + { + /// + /// + /// + /// Height + /// Hypotenuse + /// + public static double Base(double height, double hypotenuse) + { + return Math.Sqrt(hypotenuse * hypotenuse - height * height); + } + + /// + /// + /// + /// Base + /// Hypotenuse + /// + public static double Height(double bottom, double hypotenuse) + { + return Math.Sqrt(hypotenuse * hypotenuse - bottom * bottom); + } + + /// + /// + /// + /// Height + /// Base + /// + public static double Hypotenuse(double height, double bottom) + { + return Math.Sqrt(height * height + bottom * bottom); + } + } +} diff --git a/Source/OpenNest.Core/Units.cs b/Source/OpenNest.Core/Units.cs new file mode 100644 index 0000000..3b538b4 --- /dev/null +++ b/Source/OpenNest.Core/Units.cs @@ -0,0 +1,82 @@ + +namespace OpenNest +{ + public enum Units + { + Inches, + Millimeters + } + + public static class UnitsHelper + { + public static string GetShortString(Units units) + { + switch (units) + { + case Units.Inches: + return "in"; + + case Units.Millimeters: + return "mm"; + + default: + return string.Empty; + } + } + + public static string GetLongString(Units units) + { + switch (units) + { + case Units.Inches: + return "inches"; + + case Units.Millimeters: + return "millimeters"; + + default: + return string.Empty; + } + } + + public static string GetShortTimeUnit(Units units) + { + switch (units) + { + case Units.Inches: + return "min"; + + case Units.Millimeters: + return "sec"; + + default: + return string.Empty; + } + } + + public static string GetLongTimeUnit(Units units) + { + switch (units) + { + case Units.Inches: + return "minute"; + + case Units.Millimeters: + return "second"; + + default: + return string.Empty; + } + } + + public static string GetShortTimeUnitPair(Units units) + { + return GetShortString(units) + "/" + GetShortTimeUnit(units); + } + + public static string GetLongTimeUnitPair(Units units) + { + return GetLongString(units) + "/" + GetLongTimeUnit(units); + } + } +} diff --git a/Source/OpenNest.Core/Vector.cs b/Source/OpenNest.Core/Vector.cs new file mode 100644 index 0000000..7252df1 --- /dev/null +++ b/Source/OpenNest.Core/Vector.cs @@ -0,0 +1,213 @@ +using System; + +namespace OpenNest +{ + public struct Vector + { + public static readonly Vector Invalid = new Vector(double.NaN, double.NaN); + public static readonly Vector Zero = new Vector(0, 0); + + public double X; + public double Y; + + public Vector(double x, double y) + { + X = x; + Y = y; + } + + public double DistanceTo(Vector pt) + { + var vx = pt.X - X; + var vy = pt.Y - Y; + + return Math.Sqrt(vx * vx + vy * vy); + } + + public double DistanceTo(double x, double y) + { + var vx = x - X; + var vy = y - Y; + + return Math.Sqrt(vx * vx + vy * vy); + } + + public double DotProduct(Vector pt) + { + return X * pt.X + Y * pt.Y; + } + + public double Angle() + { + return OpenNest.Angle.NormalizeRad(Math.Atan2(Y, X)); + } + + /// + /// Returns the angle to the given point when the origin is this point. + /// + /// + /// + public double AngleTo(Vector pt) + { + return (pt - this).Angle(); + } + + /// + /// Returns the angle when the origin is set at the given point. + /// + /// + /// + public double AngleFrom(Vector pt) + { + return (this - pt).Angle(); + } + + /// + /// Returns the angle between this point and the given point. + /// Source: http://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors + /// + /// + /// + public double AngleBetween(Vector pt) + { + var v1 = Normalize(); + var v2 = pt.Normalize(); + var dot = v1.X * v2.X + v1.Y + v2.Y; + var det = v1.X * v2.X - v1.Y + v2.Y; + + return Math.Atan2(det, dot); + } + + public static Vector operator +(Vector pt1, Vector pt2) + { + return new Vector(pt1.X + pt2.X, pt1.Y + pt2.Y); + } + + public static Vector operator -(Vector pt1, Vector pt2) + { + return new Vector(pt1.X - pt2.X, pt1.Y - pt2.Y); + } + + public static Vector operator -(Vector pt) + { + return new Vector(-pt.X, -pt.Y); + } + + public static Vector operator *(Vector pt, double factor) + { + return new Vector(pt.X * factor, pt.Y * factor); + } + + public static Vector operator *(double factor, Vector pt) + { + return new Vector(pt.X * factor, pt.Y * factor); + } + + public static Vector operator *(Vector pt, Vector factor) + { + return new Vector(pt.X * factor.X, pt.Y * factor.Y); + } + + public static Vector operator /(Vector pt, double divisor) + { + return new Vector(pt.X / divisor, pt.Y / divisor); + } + + public static bool operator ==(Vector pt1, Vector pt2) + { + return pt1.X.IsEqualTo(pt2.X) && pt1.Y.IsEqualTo(pt2.Y); + } + + public static bool operator !=(Vector pt1, Vector pt2) + { + return !(pt1 == pt2); + } + + /// + /// Returns the unit vector equivalent to this point. + /// + /// + public Vector Normalize() + { + var d = DistanceTo(Vector.Zero); + return new Vector(X / d, Y / d); + } + + public Vector Rotate(double angle) + { + var v = new Vector(); + + var cos = Math.Cos(angle); + var sin = Math.Sin(angle); + + v.X = X * cos - Y * sin; + v.Y = X * sin + Y * cos; + + return v; + } + + public Vector Rotate(double angle, Vector origin) + { + var v = new Vector(); + var pt = this - origin; + + var cos = Math.Cos(angle); + var sin = Math.Sin(angle); + + v.X = pt.X * cos - pt.Y * sin + origin.X; + v.Y = pt.X * sin + pt.Y * cos + origin.Y; + + return v; + } + + public Vector Offset(double x, double y) + { + return new Vector(X + x, Y + y); + } + + public Vector Offset(Vector voffset) + { + return this + voffset; + } + + public Vector Scale(double factor) + { + return new Vector(X * factor, Y * factor); + } + + public Vector Scale(double factor, Vector origin) + { + return (this - origin) * factor + origin; + } + + public Vector Clone() + { + return new Vector(X, Y); + } + + public override bool Equals(object obj) + { + if (!(obj is Vector)) + return false; + + var pt = (Vector)obj; + + return (X.IsEqualTo(pt.X)) && (Y.IsEqualTo(pt.Y)); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + return string.Format("[Vector: X:{0}, Y:{1}]", X, Y); + } + + public bool IsValid() + { + return !double.IsNaN(X) && !double.IsNaN(Y); + } + } +} diff --git a/Source/OpenNest.Engine/BestCombination.cs b/Source/OpenNest.Engine/BestCombination.cs new file mode 100644 index 0000000..84260cb --- /dev/null +++ b/Source/OpenNest.Engine/BestCombination.cs @@ -0,0 +1,80 @@ +using System; + +namespace OpenNest +{ + internal static class BestCombination + { + public static bool FindFrom2(double length1, double length2, double overallLength, out int count1, out int count2) + { + overallLength += Tolerance.Epsilon; + + if (length1 > overallLength) + { + if (length2 > overallLength) + { + count1 = 0; + count2 = 0; + return false; + } + + count1 = 0; + count2 = (int)Math.Floor(overallLength / length2); + return true; + } + + if (length2 > overallLength) + { + count1 = (int)Math.Floor(overallLength / length1); + count2 = 0; + return true; + } + + var maxCountLength1 = (int)Math.Floor(overallLength / length1); + + count1 = maxCountLength1; + count2 = 0; + + var remnant = overallLength - maxCountLength1 * length1; + + if (remnant.IsEqualTo(0)) + return true; + + for (int countLength1 = 0; countLength1 <= maxCountLength1; ++countLength1) + { + var remnant1 = overallLength - countLength1 * length1; + + if (remnant1 >= length2) + { + var countLength2 = (int)Math.Floor(remnant1 / length2); + var remnant2 = remnant1 - length2 * countLength2; + + if (!(remnant2 < remnant)) + continue; + + count1 = countLength1; + count2 = countLength2; + + if (remnant2.IsEqualTo(0)) + break; + + remnant = remnant2; + } + else + { + if (!(remnant1 < remnant)) + continue; + + count1 = countLength1; + count2 = 0; + + if (remnant1.IsEqualTo(0)) + break; + + remnant = remnant1; + } + } + + return true; + } + } +} diff --git a/Source/OpenNest.Engine/CirclePacking/Bin.cs b/Source/OpenNest.Engine/CirclePacking/Bin.cs new file mode 100644 index 0000000..742be22 --- /dev/null +++ b/Source/OpenNest.Engine/CirclePacking/Bin.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.CirclePacking +{ + internal class Bin : Box + { + public Bin() + { + Items = new List(); + } + + public List Items { get; set; } + + public double Density() + { + return Items.Sum(i => i.Area()) / Area(); + } + + public object Clone() + { + return new Bin + { + Location = this.Location, + Size = this.Size, + Items = new List(Items) + }; + } + } +} diff --git a/Source/OpenNest.Engine/CirclePacking/FillEndEven.cs b/Source/OpenNest.Engine/CirclePacking/FillEndEven.cs new file mode 100644 index 0000000..f4eac96 --- /dev/null +++ b/Source/OpenNest.Engine/CirclePacking/FillEndEven.cs @@ -0,0 +1,79 @@ +using System; + +namespace OpenNest.CirclePacking +{ + internal class FillEndEven : FillEngine + { + public FillEndEven(Bin bin) + : base(bin) + { + } + + public override void Fill(Item item) + { + var max = new Vector( + Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon, + Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon); + + var rows = Math.Floor((Bin.Height + Tolerance.Epsilon) / (item.Diameter)); + + var diameter = item.Diameter; + var remaining = Bin.Height - diameter * rows; + var radius = diameter * 0.5; + + if (remaining < radius) + { + var yodd = Bin.Y + remaining; + var yoffset = diameter; + var xoffset = Trigonometry.Base(remaining, diameter); + int column = 0; + + for (var x = Bin.X; x <= max.X; x += xoffset) + { + var y = column.IsOdd() ? yodd : Bin.Y; + + for (; y <= max.Y; y += yoffset) + { + Bin.Items.Add(new Item + { + Center = new Vector(x, y) + }); + } + + column++; + } + } + else + { + var yoffset = (Bin.Height - diameter) / (2 * rows - 1); + var xoffset = Trigonometry.Base(yoffset, diameter); + + var yodd = Bin.Y + yoffset; + + yoffset *= 2.0; + + int column = 0; + + for (var x = Bin.X; x <= max.X; x += xoffset) + { + var y = column.IsOdd() ? yodd : Bin.Y; + + for (; y <= max.Y; y += yoffset) + { + Bin.Items.Add(new Item + { + Center = new Vector(x, y) + }); + } + + column++; + } + } + } + + public override void Fill(Item item, int maxCount) + { + throw new NotImplementedException(); + } + } +} diff --git a/Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs b/Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs new file mode 100644 index 0000000..e661e83 --- /dev/null +++ b/Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs @@ -0,0 +1,100 @@ +using System; + +namespace OpenNest.CirclePacking +{ + internal class FillEndOdd : FillEngine + { + public FillEndOdd(Bin bin) + : base(bin) + { + } + + public override void Fill(Item item) + { + var bin1 = FillHorizontal(item); + var bin2 = FillVertical(item); + + if (bin1.Items.Count > bin2.Items.Count) + Bin.Items.AddRange(bin1.Items); + else + Bin.Items.AddRange(bin2.Items); + } + + public override void Fill(Item item, int maxCount) + { + throw new NotImplementedException(); + } + + private Bin FillHorizontal(Item item) + { + var bin = Bin.Clone() as Bin; + + var max = new Vector( + bin.Right - item.BoundingBox.Right + Tolerance.Epsilon, + bin.Top - item.BoundingBox.Top + Tolerance.Epsilon); + + var count = Math.Floor((bin.Width + Tolerance.Epsilon) / item.Diameter); + + if (count == 0) + return bin; + + var xoffset = (bin.Width - item.Diameter) / (count - 1); + var yoffset = Trigonometry.Height(xoffset * 0.5, item.Diameter); + + int row = 0; + + for (var y = bin.Y; y <= max.Y; y += yoffset) + { + var x = row.IsOdd() ? bin.X + xoffset * 0.5 : bin.X; + + for (; x <= max.X; x += xoffset) + { + var addedItem = item.Clone() as Item; + addedItem.Center = new Vector(x, y); + + bin.Items.Add(addedItem); + } + + row++; + } + + return bin; + } + + private Bin FillVertical(Item item) + { + var bin = Bin.Clone() as Bin; + + var max = new Vector( + Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon, + Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon); + + var count = Math.Floor((bin.Height + Tolerance.Epsilon) / item.Diameter); + + if (count == 0) + return bin; + + var yoffset = (bin.Height - item.Diameter) / (count - 1); + var xoffset = Trigonometry.Base(yoffset * 0.5, item.Diameter); + + int column = 0; + + for (var x = bin.X; x <= max.X; x += xoffset) + { + var y = column.IsOdd() ? bin.Y + yoffset * 0.5 : bin.Y; + + for (; y <= max.Y; y += yoffset) + { + var addedItem = item.Clone() as Item; + addedItem.Center = new Vector(x, y); + + bin.Items.Add(addedItem); + } + + column++; + } + + return bin; + } + } +} diff --git a/Source/OpenNest.Engine/CirclePacking/FillEngine.cs b/Source/OpenNest.Engine/CirclePacking/FillEngine.cs new file mode 100644 index 0000000..7fd9488 --- /dev/null +++ b/Source/OpenNest.Engine/CirclePacking/FillEngine.cs @@ -0,0 +1,17 @@ + +namespace OpenNest.CirclePacking +{ + internal abstract class FillEngine + { + public FillEngine(Bin bin) + { + Bin = bin; + } + + public Bin Bin { get; set; } + + public abstract void Fill(Item item); + + public abstract void Fill(Item item, int maxCount); + } +} diff --git a/Source/OpenNest.Engine/CirclePacking/Item.cs b/Source/OpenNest.Engine/CirclePacking/Item.cs new file mode 100644 index 0000000..4b3ad59 --- /dev/null +++ b/Source/OpenNest.Engine/CirclePacking/Item.cs @@ -0,0 +1,19 @@ +using OpenNest.Geometry; + +namespace OpenNest.CirclePacking +{ + internal class Item : Circle + { + public int Id { get; set; } + + public object Clone() + { + return new Item + { + Radius = this.Radius, + Center = this.Center, + Id = this.Id + }; + } + } +} diff --git a/Source/OpenNest.Engine/NestDirection.cs b/Source/OpenNest.Engine/NestDirection.cs new file mode 100644 index 0000000..9dc801d --- /dev/null +++ b/Source/OpenNest.Engine/NestDirection.cs @@ -0,0 +1,9 @@ + +namespace OpenNest +{ + public enum NestDirection + { + Vertical, + Horizontal + } +} diff --git a/Source/OpenNest.Engine/NestEngine.cs b/Source/OpenNest.Engine/NestEngine.cs new file mode 100644 index 0000000..20feb2c --- /dev/null +++ b/Source/OpenNest.Engine/NestEngine.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using OpenNest.RectanglePacking; + +namespace OpenNest +{ + public class NestEngine + { + public NestEngine(Plate plate) + { + Plate = plate; + } + + public Plate Plate { get; set; } + + public NestDirection NestDirection { get; set; } + + public bool Fill(NestItem item) + { + var workArea = Plate.WorkArea(); + return FillArea(workArea, item); + } + + public bool Fill(NestItem item, int maxCount) + { + var workArea = Plate.WorkArea(); + return FillArea(workArea, item, maxCount); + } + + public bool FillArea(Box box, NestItem item) + { + var binItem = ConvertToRectangleItem(item); + + var bin = new Bin + { + Location = box.Location, + Size = box.Size + }; + + bin.Width += Plate.PartSpacing; + bin.Height += Plate.PartSpacing; + + var engine = new FillBestFit(bin); + engine.Fill(binItem); + + var nestItems = new List(); + nestItems.Add(item); + + var parts = ConvertToParts(bin, nestItems); + Plate.Parts.AddRange(parts); + + return parts.Count > 0; + } + + public bool FillArea(Box box, NestItem item, int maxCount) + { + var binItem = ConvertToRectangleItem(item); + + var bin = new Bin + { + Location = box.Location, + Size = box.Size + }; + + bin.Width += Plate.PartSpacing; + bin.Height += Plate.PartSpacing; + + var engine = new FillBestFit(bin); + engine.Fill(binItem, maxCount); + + var nestItems = new List(); + nestItems.Add(item); + + var parts = ConvertToParts(bin, nestItems); + Plate.Parts.AddRange(parts); + + return parts.Count > 0; + } + + public bool Pack(List items) + { + var workArea = Plate.WorkArea(); + return PackArea(workArea, items); + } + + public bool PackArea(Box box, List items) + { + var binItems = ConvertToRectangleItems(items); + + var bin = new Bin + { + Location = box.Location, + Size = box.Size + }; + + bin.Width += Plate.PartSpacing; + bin.Height += Plate.PartSpacing; + + var engine = new PackBottomLeft(bin); + engine.Pack(binItems); + + var parts = ConvertToParts(bin, items); + Plate.Parts.AddRange(parts); + + return parts.Count > 0; + } + + private List ConvertToParts(Bin bin, List items) + { + var parts = new List(); + + foreach (var item in bin.Items) + { + var nestItem = items[item.Id]; + var part = ConvertToPart(item, nestItem.Drawing); + parts.Add(part); + } + + return parts; + } + + private Part ConvertToPart(Item item, Drawing dwg) + { + var part = new Part(dwg); + + if (item.IsRotated) + part.Rotate(Angle.HalfPI); + + var boundingBox = part.Program.BoundingBox(); + var offset = item.Location - boundingBox.Location; + + part.Offset(offset); + + return part; + } + + private List ConvertToRectangleItems(List items) + { + var binItems = new List(); + + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + var binItem = ConvertToRectangleItem(item, i); + + int maxQty = (int)Math.Floor(Plate.Area() / binItem.Area()); + + int qty = item.Quantity < maxQty + ? item.Quantity + : maxQty; + + for (int j = 0; j < qty; j++) + binItems.Add(binItem.Clone() as Item); + } + + return binItems; + } + + private Item ConvertToRectangleItem(NestItem item, int id = 0) + { + var box = item.Drawing.Program.BoundingBox(); + + box.Width += Plate.PartSpacing; + box.Height += Plate.PartSpacing; + + + + return new Item + { + Id = id, + Location = box.Location, + Size = box.Size + }; + } + } +} diff --git a/Source/OpenNest.Engine/NestItem.cs b/Source/OpenNest.Engine/NestItem.cs new file mode 100644 index 0000000..af7437c --- /dev/null +++ b/Source/OpenNest.Engine/NestItem.cs @@ -0,0 +1,35 @@ +namespace OpenNest +{ + public class NestItem + { + /// + /// The drawing to be nested. + /// + public Drawing Drawing { get; set; } + + /// + /// Priority of the part determines nesting order. Highest priority will be nested first. + /// + public int Priority { get; set; } + + /// + /// The number of parts to be nested. + /// + public int Quantity { get; set; } + + /// + /// The rotation step in radians. + /// + public double StepAngle { get; set; } + + /// + /// The rotation start angle in radians. + /// + public double RotationStart { get; set; } + + /// + /// The rotation end angle in radians. + /// + public double RotationEnd { get; set; } + } +} diff --git a/Source/OpenNest.Engine/OpenNest.Engine.csproj b/Source/OpenNest.Engine/OpenNest.Engine.csproj new file mode 100644 index 0000000..a48fc03 --- /dev/null +++ b/Source/OpenNest.Engine/OpenNest.Engine.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {0083B9CC-54AD-4085-A30D-56BC6834B71A} + Library + Properties + OpenNest + OpenNest.Engine + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + {5a5fde8d-f8db-440e-866c-c4807e1686cf} + OpenNest.Core + + + + + \ No newline at end of file diff --git a/Source/OpenNest.Engine/Properties/AssemblyInfo.cs b/Source/OpenNest.Engine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..77b2e6f --- /dev/null +++ b/Source/OpenNest.Engine/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenNest.Engine")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OpenNest.Engine")] +[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("50013ca9-b047-41f5-b519-72e523902b53")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/Source/OpenNest.Engine/RectanglePacking/Bin.cs b/Source/OpenNest.Engine/RectanglePacking/Bin.cs new file mode 100644 index 0000000..65dfc4d --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/Bin.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.RectanglePacking +{ + internal class Bin : Box + { + public Bin() + { + Items = new List(); + } + + public List Items { get; set; } + + public double Density() + { + return Items.Sum(i => i.Area()) / Area(); + } + + public object Clone() + { + return new Bin + { + Location = this.Location, + Size = this.Size, + Items = new List(Items) + }; + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs b/Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs new file mode 100644 index 0000000..63ddeeb --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs @@ -0,0 +1,88 @@ +using System; + +namespace OpenNest.RectanglePacking +{ + internal class FillBestFit : FillEngine + { + public FillBestFit(Bin bin) + : base(bin) + { + } + + public override void Fill(Item item) + { + var bin1 = BestFitHorizontal(item); + var bin2 = BestFitVertical(item); + + if (bin1.Items.Count == bin2.Items.Count) + { + var usedArea1 = bin1.Items.GetBoundingBox().Area(); + var usedArea2 = bin2.Items.GetBoundingBox().Area(); + + if (usedArea2 < usedArea1) + Bin.Items.AddRange(bin2.Items); + else + Bin.Items.AddRange(bin1.Items); + } + else if (bin1.Items.Count > bin2.Items.Count) + Bin.Items.AddRange(bin1.Items); + else + Bin.Items.AddRange(bin2.Items); + } + + public override void Fill(Item item, int maxCount) + { + throw new NotImplementedException(); + } + + private Bin BestFitHorizontal(Item item) + { + var bin = Bin.Clone() as Bin; + + int normalColumns = 0; + int rotateColumns = 0; + + if (!BestCombination.FindFrom2(item.Width, item.Height, bin.Width, out normalColumns, out rotateColumns)) + return bin; + + var normalRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Height); + var rotateRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Width); + + item.Location = bin.Location; + + bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue)); + + item.Location.X += item.Width * normalColumns; + item.Rotate(); + + bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue)); + + return bin; + } + + private Bin BestFitVertical(Item item) + { + var bin = Bin.Clone() as Bin; + + int normalRows = 0; + int rotateRows = 0; + + if (!BestCombination.FindFrom2(item.Height, item.Width, Bin.Height, out normalRows, out rotateRows)) + return bin; + + var normalColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width); + var rotateColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Height); + + item.Location = bin.Location; + + bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue)); + + item.Location.Y += item.Height * normalRows; + item.Rotate(); + + bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue)); + + return bin; + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/FillEngine.cs b/Source/OpenNest.Engine/RectanglePacking/FillEngine.cs new file mode 100644 index 0000000..d071e31 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/FillEngine.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; + +namespace OpenNest.RectanglePacking +{ + internal abstract class FillEngine + { + public FillEngine(Bin bin) + { + Bin = bin; + } + + public Bin Bin { get; set; } + + public abstract void Fill(Item item); + + public abstract void Fill(Item item, int maxCount); + + /// + /// Vertical pattern. + /// + /// + /// + /// + /// + protected List VPattern(Item item, int rows, int columns, int maxCount) + { + var items = new List(); + + for (int i = 0; i < columns; i++) + { + var x = item.Width * i + item.X; + + for (int j = 0; j < rows; j++) + { + var y = item.Height * j + item.Y; + + var addedItem = item.Clone() as Item; + addedItem.Location = new Vector(x, y); + + items.Add(addedItem); + + if (items.Count == maxCount) + return items; + } + } + + return items; + } + + /// + /// Horizontal pattern. + /// + /// + /// + /// + /// + protected List HPattern(Item item, int rows, int columns, int maxCount) + { + var items = new List(); + + for (int i = 0; i < rows; i++) + { + var y = item.Height * i + item.Y; + + for (int j = 0; j < rows; j++) + { + var x = item.Width * j + item.X; + + var addedItem = item.Clone() as Item; + addedItem.Location = new Vector(x, y); + + items.Add(addedItem); + + if (items.Count == maxCount) + return items; + } + } + + return items; + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs b/Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs new file mode 100644 index 0000000..71470b9 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs @@ -0,0 +1,68 @@ +using System; + +namespace OpenNest.RectanglePacking +{ + internal class FillNoRotation : FillEngine + { + public FillNoRotation(Bin bin) + : base(bin) + { + } + + public NestDirection NestDirection { get; set; } + + public override void Fill(Item item) + { + var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height); + var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width); + var count = ycount * xcount; + + for (int i = 0; i < xcount; i++) + { + var x = item.Width * i + Bin.X; + + for (int j = 0; j < ycount; j++) + { + var y = item.Height * j + Bin.Y; + + var addedItem = item.Clone() as Item; + addedItem.Location = new Vector(x, y); + + Bin.Items.Add(addedItem); + } + } + } + + public override void Fill(Item item, int maxCount) + { + var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height); + var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width); + var count = ycount * xcount; + + if (count <= maxCount) + { + Fill(item); + return; + } + + var columns = 0; + var rows = 0; + + if (NestDirection == NestDirection.Vertical) + { + columns = (int)Math.Ceiling((double)maxCount / ycount); + rows = (int)Math.Ceiling((double)maxCount / columns); + } + else + { + rows = (int)Math.Ceiling((double)maxCount / xcount); + columns = (int)Math.Ceiling((double)maxCount / rows); + } + + if (item.Width > item.Height) + VPattern(item, rows, columns, maxCount); + else + HPattern(item, rows, columns, maxCount); + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs b/Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs new file mode 100644 index 0000000..9ba17d2 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs @@ -0,0 +1,65 @@ + +namespace OpenNest.RectanglePacking +{ + internal class FillSameRotation : FillEngine + { + public FillSameRotation(Bin bin) + : base(bin) + { + } + + public override void Fill(Item item) + { + var bin1 = Bin.Clone() as Bin; + var bin2 = Bin.Clone() as Bin; + + var engine = new FillNoRotation(bin1); + engine.Fill(item); + + item.Rotate(); + + engine.Bin = bin2; + engine.Fill(item); + + var density1 = bin1.Density(); + var density2 = bin2.Density(); + + if (density1.IsEqualTo(density2)) + { + var bounds1 = bin1.Items.GetBoundingBox(); + var bounds2 = bin2.Items.GetBoundingBox(); + + if (bounds2.Right < bounds1.Right) + Bin.Items.AddRange(bin2.Items); + else + Bin.Items.AddRange(bin1.Items); + } + else if (density1 > density2) + Bin.Items.AddRange(bin1.Items); + else + Bin.Items.AddRange(bin2.Items); + } + + public override void Fill(Item item, int maxCount) + { + var bin1 = Bin.Clone() as Bin; + var bin2 = Bin.Clone() as Bin; + + var engine = new FillNoRotation(bin1); + engine.Fill(item, maxCount); + + item.Rotate(); + + engine.Bin = bin2; + engine.Fill(item, maxCount); + + var bounds1 = bin1.Items.GetBoundingBox(); + var bounds2 = bin2.Items.GetBoundingBox(); + + if (bounds2.Right < bounds1.Right) + Bin.Items.AddRange(bin2.Items); + else + Bin.Items.AddRange(bin1.Items); + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/Item.cs b/Source/OpenNest.Engine/RectanglePacking/Item.cs new file mode 100644 index 0000000..21d7058 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/Item.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +namespace OpenNest.RectanglePacking +{ + internal class Item : Box + { + public int Id { get; set; } + + public bool IsRotated { get; private set; } + + public void Rotate() + { + Generic.Swap(ref Size.Width, ref Size.Height); + IsRotated = !IsRotated; + } + + public object Clone() + { + return new Item + { + IsRotated = this.IsRotated, + Location = this.Location, + Size = this.Size, + Id = this.Id + }; + } + } + + internal static class ItemListExtensions + { + public static Box GetBoundingBox(this IList items) + { + if (items.Count == 0) + return Box.Empty; + + double minX = items[0].X; + double minY = items[0].Y; + double maxX = items[0].X + items[0].Width; + double maxY = items[0].Y + items[0].Height; + + foreach (var box in items) + { + if (box.Left < minX) minX = box.Left; + if (box.Right > maxX) maxX = box.Right; + if (box.Bottom < minY) minY = box.Bottom; + if (box.Top > maxY) maxY = box.Top; + } + + return new Box(minX, minY, maxX - minX, maxY - minY); + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs b/Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs new file mode 100644 index 0000000..8ca5268 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.RectanglePacking +{ + internal class PackBottomLeft : PackEngine + { + private List points; + + public PackBottomLeft(Bin bin) + : base(bin) + { + points = new List(); + } + + public override void Pack(List items) + { + items = items.OrderBy(i => -i.Area()).ToList(); + + points.Add(Bin.Location); + + var skip = new List(); + + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + + if (skip.Contains(item.Id)) + continue; + + var pt = FindPointVertical(item); + + if (pt == null) + { + skip.Add(item.Id); + continue; + } + + item.Location = pt.Value; + + points.Remove(pt.Value); + points.Add(new Vector(item.Left, item.Top)); + points.Add(new Vector(item.Right, item.Bottom)); + + Bin.Items.Add(item); + } + + points.Clear(); + } + + private Vector? FindPointVertical(Item item) + { + var pt = new Vector(double.MaxValue, double.MaxValue); + + for (int i = 0; i < points.Count; i++) + { + var point = points[i]; + + item.Location = point; + + if (!IsValid(item)) + continue; + + if (point.X < pt.X) + pt = point; + else if (point.X.IsEqualTo(pt.X) && point.Y < pt.Y) + pt = point; + } + + if (pt.X != double.MaxValue && pt.Y != double.MaxValue) + return pt; + + return null; + } + + private Vector? FindPointHorizontal(Item item) + { + var pt = new Vector(double.MaxValue, double.MaxValue); + + for (int i = 0; i < points.Count; i++) + { + var point = points[i]; + + item.Location = point; + + if (!IsValid(item)) + continue; + + if (point.Y < pt.Y) + pt = point; + else if (point.Y.IsEqualTo(pt.Y) && point.X < pt.X) + pt = point; + } + + if (pt.X != double.MaxValue && pt.Y != double.MaxValue) + return pt; + + return null; + } + + private bool IsValid(Item item) + { + if (!Bin.Contains(item)) + return false; + + foreach (var it in Bin.Items) + { + if (item.Intersects(it)) + return false; + } + + return true; + } + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/PackEngine.cs b/Source/OpenNest.Engine/RectanglePacking/PackEngine.cs new file mode 100644 index 0000000..9c4f7b3 --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/PackEngine.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace OpenNest.RectanglePacking +{ + internal abstract class PackEngine + { + public PackEngine(Bin bin) + { + Bin = bin; + } + + public Bin Bin { get; set; } + + public abstract void Pack(List items); + } +} diff --git a/Source/OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs b/Source/OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs new file mode 100644 index 0000000..dc7678e --- /dev/null +++ b/Source/OpenNest.Engine/RectanglePacking/PackFirstFitDecreasing.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenNest.RectanglePacking +{ + internal class FirstFitDecreasing : PackEngine + { + private readonly List levels; + + public FirstFitDecreasing(Bin bin) + : base(bin) + { + levels = new List(); + } + + public override void Pack(List items) + { + items = items.OrderBy(i => -i.Height).ToList(); + + foreach (var item in items) + { + if (item.Height > Bin.Height) + continue; + + var level = FindLevel(item); + + if (level == null) + continue; + + level.AddItem(item); + } + } + + private Level FindLevel(Item item) + { + foreach (var level in levels) + { + if (level.Height < item.Height) + continue; + + if (level.RemainingWidth < item.Width) + continue; + + return level; + } + + return CreateNewLevel(item); + } + + private Level CreateNewLevel(Item item) + { + var y = Bin.Y; + var lastLevel = levels.LastOrDefault(); + + if (lastLevel != null) + y = lastLevel.Y + lastLevel.Height; + + var remaining = Bin.Top - y; + + if (remaining < item.Height) + return null; + + var level = new Level(Bin); + level.Y = y; + level.Height = item.Height; + + levels.Add(level); + + return level; + } + + private class Level + { + public Level(Bin parent) + { + Parent = parent; + NextItemLocation = parent.Location; + } + + public Bin Parent { get; set; } + + private Vector NextItemLocation; + + public double X + { + get { return Parent.X; } + } + + public double Y + { + get { return NextItemLocation.Y; } + set { NextItemLocation.Y = value; } + } + + public double Width + { + get { return Parent.Width; } + } + + public double Height { get; set; } + + public double Top + { + get { return Y + Height; } + } + + public double RemainingWidth + { + get { return X + Width - NextItemLocation.X; } + } + + public void AddItem(Item item) + { + item.Location = NextItemLocation; + Parent.Items.Add(item); + + NextItemLocation = new Vector(NextItemLocation.X + item.Width, NextItemLocation.Y); + } + } + } +} diff --git a/Source/OpenNest.sln b/Source/OpenNest.sln new file mode 100644 index 0000000..c0e658e --- /dev/null +++ b/Source/OpenNest.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest", "OpenNest\OpenNest.csproj", "{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Core", "OpenNest.Core\OpenNest.Core.csproj", "{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Engine", "OpenNest.Engine\OpenNest.Engine.csproj", "{0083B9CC-54AD-4085-A30D-56BC6834B71A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.Build.0 = Release|Any CPU + {5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.Build.0 = Release|Any CPU + {0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/OpenNest/Actions/Action.cs b/Source/OpenNest/Actions/Action.cs new file mode 100644 index 0000000..2df37da --- /dev/null +++ b/Source/OpenNest/Actions/Action.cs @@ -0,0 +1,20 @@ +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + public abstract class Action + { + protected PlateView plateView; + + protected Action(PlateView plateView) + { + this.plateView = plateView; + } + + public abstract void DisconnectEvents(); + + public abstract void CancelAction(); + + public abstract bool IsBusy(); + } +} diff --git a/Source/OpenNest/Actions/ActionAddPart.cs b/Source/OpenNest/Actions/ActionAddPart.cs new file mode 100644 index 0000000..40034c5 --- /dev/null +++ b/Source/OpenNest/Actions/ActionAddPart.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Add Parts")] + public class ActionAddPart : Action + { + private LayoutPart part; + private double lastScale; + + public ActionAddPart(PlateView plateView) + : this(plateView, null) + { + } + + public ActionAddPart(PlateView plateView, Drawing drawing) + : base(plateView) + { + plateView.KeyDown += plateView_KeyDown; + plateView.MouseMove += plateView_MouseMove; + plateView.MouseDown += plateView_MouseDown; + plateView.Paint += plateView_Paint; + + part = LayoutPart.Create(new Part(drawing), plateView); + part.IsSelected = true; + + lastScale = double.NaN; + + plateView.SelectedParts.Clear(); + plateView.SelectedParts.Add(part); + } + + private void plateView_MouseDown(object sender, MouseEventArgs e) + { + switch (e.Button) + { + case MouseButtons.Left: + Apply(); + break; + } + } + + private void plateView_KeyDown(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.F1: + case Keys.Enter: + Apply(); + break; + + case Keys.F: + if ((Control.ModifierKeys & Keys.Control) == Keys.Control) + Fill(); + break; + } + } + + private void plateView_Paint(object sender, PaintEventArgs e) + { + if (plateView.ViewScale != lastScale) + { + part.Update(plateView); + part.Draw(e.Graphics); + } + else + { + if (part.IsDirty) + part.Update(plateView); + + part.Draw(e.Graphics); + } + + lastScale = plateView.ViewScale; + } + + private void plateView_MouseMove(object sender, MouseEventArgs e) + { + var offset = plateView.CurrentPoint - part.BoundingBox.Location; + part.Offset(offset); + plateView.Invalidate(); + } + + public override void DisconnectEvents() + { + plateView.KeyDown -= plateView_KeyDown; + plateView.MouseMove -= plateView_MouseMove; + plateView.MouseDown -= plateView_MouseDown; + plateView.Paint -= plateView_Paint; + + plateView.SelectedParts.Clear(); + plateView.Invalidate(); + } + + public override void CancelAction() + { + } + + public override bool IsBusy() + { + return false; + } + + private void Fill() + { + var boxes = new List(); + + foreach (var part in plateView.Plate.Parts) + boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing)); + + var bounds = plateView.Plate.WorkArea(); + + var vbox = Helper.GetLargestBoxVertically(plateView.CurrentPoint, bounds, boxes); + var hbox = Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, bounds, boxes); + + var box = vbox.Area() > hbox.Area() ? vbox : hbox; + + var engine = new NestEngine(plateView.Plate); + engine.FillArea(box, new NestItem { Drawing = this.part.BasePart.BaseDrawing }); + } + + private void Apply() + { + if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) + { + plateView.PushSelected(PushDirection.Left); + plateView.PushSelected(PushDirection.Down); + } + + plateView.Plate.Parts.Add(part.BasePart.Clone() as Part); + } + } +} diff --git a/Source/OpenNest/Actions/ActionClone.cs b/Source/OpenNest/Actions/ActionClone.cs new file mode 100644 index 0000000..7cc73ed --- /dev/null +++ b/Source/OpenNest/Actions/ActionClone.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Clone Parts")] + public class ActionClone : Action + { + private readonly List parts; + + private double lastScale; + + public ActionClone(PlateView plateView, List partsToClone) + : base(plateView) + { + plateView.KeyDown += plateView_KeyDown; + plateView.MouseMove += plateView_MouseMove; + plateView.MouseDown += plateView_MouseDown; + plateView.Paint += plateView_Paint; + + parts = new List(); + lastScale = double.NaN; + + for (int i = 0; i < partsToClone.Count; i++) + { + var part = LayoutPart.Create(partsToClone[i].Clone() as Part, plateView); + part.IsSelected = true; + parts.Add(part); + } + + plateView.SelectedParts.Clear(); + plateView.SelectedParts.AddRange(parts); + } + + private void plateView_MouseDown(object sender, MouseEventArgs e) + { + switch (e.Button) + { + case MouseButtons.Left: + Apply(); + break; + } + } + + private void plateView_KeyDown(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.F1: + case Keys.Enter: + Apply(); + break; + } + } + + private void plateView_Paint(object sender, PaintEventArgs e) + { + if (plateView.ViewScale != lastScale) + { + parts.ForEach(p => + { + p.Update(plateView); + p.Draw(e.Graphics); + }); + } + else + { + parts.ForEach(p => + { + if (p.IsDirty) + p.Update(plateView); + + p.Draw(e.Graphics); + }); + } + + lastScale = plateView.ViewScale; + } + + private void plateView_MouseMove(object sender, MouseEventArgs e) + { + var offset = plateView.CurrentPoint - parts.GetBoundingBox().Location; + parts.ForEach(p => p.Offset(offset)); + plateView.Invalidate(); + } + + public override void DisconnectEvents() + { + plateView.KeyDown -= plateView_KeyDown; + plateView.MouseMove -= plateView_MouseMove; + plateView.MouseDown -= plateView_MouseDown; + plateView.Paint -= plateView_Paint; + + plateView.SelectedParts.Clear(); + plateView.Invalidate(); + } + + public override void CancelAction() + { + } + + public override bool IsBusy() + { + return false; + } + + public void Apply() + { + if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) + { + plateView.PushSelected(PushDirection.Left); + plateView.PushSelected(PushDirection.Down); + } + + parts.ForEach(p => plateView.Plate.Parts.Add(p.BasePart.Clone() as Part)); + } + } +} diff --git a/Source/OpenNest/Actions/ActionFillArea.cs b/Source/OpenNest/Actions/ActionFillArea.cs new file mode 100644 index 0000000..ba64af2 --- /dev/null +++ b/Source/OpenNest/Actions/ActionFillArea.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Fill Area")] + public class ActionFillArea : ActionSelectArea + { + private Drawing drawing; + + public ActionFillArea(PlateView plateView, Drawing drawing) + : base(plateView) + { + plateView.PreviewKeyDown += plateView_PreviewKeyDown; + this.drawing = drawing; + } + + private void plateView_PreviewKeyDown(object sender, System.Windows.Forms.PreviewKeyDownEventArgs e) + { + if (e.KeyCode == Keys.Enter) + FillArea(); + } + + private void FillArea() + { + var engine = new NestEngine(plateView.Plate); + engine.FillArea(SelectedArea, new NestItem + { + Drawing = drawing + }); + + plateView.Invalidate(); + Update(); + } + + public override void DisconnectEvents() + { + plateView.PreviewKeyDown -= plateView_PreviewKeyDown; + base.DisconnectEvents(); + } + } +} diff --git a/Source/OpenNest/Actions/ActionSelect.cs b/Source/OpenNest/Actions/ActionSelect.cs new file mode 100644 index 0000000..3ffb488 --- /dev/null +++ b/Source/OpenNest/Actions/ActionSelect.cs @@ -0,0 +1,252 @@ +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Select")] + public class ActionSelect : Action + { + private readonly Pen borderPenContain; + private readonly Pen borderPenIntersect; + private readonly Brush fillBrushContain; + private readonly Brush fillBrushIntersect; + + private Pen borderPen; + private Brush fillBrush; + + public Vector Point1; + public Vector Point2; + + private Status status; + + public ActionSelect(PlateView plateView) + : base(plateView) + { + borderPenIntersect = new Pen(Color.FromArgb(50, 255, 50)); + fillBrushIntersect = new SolidBrush(Color.FromArgb(50, 50, 255, 50)); + + borderPenContain = new Pen(Color.FromArgb(99, 162, 228)); + fillBrushContain = new SolidBrush(Color.FromArgb(90, 150, 200, 255)); + + UpdateBrushAndPen(); + + status = Status.SetFirstPoint; + + plateView.MouseDown += plateView_MouseDown; + plateView.MouseUp += plateView_MouseUp; + plateView.MouseMove += plateView_MouseMove; + plateView.Paint += plateView_Paint; + } + + public override void DisconnectEvents() + { + plateView.MouseDown -= plateView_MouseDown; + plateView.MouseUp -= plateView_MouseUp; + plateView.MouseMove -= plateView_MouseMove; + plateView.Paint -= plateView_Paint; + } + + public override void CancelAction() + { + status = Status.SetFirstPoint; + + plateView.DeselectAll(); + plateView.Invalidate(); + } + + public override bool IsBusy() + { + return status != Status.SetFirstPoint || plateView.SelectedParts.Count > 0; + } + + private void plateView_MouseMove(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + return; + + if (status == Status.SetSecondPoint) + { + Point2 = plateView.PointControlToWorld(e.Location); + UpdateBrushAndPen(); + } + else + { + if (plateView.SelectedParts.Count == 0) + return; + + var offset = plateView.CurrentPoint - plateView.LastPoint; + + foreach (var part in plateView.SelectedParts) + part.Offset(offset.X, offset.Y); + } + + plateView.Invalidate(); + } + + private void plateView_MouseDown(object sender, MouseEventArgs e) + { + if (!plateView.AllowSelect) + return; + + if (e.Button == MouseButtons.Left) + { + if (plateView.SelectedParts.Count > 0) + { + var part = plateView.GetPartAtControlPoint(e.Location); + + if (part == null) + CancelAction(); + } + + if (SelectPartAtCurrentPoint()) + return; + + if (status == Status.SetFirstPoint) + { + Point1 = plateView.PointControlToWorld(e.Location); + Point2 = Point1; + status = Status.SetSecondPoint; + } + } + else if (e.Button == MouseButtons.Right) + { + CancelAction(); + } + } + + private void plateView_MouseUp(object sender, MouseEventArgs e) + { + if (!plateView.AllowSelect) + return; + + if (e.Button != MouseButtons.Left) + return; + + if (status == Status.SetSecondPoint) + { + Point2 = plateView.PointControlToWorld(e.Location); + SelectPartsInWindow(); + + plateView.Invalidate(); + status = Status.SetFirstPoint; + } + } + + private void plateView_Paint(object sender, PaintEventArgs e) + { + if (status != Status.SetSecondPoint) + return; + + var rect = GetRectangle(); + + e.Graphics.FillRectangle(fillBrush, rect); + + e.Graphics.DrawRectangle(borderPen, + rect.X, + rect.Y, + rect.Width, + rect.Height); + } + + private bool SelectPartAtCurrentPoint() + { + var part = plateView.GetPartAtPoint(plateView.CurrentPoint); + + if (part == null) + { + plateView.DeselectAll(); + status = Status.SetFirstPoint; + return false; + } + + if (Control.ModifierKeys != Keys.Control && plateView.SelectedParts.Contains(part) == false) + plateView.DeselectAll(); + + if (plateView.SelectedParts.Contains(part) == false) + { + plateView.SelectedParts.Add(part); + part.IsSelected = true; + } + + plateView.Invalidate(); + return true; + } + + private void SelectPartsInWindow() + { + var parts = plateView.GetPartsFromWindow(GetRectangle(), SelectionType); + + foreach (var part in parts) + { + if (plateView.SelectedParts.Contains(part)) + continue; + + plateView.SelectedParts.Add(part); + part.IsSelected = true; + } + } + + private void UpdateBrushAndPen() + { + if (SelectionType == SelectionType.Intersect) + { + borderPen = borderPenIntersect; + fillBrush = fillBrushIntersect; + } + else + { + borderPen = borderPenContain; + fillBrush = fillBrushContain; + } + } + + private RectangleF GetRectangle() + { + var rect = new RectangleF(); + var pt1 = plateView.PointWorldToGraph(Point1); + var pt2 = plateView.PointWorldToGraph(Point2); + + if (pt1.X < pt2.X) + { + rect.X = pt1.X; + rect.Width = pt2.X - pt1.X; + } + else + { + rect.X = pt2.X; + rect.Width = pt1.X - pt2.X; + } + + if (pt1.Y < pt2.Y) + { + rect.Y = pt1.Y; + rect.Height = pt2.Y - pt1.Y; + } + else + { + rect.Y = pt2.Y; + rect.Height = pt1.Y - pt2.Y; + } + + return rect; + } + + public SelectionType SelectionType + { + get + { + return Point1.X < Point2.X + ? SelectionType.Contains + : SelectionType.Intersect; + } + } + + public enum Status + { + SetFirstPoint, + SetSecondPoint + } + } +} diff --git a/Source/OpenNest/Actions/ActionSelectArea.cs b/Source/OpenNest/Actions/ActionSelectArea.cs new file mode 100644 index 0000000..692a624 --- /dev/null +++ b/Source/OpenNest/Actions/ActionSelectArea.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Select Area")] + public class ActionSelectArea : Action + { + public Box SelectedArea { get; private set; } + private Box Bounds { get; set; } + private List boxes { get; set; } + + private bool dynamicSelect; + private bool altSelect; + + private readonly Pen pen; + private readonly Brush brush; + + private readonly Font font; + private readonly StringFormat stringFormat; + + public ActionSelectArea(PlateView plateView) + : base(plateView) + { + boxes = new List(); + pen = new Pen(Color.FromArgb(50, 255, 50)); + brush = new SolidBrush(Color.FromArgb(50, 50, 255, 50)); + font = new Font(SystemFonts.DefaultFont.FontFamily, 14); + + stringFormat = new StringFormat + { + Alignment = StringAlignment.Center, + LineAlignment = StringAlignment.Center + }; + + SelectedArea = Box.Empty; + dynamicSelect = true; + + Update(); + + if (dynamicSelect) + plateView.MouseMove += plateView_MouseMove; + else + plateView.MouseClick += plateView_MouseClick; + + plateView.KeyUp += plateView_KeyUp; + plateView.Paint += plateView_Paint; + } + + private void plateView_KeyUp(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Space: + altSelect = !altSelect; + UpdateSelectedArea(); + break; + } + } + + public bool DynamicSelect + { + get { return dynamicSelect; } + set + { + if (value == dynamicSelect) + return; + + dynamicSelect = value; + + if (dynamicSelect) + { + plateView.MouseMove += plateView_MouseMove; + plateView.MouseClick -= plateView_MouseClick; + } + else + { + plateView.MouseMove -= plateView_MouseMove; + plateView.MouseClick += plateView_MouseClick; + } + } + } + + private void plateView_Paint(object sender, System.Windows.Forms.PaintEventArgs e) + { + if (SelectedArea == Box.Empty) + return; + + var location = plateView.PointWorldToGraph(SelectedArea.Location); + var size = new SizeF( + plateView.LengthWorldToGui(SelectedArea.Width), + plateView.LengthWorldToGui(SelectedArea.Height)); + + var rect = new System.Drawing.RectangleF(location.X, location.Y - size.Height, size.Width, size.Height); + + e.Graphics.DrawRectangle(pen, + rect.X, + rect.Y, + rect.Width, + rect.Height); + + e.Graphics.FillRectangle(brush, rect); + + e.Graphics.DrawString( + SelectedArea.Size.ToString(2), + font, + Brushes.Green, + rect, + stringFormat); + } + + private void plateView_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) + { + UpdateSelectedArea(); + } + + private void plateView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) + { + if (e.Button != System.Windows.Forms.MouseButtons.Left) + return; + + UpdateSelectedArea(); + plateView.Invalidate(); + } + + public override void DisconnectEvents() + { + plateView.KeyUp -= plateView_KeyUp; + plateView.MouseMove -= plateView_MouseMove; + plateView.MouseClick -= plateView_MouseClick; + plateView.Paint -= plateView_Paint; + } + + public override void CancelAction() + { + plateView.Invalidate(); + } + + public override bool IsBusy() + { + return false; + } + + private void UpdateSelectedArea() + { + SelectedArea = altSelect + ? Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes) + : Helper.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes); + + plateView.Invalidate(); + } + + public void Update() + { + foreach (var part in plateView.Plate.Parts) + boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing)); + + Bounds = plateView.Plate.WorkArea(); + } + } +} diff --git a/Source/OpenNest/Actions/ActionSetSequence.cs b/Source/OpenNest/Actions/ActionSetSequence.cs new file mode 100644 index 0000000..7dfb746 --- /dev/null +++ b/Source/OpenNest/Actions/ActionSetSequence.cs @@ -0,0 +1,155 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using OpenNest.Controls; +using OpenNest.Forms; +using OpenNest.Geometry; + +namespace OpenNest.Actions +{ + [DisplayName("Set Sequence")] + public class ActionSetSequence : Action + { + private readonly SequenceForm SequenceForm; + private readonly List ShapePartPairs; + private readonly Pen pen; + + private Shape ClosestShape; + private Part ClosestPart; + private int sequenceNumber = 1; + + public int SequenceNumber + { + get { return sequenceNumber; } + set + { + if (value <= SequenceForm.numericUpDown1.Maximum && value >= SequenceForm.numericUpDown1.Minimum) + { + sequenceNumber = value; + SequenceForm.numericUpDown1.Value = sequenceNumber; + } + } + } + + public ActionSetSequence(PlateView plateView) + : base(plateView) + { + SequenceForm = new Forms.SequenceForm(); + SequenceForm.numericUpDown1.DataBindings.Add("Value", this, "SequenceNumber", false, DataSourceUpdateMode.OnPropertyChanged); + SequenceForm.numericUpDown1.Maximum = plateView.Plate.Parts.Count; + SequenceForm.Owner = Application.OpenForms[0]; + SequenceForm.Show(); + plateView.Invalidate(); + + pen = new Pen(Color.Blue, 2.0f); + + ShapePartPairs = new List(); + + foreach (var part in plateView.Plate.Parts) + { + var entities = ConvertProgram.ToGeometry(part.Program).Where(e => e.Layer == SpecialLayers.Cut).ToList(); + entities.ForEach(entity => entity.Offset(part.Location)); + var shapes = Helper.GetShapes(entities); + var shape = new Shape(); + shape.Entities.AddRange(shapes); + ShapePartPairs.Add(new Pair() { Part = part, Shape = shape }); + } + + plateView.MouseMove += plateView_MouseMove; + plateView.MouseClick += plateView_MouseClick; + plateView.Paint += plateView_Paint; + + SequenceNumber = 1; + } + + private void plateView_MouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + CancelAction(); + + if (e.Button != MouseButtons.Left) + return; + + if (SequenceNumber > plateView.Plate.Parts.Count) + SequenceNumber = plateView.Plate.Parts.Count; + + if (ClosestPart == null) + return; + + plateView.Plate.Parts.Remove(ClosestPart); + plateView.Plate.Parts.Insert(SequenceNumber - 1, ClosestPart); + + SequenceNumber++; + + plateView.Invalidate(); + } + + private void plateView_MouseMove(object sender, MouseEventArgs e) + { + var pair = GetClosestShape(); + ClosestShape = pair.HasValue ? pair.Value.Shape : null; + ClosestPart = pair.HasValue ? pair.Value.Part : null; + plateView.Invalidate(); + } + + private void plateView_Paint(object sender, PaintEventArgs e) + { + if (ClosestShape == null) return; + + var path = ClosestShape.GetGraphicsPath(); + path.Transform(plateView.Matrix); + e.Graphics.DrawPath(pen, path); + path.Dispose(); + } + + private Pair? GetClosestShape() + { + Pair? closestShape = null; + double distance = double.MaxValue; + + for (int i = 0; i < ShapePartPairs.Count; i++) + { + var shape = ShapePartPairs[i]; + var closestPt = shape.Shape.ClosestPointTo(plateView.CurrentPoint); + var distance2 = closestPt.DistanceTo(plateView.CurrentPoint); + + if (distance2 < distance) + { + closestShape = shape; + distance = distance2; + } + } + + return closestShape; + } + + public override void DisconnectEvents() + { + plateView.Paint -= plateView_Paint; + plateView.MouseMove -= plateView_MouseMove; + plateView.MouseClick -= plateView_MouseClick; + plateView.Invalidate(); + + SequenceForm.Close(); + } + + public override void CancelAction() + { + SequenceNumber = 1; + SequenceForm.numericUpDown1.Value = SequenceNumber + 1; + } + + public override bool IsBusy() + { + return false; + } + + private struct Pair + { + public Part Part; + public Shape Shape; + } + } +} diff --git a/Source/OpenNest/Actions/ActionZoomWindow.cs b/Source/OpenNest/Actions/ActionZoomWindow.cs new file mode 100644 index 0000000..ec2032d --- /dev/null +++ b/Source/OpenNest/Actions/ActionZoomWindow.cs @@ -0,0 +1,205 @@ +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest.Actions +{ + [DisplayName("Zoom Window")] + public class ActionZoomWindow : Action + { + public Vector Point1; + public Vector Point2; + + private readonly Pen borderPen; + private readonly Brush fillBrush; + + private Status status; + + public ActionZoomWindow(PlateView plateView) + : base(plateView) + { + borderPen = new Pen(Color.FromArgb(99, 162, 228)); + fillBrush = new SolidBrush(Color.FromArgb(90, 150, 200, 255)); + + status = Status.SetFirstPoint; + + plateView.Cursor = Cursors.Arrow; + plateView.MouseDown += plateView_MouseDown; + plateView.MouseUp += plateView_MouseUp; + plateView.MouseMove += plateView_MouseMove; + plateView.Paint += plateView_Paint; + } + + public override void DisconnectEvents() + { + plateView.Cursor = Cursors.Cross; + plateView.MouseDown -= plateView_MouseDown; + plateView.MouseUp -= plateView_MouseUp; + plateView.MouseMove -= plateView_MouseMove; + plateView.Paint -= plateView_Paint; + } + + public override void CancelAction() + { + Point1 = Vector.Invalid; + Point2 = Vector.Invalid; + + plateView.Invalidate(); + status = Status.SetFirstPoint; + } + + public override bool IsBusy() + { + return status != Status.SetFirstPoint; + } + + private void plateView_MouseMove(object sender, MouseEventArgs e) + { + if (status == Status.SetSecondPoint || e.Button == MouseButtons.Left) + { + Point2 = plateView.PointControlToWorld(e.Location); + plateView.Invalidate(); + } + } + + private void plateView_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + CancelAction(); + return; + } + + if (e.Button != MouseButtons.Left) + return; + + if (e.Button == MouseButtons.Left && status == Status.SetFirstPoint) + { + Point1 = plateView.PointControlToWorld(e.Location); + Point2 = Point1; + plateView.Invalidate(); + } + } + + private void plateView_MouseUp(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + return; + + Point2 = plateView.PointControlToWorld(e.Location); + + if (status == Status.SetFirstPoint) + { + var distance1 = Point1.DistanceTo(Point2); + var distance2 = plateView.LengthWorldToGui(distance1); + + if (distance2 > 40) + { + ZoomWindow(); + CancelAction(); + } + else + { + status = Status.SetSecondPoint; + } + } + else if (status == Status.SetSecondPoint) + { + ZoomWindow(); + CancelAction(); + } + } + + private void plateView_Paint(object sender, PaintEventArgs e) + { + if (!Point1.IsValid() || !Point2.IsValid()) + return; + + var rect = GetRectangle(); + + e.Graphics.FillRectangle(fillBrush, rect); + + e.Graphics.DrawRectangle(borderPen, + rect.X, + rect.Y, + rect.Width, + rect.Height); + + var centerX = rect.X + rect.Width * 0.5f; + var centerY = rect.Y + rect.Height * 0.5f; + + const float halfWidth = 10; + + e.Graphics.DrawLine(borderPen, centerX, centerY - halfWidth, centerX, centerY + halfWidth); + e.Graphics.DrawLine(borderPen, centerX - halfWidth, centerY, centerX + halfWidth, centerY); + } + + private void ZoomWindow() + { + double x, y, w, h; + + if (Point1.X < Point2.X) + { + x = Point1.X; + w = Point2.X - Point1.X; + } + else + { + x = Point2.X; + w = Point1.X - Point2.X; + } + + if (Point1.Y < Point2.Y) + { + y = Point1.Y; + h = Point2.Y - Point1.Y; + } + else + { + y = Point2.Y; + h = Point1.Y - Point2.Y; + } + + plateView.ZoomToArea(x, y, w, h); + plateView.Refresh(); + } + + private RectangleF GetRectangle() + { + var rect = new RectangleF(); + var pt1 = plateView.PointWorldToGraph(Point1); + var pt2 = plateView.PointWorldToGraph(Point2); + + if (pt1.X < pt2.X) + { + rect.X = pt1.X; + rect.Width = pt2.X - pt1.X; + } + else + { + rect.X = pt2.X; + rect.Width = pt1.X - pt2.X; + } + + if (pt1.Y < pt2.Y) + { + rect.Y = pt1.Y; + rect.Height = pt2.Y - pt1.Y; + } + else + { + rect.Y = pt2.Y; + rect.Height = pt1.Y - pt2.Y; + } + + return rect; + } + + public enum Status + { + SetFirstPoint, + SetSecondPoint + } + } +} diff --git a/Source/OpenNest/ColorScheme.cs b/Source/OpenNest/ColorScheme.cs new file mode 100644 index 0000000..aa2148d --- /dev/null +++ b/Source/OpenNest/ColorScheme.cs @@ -0,0 +1,140 @@ +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace OpenNest +{ + public sealed class ColorScheme + { + private Color layoutOutlineColor; + private Color layoutFillColor; + private Color boundingBoxColor; + private Color rapidColor; + private Color originColor; + private Color edgeSpacingColor; + + public static readonly ColorScheme Default = new ColorScheme + { + BackgroundColor = Color.DarkGray, + LayoutOutlineColor = Color.Gray, + LayoutFillColor = Color.WhiteSmoke, + BoundingBoxColor = Color.FromArgb(128, 128, 255), + RapidColor = Color.DodgerBlue, + OriginColor = Color.Gray, + EdgeSpacingColor = Color.FromArgb(180, 180, 180), + }; + + #region Pens/Brushes + + public Pen LayoutOutlinePen { get; private set; } + + public Brush LayoutFillBrush { get; private set; } + + public Pen BoundingBoxPen { get; private set; } + + public Pen RapidPen { get; private set; } + + public Pen OriginPen { get; private set; } + + public Pen EdgeSpacingPen { get; private set; } + + #endregion Pens/Brushes + + #region Colors + + public Color BackgroundColor { get; set; } + + public Color LayoutOutlineColor + { + get { return layoutOutlineColor; } + set + { + layoutOutlineColor = value; + + if (LayoutOutlinePen != null) + LayoutOutlinePen.Dispose(); + + LayoutOutlinePen = new Pen(value); + } + } + + public Color LayoutFillColor + { + get { return layoutFillColor; } + set + { + layoutFillColor = value; + + if (LayoutFillBrush != null) + LayoutFillBrush.Dispose(); + + LayoutFillBrush = new SolidBrush(value); + } + } + + public Color BoundingBoxColor + { + get { return boundingBoxColor; } + set + { + boundingBoxColor = value; + + if (BoundingBoxPen != null) + BoundingBoxPen.Dispose(); + + BoundingBoxPen = new Pen(value); + } + } + + public Color RapidColor + { + get { return rapidColor; } + set + { + rapidColor = value; + + if (RapidPen != null) + RapidPen.Dispose(); + + RapidPen = new Pen(value) + { + DashPattern = new float[] { 10, 10 }, + DashCap = DashCap.Flat + }; + } + } + + public Color OriginColor + { + get { return originColor; } + set + { + originColor = value; + + if (OriginPen != null) + OriginPen.Dispose(); + + OriginPen = new Pen(value); + } + } + + public Color EdgeSpacingColor + { + get { return edgeSpacingColor; } + set + { + edgeSpacingColor = value; + + if (EdgeSpacingPen != null) + EdgeSpacingPen.Dispose(); + + EdgeSpacingPen = new Pen(value) + { + DashPattern = new float[] { 3, 3 }, + DashCap = DashCap.Flat + }; + } + } + + #endregion Colors + } +} diff --git a/Source/OpenNest/Controls/BottomPanel.cs b/Source/OpenNest/Controls/BottomPanel.cs new file mode 100644 index 0000000..81fbda2 --- /dev/null +++ b/Source/OpenNest/Controls/BottomPanel.cs @@ -0,0 +1,36 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class BottomPanel : Panel + { + private readonly Pen lightPen; + private readonly Pen darkPen; + + public BottomPanel() + { + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.Height = 50; + this.Dock = DockStyle.Bottom; + + lightPen = new Pen(ProfessionalColors.SeparatorLight); + darkPen = new Pen(ProfessionalColors.SeparatorDark); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + e.Graphics.DrawLine(darkPen, 0, 0, Width, 0); + e.Graphics.DrawLine(lightPen, 0, 1, Width, 1); + } + } +} diff --git a/Source/OpenNest/Controls/DrawControl.cs b/Source/OpenNest/Controls/DrawControl.cs new file mode 100644 index 0000000..8d1c94c --- /dev/null +++ b/Source/OpenNest/Controls/DrawControl.cs @@ -0,0 +1,247 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public abstract class DrawControl : Control + { + protected PointF origin; + protected Point lastPoint; + protected System.Drawing.Size lastSize; + + public Vector LastPoint { get; protected set; } + public Vector CurrentPoint { get; protected set; } + + public float ViewScale { get; protected set; } + public float ViewScaleMin { get; protected set; } + public float ViewScaleMax { get; protected set; } + + internal Matrix Matrix { get; set; } + + protected const int BorderWidth = 50; + protected const float ZoomInFactor = 1.15f; + protected const float ZoomOutFactor = 1.0f / ZoomInFactor; + + protected DrawControl() + { + ViewScale = 1.0f; + ViewScaleMin = 0.3f; + ViewScaleMax = 3000; + origin = new PointF(100, 100); + } + + /// + /// Returns a point with coordinates relative to the control from the graph. + /// + /// + /// + /// + public Point PointGraphToControl(float x, float y) + { + return new Point((int)(x + origin.X), (int)(y + origin.Y)); + } + + /// + /// Returns a point with coordinates relative to the control from the graph. + /// + /// + /// + public Point PointGraphToControl(PointF pt) + { + return PointGraphToControl(pt.X, pt.Y); + } + + /// + /// Returns a point with coordinates relative to the control from the world. + /// + /// + /// + /// + public Point PointWorldToControl(double x, double y) + { + return PointGraphToControl(PointWorldToGraph(x, y)); + } + + /// + /// Returns a point with coordinates relative to the control from the world. + /// + /// + /// + public Point PointWorldToControl(Vector pt) + { + return PointGraphToControl(PointWorldToGraph(pt.X, pt.Y)); + } + + /// + /// Returns a point with coordinates relative to the graph from the control. + /// + /// + /// + /// + public PointF PointControlToGraph(int x, int y) + { + return new PointF(x - origin.X, y - origin.Y); + } + + /// + /// Returns a point with coordinates relative to the graph from the control. + /// + /// + /// + public PointF PointControlToGraph(Point pt) + { + return PointControlToGraph(pt.X, pt.Y); + } + + /// + /// Returns a point with coordinates relative to the graph from the world. + /// + /// + /// + /// + public PointF PointWorldToGraph(double x, double y) + { + return new PointF(ViewScale * (float)x, -ViewScale * (float)y); + } + + /// + /// Returns a point with coordinates relative to the graph from the world. + /// + /// + /// + public PointF PointWorldToGraph(Vector pt) + { + return PointWorldToGraph(pt.X, pt.Y); + } + + /// + /// Returns a point with coordinates relative to the world from the control. + /// + /// + /// + /// + public Vector PointControlToWorld(int x, int y) + { + return PointGraphToWorld(PointControlToGraph(x, y)); + } + + /// + /// Returns a point with coordinates relative to the world from the control. + /// + /// + /// + public Vector PointControlToWorld(Point pt) + { + return PointGraphToWorld(PointControlToGraph(pt.X, pt.Y)); + } + + /// + /// Returns a point with coordinates relative to the world from the graph. + /// + /// + /// + /// + public Vector PointGraphToWorld(float x, float y) + { + return new Vector(x / ViewScale, y / -ViewScale); + } + + /// + /// Returns a point with coordinates relative to the world from the graph. + /// + /// + /// + public Vector PointGraphToWorld(PointF pt) + { + return PointGraphToWorld(pt.X, pt.Y); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + origin.X += (Size.Width - lastSize.Width) * 0.5f; + origin.Y += (Size.Height - lastSize.Height) * 0.5f; + + lastSize = Size; + } + + public float LengthWorldToGui(double length) + { + return ViewScale * (float)length; + } + + public double LengthGuiToWorld(float length) + { + return length / ViewScale; + } + + public virtual void ZoomToControlPoint(Point pt, float zoomFactor, bool redraw = true) + { + var pt2 = PointControlToWorld(pt); + ZoomToPoint(pt2, zoomFactor, redraw); + } + + public virtual void ZoomToPoint(Vector pt, float zoomFactor, bool redraw = true) + { + var newScale = ViewScale * zoomFactor; + + if (newScale < ViewScaleMin) + zoomFactor = ViewScaleMin / ViewScale; + else if (newScale > ViewScaleMax) + zoomFactor = ViewScaleMax / ViewScale; + + origin.X -= (float)(pt.X * zoomFactor - pt.X) * ViewScale; + origin.Y += (float)(pt.Y * zoomFactor - pt.Y) * ViewScale; + + ViewScale *= zoomFactor; + UpdateMatrix(); + + if (redraw) Invalidate(); + } + + public virtual void ZoomToArea(Box box, bool redraw = true) + { + ZoomToArea(box.X, box.Y, box.Width, box.Height, redraw); + } + + public virtual void ZoomToArea(double x, double y, double width, double height, bool redraw = true) + { + if (width <= 0 || height <= 0) + return; + + var a = (Height - BorderWidth) / height; + var b = (Width - BorderWidth) / width; + + ViewScale = (float)(a < b ? a : b); + + if (ViewScale > ViewScaleMax) + ViewScale = ViewScaleMax; + else if (ViewScale < ViewScaleMin) + ViewScale = ViewScaleMin; + + var px = LengthWorldToGui(x); + var py = LengthWorldToGui(y); + var pw = LengthWorldToGui(width); + var ph = LengthWorldToGui(height); + + origin.X = (Width - pw) * 0.5f - px; + origin.Y = (Height + ph) * 0.5f + py; + + UpdateMatrix(); + + if (redraw) Invalidate(); + } + + protected virtual void UpdateMatrix() + { + if (Matrix != null) + Matrix.Dispose(); + + Matrix = new Matrix(); + Matrix.Scale(ViewScale, -ViewScale); + } + } +} diff --git a/Source/OpenNest/Controls/DrawingListBox.cs b/Source/OpenNest/Controls/DrawingListBox.cs new file mode 100644 index 0000000..272e684 --- /dev/null +++ b/Source/OpenNest/Controls/DrawingListBox.cs @@ -0,0 +1,101 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + using Size = System.Drawing.Size; + + public class DrawingListBox : ListBox + { + private readonly Size imageSize; + private readonly Font nameFont; + private Point lastClickLocation; + + public DrawingListBox() + { + SetStyle( + ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer, true); + + DrawMode = DrawMode.OwnerDrawFixed; + ItemHeight = 85; + + imageSize = new Size(ItemHeight, ItemHeight - 10); + nameFont = new Font(Font.FontFamily, 10, FontStyle.Bold); + } + + public Units Units { get; set; } + + protected override void OnDrawItem(DrawItemEventArgs e) + { + if (e.Index >= Items.Count || e.Index <= -1) + return; + + var dwg = Items[e.Index] as Drawing; + + if (dwg == null) + return; + + e.Graphics.FillRectangle(Brushes.White, e.Bounds); + + var pt = new PointF(5, e.Bounds.Y + 5); + + var brush = new SolidBrush(dwg.Color); + var pen = new Pen(ControlPaint.Dark(dwg.Color)); + + var img = dwg.Program.GetImage(imageSize, pen, brush); + + pen.Dispose(); + brush.Dispose(); + + e.Graphics.DrawImage(img, pt); + + pt.X += imageSize.Width + 10; + + e.Graphics.DrawString(dwg.Name, nameFont, Brushes.Black, pt); + + var bounds = dwg.Program.BoundingBox(); + var text1 = string.Format("{0} of {1} nested", dwg.Quantity.Nested, dwg.Quantity.Required); + var text2 = bounds.Size.ToString(4); + var text3 = string.Format("{0} sq/{1}", Math.Round(dwg.Area, 4), UnitsHelper.GetShortString(Units)); + + pt.Y += 22; + e.Graphics.DrawString(text1, Font, Brushes.Gray, pt); + pt.Y += 18; + e.Graphics.DrawString(text2, Font, Brushes.Gray, pt); + pt.Y += 18; + e.Graphics.DrawString(text3, Font, Brushes.Gray, pt); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + var item = SelectedItem as Drawing; + + if (item == null) + return; + + if (e.Button == MouseButtons.Left && e.Location.DistanceTo(lastClickLocation) > 3) + DoDragDrop(item, DragDropEffects.Copy); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + lastClickLocation = e.Location; + } + } + + public static class PointExtensions + { + public static double DistanceTo(this Point pt1, Point pt2) + { + var x = pt2.X - pt1.X; + var y = pt2.Y - pt1.Y; + + return Math.Sqrt(x * x + y * y); + } + } +} diff --git a/Source/OpenNest/Controls/EntityView.cs b/Source/OpenNest/Controls/EntityView.cs new file mode 100644 index 0000000..4b32381 --- /dev/null +++ b/Source/OpenNest/Controls/EntityView.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; +using OpenNest.Geometry; + +namespace OpenNest.Controls +{ + public class EntityView : DrawControl + { + public List Entities; + + private Pen pen = new Pen(Color.FromArgb(70, 70, 70)); + + public EntityView() + { + Entities = new List(); + + this.BackColor = Color.FromArgb(33, 40, 48); + this.Cursor = Cursors.Cross; + + SetStyle( + ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | + ControlStyles.UserPaint, true); + } + + protected override void OnMouseClick(MouseEventArgs e) + { + base.OnMouseClick(e); + if (!Focused) Focus(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; + e.Graphics.DrawLine(pen, origin.X, 0, origin.X, Height); + e.Graphics.DrawLine(pen, 0, origin.Y, Width, origin.Y); + + e.Graphics.TranslateTransform(origin.X, origin.Y); + + foreach (var entity in Entities) + DrawEntity(e.Graphics, entity, Pens.White); + +#if DRAW_OFFSET + + var offsetShape = new Shape(); + offsetShape.Entities.AddRange(Entities); + + foreach (var entity in ((Shape)offsetShape.OffsetEntity(0.25, OffsetSide.Left)).Entities) + DrawEntity(e.Graphics, entity, Pens.RoyalBlue); +#endif + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + + float multiplier = Math.Abs(e.Delta / 120.0f); + + if (e.Delta > 0) + ZoomToControlPoint(e.Location, (float)Math.Pow(ZoomInFactor, multiplier)); + else + ZoomToControlPoint(e.Location, (float)Math.Pow(ZoomOutFactor, multiplier)); + + Invalidate(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (e.Button == MouseButtons.Middle) + { + var diffx = e.X - lastPoint.X; + var diffy = e.Y - lastPoint.Y; + + origin.X += diffx; + origin.Y += diffy; + + Invalidate(); + } + else + { + LastPoint = CurrentPoint; + CurrentPoint = PointControlToWorld(e.Location); + } + + lastPoint = e.Location; + + base.OnMouseMove(e); + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + + if (e.Button == MouseButtons.Middle) + ZoomToFit(); + } + + private void DrawEntity(Graphics g, Entity e, Pen pen) + { + if (!e.Layer.IsVisible) + return; + + switch (e.Type) + { + case EntityType.Arc: + DrawArc(g, (Arc)e, pen); + break; + + case EntityType.Circle: + DrawCircle(g, (Circle)e, pen); + break; + + case EntityType.Line: + DrawLine(g, (Line)e, pen); + break; + } + } + + private void DrawLine(Graphics g, Line line, Pen pen) + { + var pt1 = PointWorldToGraph(line.StartPoint); + var pt2 = PointWorldToGraph(line.EndPoint); + + g.DrawLine(pen, pt1, pt2); + } + + private void DrawArc(Graphics g, Arc arc, Pen pen) + { + var center = PointWorldToGraph(arc.Center); + var radius = LengthWorldToGui(arc.Radius); + var diameter = radius * 2.0f; + + var startAngle = arc.IsReversed + ? -(float)Angle.ToDegrees(arc.EndAngle) + : -(float)Angle.ToDegrees(arc.StartAngle); + + g.DrawArc( + pen, + center.X - radius, + center.Y - radius, + diameter, + diameter, + startAngle, + -(float)Angle.ToDegrees(arc.SweepAngle())); + } + + private void DrawCircle(Graphics g, Circle circle, Pen pen) + { + var center = PointWorldToGraph(circle.Center); + var radius = LengthWorldToGui(circle.Radius); + var diameter = radius * 2.0f; + + g.DrawEllipse(pen, + center.X - radius, + center.Y - radius, + diameter, + diameter); + } + + private void DrawPoint(Graphics g, Vector pt, Pen pen) + { + var pt1 = PointWorldToGraph(pt); + + g.DrawLine(pen, pt1.X - 3, pt1.Y, pt1.X + 3, pt1.Y); + g.DrawLine(pen, pt1.X, pt1.Y - 3, pt1.X, pt1.Y + 3); + } + + private void DrawPoint(Graphics g, Vector pt, Pen pen, Brush fillBrush) + { + var pt1 = PointWorldToGraph(pt); + var rect = new RectangleF(pt1.X - 3, pt1.Y - 3, 6, 6); + + g.FillEllipse(fillBrush, rect); + g.DrawEllipse(pen, rect); + } + + private void DrawIntersections(Graphics g) + { + for (int i = 0; i < Entities.Count; ++i) + { + var entity1 = Entities[i]; + + if (entity1.Type != EntityType.Line) + continue; + + for (int j = i + 1; j < Entities.Count; ++j) + { + var entity2 = Entities[j]; + + if (entity2.Type != EntityType.Line) + continue; + + var line1 = (Line)entity1; + var line2 = (Line)entity2; + + Vector pt; + + if (line1.Intersects(line2, out pt)) + DrawPoint(g, pt, Pens.Yellow, Brushes.Red); + } + } + } + + public void ZoomToFit(bool redraw = true) + { + ZoomToArea(Entities.GetBoundingBox(), redraw); + } + } +} diff --git a/Source/OpenNest/Controls/HorizontalLine.cs b/Source/OpenNest/Controls/HorizontalLine.cs new file mode 100644 index 0000000..e97c840 --- /dev/null +++ b/Source/OpenNest/Controls/HorizontalLine.cs @@ -0,0 +1,38 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class HorizontalLine : Control + { + private readonly Pen lightPen; + private readonly Pen darkPen; + + public HorizontalLine() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.Selectable, false); + + lightPen = new Pen(ProfessionalColors.SeparatorLight); + darkPen = new Pen(ProfessionalColors.SeparatorDark); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + float midpoint = Height * 0.5f; + + e.Graphics.DrawLine(darkPen, 0, midpoint, Width, midpoint); + midpoint++; + e.Graphics.DrawLine(lightPen, 0, midpoint, Width, midpoint); + } + } +} diff --git a/Source/OpenNest/Controls/LayoutViewGL.cs b/Source/OpenNest/Controls/LayoutViewGL.cs new file mode 100644 index 0000000..f32a2c6 --- /dev/null +++ b/Source/OpenNest/Controls/LayoutViewGL.cs @@ -0,0 +1,451 @@ +using System.Runtime.Remoting.Messaging; +using libPep; +using libPep.Codes; +using OpenTK.Graphics.OpenGL; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class LayoutViewGL : OpenTK.GLControl + { + private float scale; + private bool loaded = false; + + private const double TwoPI = Math.PI * 2.0; + private const int Resolution = 100; + private const int BorderWidth = 50; + private const float ZoomInFactor = 1.1f; + private const float ZoomOutFactor = 0.9f; + + private PointF origin; + private PointF lastPoint; + + private Vector curpos; + private ProgrammingMode mode; + + public Color RapidColor { get; set; } + public Color PlateFillColor { get; set; } + public Color PlateBorderColor { get; set; } + public Color GeometryColor { get; set; } + + public Plate Plate; + + public LayoutViewGL() + { + scale = 25; + origin = new PointF(0, 0); + lastPoint = new PointF(); + + BackColor = Color.White; + RapidColor = Color.Blue; + GeometryColor = Color.LimeGreen; + PlateFillColor = Color.WhiteSmoke; + PlateBorderColor = Color.DarkGray; + + Cursor = Cursors.Cross; + + Plate = new Plate(); + + Context.SwapInterval = 0; + } + + public bool DrawRapid { get; set; } + + private void SetupViewport() + { + int w = Width; + int h = Height; + GL.MatrixMode(MatrixMode.Projection); + GL.LoadIdentity(); + GL.Ortho(0, w, 0, h, -1, 1); + GL.Viewport(0, 0, w, h); + } + + protected override void OnLoad(EventArgs e) + { + SetupViewport(); + GL.ClearColor(this.BackColor); + loaded = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + if (!loaded) + return; + + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadIdentity(); + + //DrawAxis(); + + GL.Translate(origin.X, origin.Y, 0); + GL.Scale(scale, scale, 1); + + curpos = new Vector(); + + if (Plate != null) + { + GL.Color3(PlateFillColor); + GL.Begin(PrimitiveType.Quads); + DrawPlate(Plate); + GL.End(); + + GL.Color3(PlateBorderColor); + GL.Begin(PrimitiveType.LineLoop); + DrawPlate(Plate); + GL.End(); + + GL.Color3(GeometryColor); + DrawProgram(Plate); + } + + SwapBuffers(); + } + + protected override void OnResize(EventArgs e) + { + if (!loaded) + return; + + SetupViewport(); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + + float multiplier = Math.Abs(e.Delta / 120.0f); + + if (e.Delta > 0) + ZoomToPoint(e.X, e.Y, (float)Math.Pow(ZoomInFactor, multiplier)); + else + ZoomToPoint(e.X, e.Y, (float)Math.Pow(ZoomOutFactor, multiplier)); + + Invalidate(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (e.Button == MouseButtons.Middle) + { + var diffx = e.X - lastPoint.X; + var diffy = e.Y - lastPoint.Y; + + origin.X += diffx; + origin.Y -= diffy; + + Invalidate(); + } + + lastPoint = e.Location; + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + + if (e.Button == MouseButtons.Middle) + { + ZoomToFit(); + Invalidate(); + } + } + + private void DrawAxis() + { + GL.Begin(PrimitiveType.Lines); + GL.Vertex2(origin.X, 0); + GL.Vertex2(origin.X, Height); + + GL.Vertex2(0, origin.Y); + GL.Vertex2(Width, origin.Y); + GL.End(); + } + + public void ZoomToPoint(double x, double y, float zoomFactor) + { + double x1 = ToRealX(x); + double y1 = ToRealY(y); + + origin.X -= (float)(x1 * zoomFactor - x1) * scale; + origin.Y -= (float)(y1 * zoomFactor - y1) * scale; + + scale *= zoomFactor; + } + + private void DrawPlate(Plate plate) + { + switch (plate.Quadrant) + { + case 1: + GL.Vertex2(0, 0); + GL.Vertex2(0, plate.Size.Width); + GL.Vertex2(plate.Size.Length, plate.Size.Width); + GL.Vertex2(plate.Size.Length, 0); + break; + + case 2: + GL.Vertex2(0, 0); + GL.Vertex2(0, plate.Size.Width); + GL.Vertex2(-plate.Size.Length, plate.Size.Width); + GL.Vertex2(-plate.Size.Length, 0); + break; + + case 3: + GL.Vertex2(0, 0); + GL.Vertex2(0, -plate.Size.Width); + GL.Vertex2(-plate.Size.Length, -plate.Size.Width); + GL.Vertex2(-plate.Size.Length, 0); + break; + + case 4: + GL.Vertex2(0, 0); + GL.Vertex2(0, -plate.Size.Width); + GL.Vertex2(plate.Size.Length, -plate.Size.Width); + GL.Vertex2(plate.Size.Length, 0); + break; + + default: + return; + } + } + + private void DrawProgram(libPep.Program pgm) + { + mode = pgm.Mode; + + foreach (var code in pgm) + { + switch (code.Type()) + { + case CodeType.Arc: + var arc = (Arc)code; + DrawArc(arc); + break; + + case CodeType.Line: + var line = (Line)code; + DrawLine(line); + break; + + case CodeType.SubProgramCall: + var tmpmode = mode; + var subpgm = (SubProgramCall)code; + + if (subpgm.Loop != null) + DrawProgram(subpgm.Loop); + + mode = tmpmode; + break; + } + } + } + + private void DrawLine(Line line) + { + var pt = line.EndPoint; + + if (mode == ProgrammingMode.Incremental) + pt += curpos; + + if (line.IsRapid) + { + if (DrawRapid) + { + GL.PushAttrib(AttribMask.EnableBit); + GL.Enable(EnableCap.LineStipple); + GL.LineStipple(2, 0xCCCC); + GL.Color3(RapidColor); + DrawLine(curpos, pt); + GL.Color3(GeometryColor); + GL.PopAttrib(); + } + } + else + DrawLine(curpos, pt); + + curpos = pt; + } + + private void DrawLine(Vector pt1, Vector pt2) + { + GL.Begin(PrimitiveType.Lines); + GL.Vertex2(pt1.X, pt1.Y); + GL.Vertex2(pt2.X, pt2.Y); + GL.End(); + } + + private void DrawArc(Arc arc) + { + var endpt = arc.EndPoint; + var center = arc.CenterPoint; + + if (mode == ProgrammingMode.Incremental) + { + endpt += curpos; + center += curpos; + } + + // start angle in radians + var startAngle = Math.Atan2( + curpos.Y - center.Y, + curpos.X - center.X); + + // end angle in radians + var endAngle = Math.Atan2( + endpt.Y - center.Y, + endpt.X - center.X); + + endAngle = NormalizeAngle(endAngle); + startAngle = NormalizeAngle(startAngle); + + if (arc.Rotation == RotationType.CCW && endAngle < startAngle) + endAngle += TwoPI; + else if (arc.Rotation == RotationType.CW && startAngle < endAngle) + startAngle += TwoPI; + + var dx = endpt.X - center.X; + var dy = endpt.Y - center.Y; + + var radius = Math.Sqrt(dx * dx + dy * dy); + + if (startAngle.IsEqualTo(endAngle)) + { + GL.End(); + GL.Begin(PrimitiveType.LineLoop); + DrawCircle(center, radius); + GL.End(); + GL.Begin(PrimitiveType.Polygon); + } + else + DrawArc(center, radius, endAngle, startAngle); + + curpos = endpt; + } + + private void DrawArc(Vector center, double radius, double startAngle, double endAngle) + { + GL.Begin(PrimitiveType.LineStrip); + + var angle = (endAngle - startAngle) / Resolution; + + for (int i = 0; i <= Resolution; i++) + { + GL.Vertex2( + Math.Cos(startAngle + angle * i) * radius + center.X, + Math.Sin(startAngle + angle * i) * radius + center.Y); + } + + GL.End(); + } + + private void DrawCircle(Vector center, double radius) + { + const float angle = (float)Math.PI * 2.0f; + const float increment = angle / Resolution; + + for (float i = 0; i <= angle; i += increment) + { + GL.Vertex2( + Math.Cos(i) * radius + center.X, + Math.Sin(i) * radius + center.Y); + } + + } + + private static double NormalizeAngle(double angle) + { + double r = angle % TwoPI; + return r < 0 ? TwoPI + r : r; + } + + private static void Swap(ref T a, ref T b) + { + T c = a; + a = b; + b = c; + } + + public void ZoomToFit() + { + if (Plate.Size.Width <= 0 || Plate.Size.Length <= 0) + return; + + float a = (this.Height - BorderWidth) / (float)Plate.Size.Width; + float b = (this.Width - BorderWidth) / (float)Plate.Size.Length; + + scale = a < b ? a : b; + + double px; + double py; + + switch (Plate.Quadrant) + { + case 1: + px = py = ToGui(0); + break; + + case 2: + px = ToGui(-Plate.Size.Length); + py = ToGui(0); + break; + + case 3: + px = ToGui(-Plate.Size.Length); + py = ToGui(-Plate.Size.Width); + break; + + case 4: + px = ToGui(0); + py = ToGui(-Plate.Size.Width); + break; + + default: + return; + } + + var pw = ToGui(Plate.Size.Length); + var ph = ToGui(Plate.Size.Width); + + origin.X = (float)((this.Width - pw) * 0.5f - px); + origin.Y = (float)((this.Height - ph) * 0.5f - py); + + Invalidate(); + } + + public float ToGui(double v) + { + return (float)v * scale; + } + + public double ToReal(double v) + { + return v / scale; + } + + public double ToRealX(double x) + { + return (x - origin.X) / scale; + } + + public double ToRealY(double y) + { + return (Height - y - origin.Y) / scale; + } + + public float ToGuiX(double x) + { + return scale * (float)x + origin.X; + } + + public float ToGuiY(double y) + { + return scale * (float)y + origin.Y; + } + } +} diff --git a/Source/OpenNest/Controls/NumericUpDown.cs b/Source/OpenNest/Controls/NumericUpDown.cs new file mode 100644 index 0000000..3518fb8 --- /dev/null +++ b/Source/OpenNest/Controls/NumericUpDown.cs @@ -0,0 +1,40 @@ +using System; + +namespace OpenNest.Controls +{ + public class NumericUpDown : System.Windows.Forms.NumericUpDown + { + private string suffix; + + + + public NumericUpDown() + { + suffix = string.Empty; + } + + public string Suffix + { + get { return suffix; } + set + { + suffix = value; + UpdateEditText(); + } + } + + protected override void OnEnter(EventArgs e) + { + base.OnEnter(e); + this.Select(0, Text.Length); + } + + protected override void UpdateEditText() + { + if (Suffix != null) + Text = Value.ToString("N" + DecimalPlaces) + Suffix; + else + base.UpdateEditText(); + } + } +} diff --git a/Source/OpenNest/Controls/PlateView.cs b/Source/OpenNest/Controls/PlateView.cs new file mode 100644 index 0000000..cbc29cf --- /dev/null +++ b/Source/OpenNest/Controls/PlateView.cs @@ -0,0 +1,880 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Windows.Forms; +using OpenNest.Actions; +using OpenNest.CNC; +using OpenNest.Collections; +using Action = OpenNest.Actions.Action; +using Timer = System.Timers.Timer; + +namespace OpenNest.Controls +{ + public class PlateView : DrawControl + { + private readonly Font programIdFont; + private readonly Timer redrawTimer; + + private string status; + private Plate plate; + private Action currentAction; + private List parts; + + public List SelectedParts; + public ReadOnlyCollection Parts; + + public event EventHandler PartAdded; + public event EventHandler PartRemoved; + public event EventHandler StatusChanged; + + public PlateView() + : this(ColorScheme.Default) + { + } + + public PlateView(ColorScheme colorScheme) + { + Plate = new Plate(60, 120); + programIdFont = new Font(DefaultFont, FontStyle.Bold | FontStyle.Underline); + origin = new PointF(); + parts = new List(); + Parts = new ReadOnlyCollection(parts); + SelectedParts = new List(); + + redrawTimer = new Timer() + { + AutoReset = false, + Enabled = true, + Interval = 50 + }; + redrawTimer.Elapsed += redrawTimer_Elapsed; + + SetStyle( + ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | + ControlStyles.UserPaint, true); + + ViewScale = 1.0f; + RotateIncrementAngle = 10; + OffsetIncrementDistance = 10; + ColorScheme = colorScheme; + BackColor = colorScheme.BackgroundColor; + Cursor = Cursors.Cross; + AllowPan = true; + AllowSelect = true; + AllowZoom = true; + AllowDrop = true; + DrawOrigin = true; + DrawRapid = false; + DrawBounds = true; + FillParts = true; + SetAction(typeof(ActionSelect)); + + UpdateMatrix(); + } + + public ColorScheme ColorScheme { get; set; } + + public bool AllowZoom { get; set; } + + public bool AllowSelect { get; set; } + + public bool AllowPan { get; set; } + + public bool DrawOrigin { get; set; } + + public bool DrawRapid { get; set; } + + public bool DrawBounds { get; set; } + + public bool FillParts { get; set; } + + public double RotateIncrementAngle { get; set; } + + public double OffsetIncrementDistance { get; set; } + + public Plate Plate + { + get { return plate; } + set { SetPlate(value); } + } + + private void SetPlate(Plate p) + { + if (plate != null) + { + plate.PartAdded -= plate_PartAdded; + plate.PartRemoved -= plate_PartRemoved; + parts.Clear(); + SelectedParts.Clear(); + } + + plate = p; + plate.PartAdded += plate_PartAdded; + plate.PartRemoved += plate_PartRemoved; + + foreach (var part in plate.Parts) + parts.Add(LayoutPart.Create(part, this)); + + SetAction(typeof(ActionSelect)); + } + + public string Status + { + get { return status; } + protected set + { + status = value; + + if (StatusChanged != null) + StatusChanged.Invoke(this, new EventArgs()); + } + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + if (!Focused) Focus(); + } + + protected override void OnDragEnter(DragEventArgs drgevent) + { + if (drgevent.Data.GetData(typeof(Drawing)) != null) + drgevent.Effect = DragDropEffects.Copy; + } + + protected override void OnDragDrop(DragEventArgs drgevent) + { + var dwg = drgevent.Data.GetData(typeof(Drawing)) as Drawing; + + if (dwg == null) + return; + + var pt1 = PointToClient(new Point(drgevent.X, drgevent.Y)); + var pt2 = PointControlToWorld(pt1); + + AddPartFromDrawing(dwg, pt2); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + + var multiplier = Math.Abs(e.Delta / 120); + + if (SelectedParts.Count > 0 && ((ModifierKeys & Keys.Shift) == Keys.Shift)) + { + var increment = (ModifierKeys & Keys.Control) == Keys.Control + ? RotateIncrementAngle * 0.1 + : RotateIncrementAngle; + + var angle = Angle.ToRadians((e.Delta > 0 ? -increment : increment) * multiplier); + + RotateSelectedParts(angle); + } + else + { + if (AllowZoom) + { + if (e.Delta > 0) + ZoomToControlPoint(e.Location, (float)Math.Pow(ZoomInFactor, multiplier)); + else + ZoomToControlPoint(e.Location, (float)Math.Pow(ZoomOutFactor, multiplier)); + } + } + + Invalidate(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (e.Button == MouseButtons.Middle) + { + if (AllowPan) + { + var diffx = e.X - lastPoint.X; + var diffy = e.Y - lastPoint.Y; + + origin.X += diffx; + origin.Y += diffy; + + Invalidate(); + } + } + else + { + LastPoint = CurrentPoint; + CurrentPoint = PointControlToWorld(e.Location); + } + + lastPoint = e.Location; + + base.OnMouseMove(e); + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + + if (e.Button == MouseButtons.Middle) + ZoomToFit(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Delete: + RemoveSelectedParts(); + break; + + default: + base.OnKeyDown(e); + break; + } + } + + protected override bool ProcessDialogKey(Keys keyData) + { + // Only handle TAB, RETURN, ESC, and ARROW KEYS here. + // All other keys can be handled in OnKeyDown method. + + switch (keyData) + { + case Keys.Escape: + if (currentAction.IsBusy()) + currentAction.CancelAction(); + else + SetAction(typeof(ActionSelect)); + break; + + case Keys.Left: + SelectedParts.ForEach(part => part.Offset(-OffsetIncrementDistance, 0)); + Invalidate(); + break; + + case Keys.X: + case Keys.Shift | Keys.Left: + PushSelected(PushDirection.Left); + break; + + case Keys.Shift | Keys.X: + case Keys.Shift | Keys.Right: + PushSelected(PushDirection.Right); + break; + + case Keys.Shift | Keys.Y: + case Keys.Shift | Keys.Up: + PushSelected(PushDirection.Up); + break; + + case Keys.Y: + case Keys.Shift | Keys.Down: + PushSelected(PushDirection.Down); + break; + + case Keys.Right: + SelectedParts.ForEach(part => part.Offset(OffsetIncrementDistance, 0)); + Invalidate(); + break; + + case Keys.Up: + SelectedParts.ForEach(part => part.Offset(0, OffsetIncrementDistance)); + Invalidate(); + break; + + case Keys.Down: + SelectedParts.ForEach(part => part.Offset(0, -OffsetIncrementDistance)); + Invalidate(); + break; + } + + return base.ProcessDialogKey(keyData); + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; + + if (DrawOrigin) + { + e.Graphics.DrawLine(ColorScheme.OriginPen, origin.X, 0, origin.X, Height); + e.Graphics.DrawLine(ColorScheme.OriginPen, 0, origin.Y, Width, origin.Y); + } + + e.Graphics.TranslateTransform(origin.X, origin.Y); + + DrawPlate(e.Graphics); + DrawParts(e.Graphics); + + base.OnPaint(e); + } + + protected override void OnHandleDestroyed(EventArgs e) + { + base.OnHandleDestroyed(e); + + if (currentAction != null) + { + currentAction.CancelAction(); + currentAction.DisconnectEvents(); + currentAction = null; + } + } + + public override void Refresh() + { + parts.ForEach(p => p.Update(this)); + Invalidate(); + } + + private void DrawPlate(Graphics g) + { + var plateRect = new RectangleF + { + Width = LengthWorldToGui(Plate.Size.Width), + Height = LengthWorldToGui(Plate.Size.Height) + }; + + var edgeSpacingRect = new RectangleF + { + Width = LengthWorldToGui(Plate.Size.Width - Plate.EdgeSpacing.Left - Plate.EdgeSpacing.Right), + Height = LengthWorldToGui(Plate.Size.Height - Plate.EdgeSpacing.Top - Plate.EdgeSpacing.Bottom) + }; + + switch (Plate.Quadrant) + { + case 1: + plateRect.Location = PointWorldToGraph(0, 0); + edgeSpacingRect.Location = PointWorldToGraph( + Plate.EdgeSpacing.Left, + Plate.EdgeSpacing.Bottom); + break; + + case 2: + plateRect.Location = PointWorldToGraph(-Plate.Size.Width, 0); + edgeSpacingRect.Location = PointWorldToGraph( + Plate.EdgeSpacing.Left - Plate.Size.Width, + Plate.EdgeSpacing.Bottom); + break; + + case 3: + plateRect.Location = PointWorldToGraph(-Plate.Size.Width, -Plate.Size.Height); + edgeSpacingRect.Location = PointWorldToGraph( + Plate.EdgeSpacing.Left - Plate.Size.Width, + Plate.EdgeSpacing.Bottom - Plate.Size.Height); + break; + + case 4: + plateRect.Location = PointWorldToGraph(0, -Plate.Size.Height); + edgeSpacingRect.Location = PointWorldToGraph( + Plate.EdgeSpacing.Left, + Plate.EdgeSpacing.Bottom - Plate.Size.Height); + break; + + default: + return; + } + + plateRect.Y -= plateRect.Height; + edgeSpacingRect.Y -= edgeSpacingRect.Height; + + g.FillRectangle(ColorScheme.LayoutFillBrush, plateRect); + + var viewBounds = new RectangleF(-origin.X, -origin.Y, Width, Height); + + if (!edgeSpacingRect.Contains(viewBounds)) + { + g.DrawRectangle(ColorScheme.EdgeSpacingPen, + edgeSpacingRect.X, + edgeSpacingRect.Y, + edgeSpacingRect.Width, + edgeSpacingRect.Height); + } + + g.DrawRectangle(ColorScheme.LayoutOutlinePen, + plateRect.X, + plateRect.Y, + plateRect.Width, + plateRect.Height); + } + + private void DrawParts(Graphics g) + { + var viewBounds = new RectangleF(-origin.X, -origin.Y, Width, Height); + + for (int i = 0; i < parts.Count; ++i) + { + var part = parts[i]; + + if (part.IsDirty) + part.Update(this); + + var path = part.Path; + var pathBounds = path.GetBounds(); + + if (!pathBounds.IntersectsWith(viewBounds)) + continue; + + part.Draw(g, (i + 1).ToString()); + } + + if (DrawBounds) + { + var bounds = SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox(); + DrawBox(g, bounds); + } + + if (DrawRapid) + DrawRapids(g); + } + + private void DrawRapids(Graphics g) + { + var pos = new Vector(0, 0); + + for (int i = 0; i < Plate.Parts.Count; ++i) + { + var part = Plate.Parts[i]; + var pgm = part.Program; + + DrawLine(g, pos, part.Location, ColorScheme.RapidPen); + pos = part.Location; + DrawRapids(g, pgm, ref pos); + } + } + + private void DrawRapids(Graphics g, Program pgm, ref Vector pos) + { + for (int i = 0; i < pgm.Length; ++i) + { + var code = pgm[i]; + + if (code.Type == CodeType.SubProgramCall) + { + var subpgm = (SubProgramCall)code; + var program = subpgm.Program; + + if (program != null) + DrawRapids(g, program, ref pos); + } + else + { + var motion = code as Motion; + + if (motion != null) + { + if (pgm.Mode == Mode.Incremental) + { + var endpt = motion.EndPoint + pos; + + if (code.Type == CodeType.RapidMove) + DrawLine(g, pos, endpt, ColorScheme.RapidPen); + pos = endpt; + } + else + { + if (code.Type == CodeType.RapidMove) + DrawLine(g, pos, motion.EndPoint, ColorScheme.RapidPen); + pos = motion.EndPoint; + } + } + } + } + } + + private void DrawLine(Graphics g, Vector pt1, Vector pt2, Pen pen) + { + var point1 = PointWorldToGraph(pt1); + var point2 = PointWorldToGraph(pt2); + + g.DrawLine(pen, point1, point2); + } + + private void DrawBox(Graphics g, Box box) + { + var rect = new RectangleF + { + Location = PointWorldToGraph(box.Location), + Width = LengthWorldToGui(box.Width), + Height = LengthWorldToGui(box.Height) + }; + + g.DrawRectangle(ColorScheme.BoundingBoxPen, rect.X, rect.Y - rect.Height, rect.Width, rect.Height); + } + + public LayoutPart GetPartAtControlPoint(Point pt) + { + var pt2 = PointControlToGraph(pt); + return GetPartAtGraphPoint(pt2); + } + + public LayoutPart GetPartAtGraphPoint(PointF pt) + { + for (int i = parts.Count - 1; i >= 0; --i) + { + if (parts[i].Path.IsVisible(pt)) + return parts[i]; + } + + return null; + } + + public LayoutPart GetPartAtPoint(Vector pt) + { + var pt2 = PointWorldToGraph(pt); + return GetPartAtGraphPoint(pt2); + } + + public IList GetPartsFromWindow(RectangleF rect, SelectionType selectionType) + { + var list = new List(); + + if (selectionType == SelectionType.Intersect) + { + for (int i = 0; i < parts.Count; ++i) + { + var part = parts[i]; + var path = part.Path; + var region = new Region(path); + + if (region.IsVisible(rect)) + list.Add(part); + + region.Dispose(); + } + } + else + { + for (int i = 0; i < parts.Count; ++i) + { + var part = parts[i]; + var path = part.Path; + var bounds = path.GetBounds(); + + if (rect.Contains(bounds)) + list.Add(part); + } + } + + return list; + } + + public void SetAction(Type type) + { + var action = Activator.CreateInstance(type, this) as Action; + + if (action == null) + return; + + if (currentAction != null) + { + currentAction.CancelAction(); + currentAction.DisconnectEvents(); + currentAction = null; + } + + currentAction = action; + + Status = GetDisplayName(type); + } + + public void SetAction(Type type, params object[] args) + { + if (currentAction != null) + { + currentAction.CancelAction(); + currentAction.DisconnectEvents(); + currentAction = null; + } + + Array.Resize(ref args, args.Length + 1); + + // shift all elements to the right + for (int i = args.Length - 2; i >= 0; i--) + args[i + 1] = args[i]; + + // set the first argument to this. + args[0] = this; + + var action = Activator.CreateInstance(type, args) as Action; + + if (action == null) + return; + + currentAction = action; + + Status = GetDisplayName(type); + } + + public void AlignSelected(AlignType alignType) + { + if (SelectedParts.Count == 0) + return; + + AlignSelected(alignType, SelectedParts[0]); + } + + public void AlignSelected(AlignType alignType, LayoutPart fixedPart) + { + switch (alignType) + { + case AlignType.Bottom: + Align.Bottom(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.Horizontally: + Align.Horizontally(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.Left: + Align.Left(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.Right: + Align.Right(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.Top: + Align.Top(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.Vertically: + Align.Vertically(fixedPart.BasePart, SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.EvenlySpaceHorizontally: + Align.EvenlyDistributeHorizontally(SelectedParts.Select(p => p.BasePart).ToList()); + break; + + case AlignType.EvenlySpaceVertically: + Align.EvenlyDistributeVertically(SelectedParts.Select(p => p.BasePart).ToList()); + break; + + default: + return; + } + + SelectedParts.ForEach(p => p.IsDirty = true); + Invalidate(); + } + + public void AddPartFromDrawing(Drawing dwg, Vector location) + { + var part = new Part(dwg, location); + + part.Offset( + part.Location.X - part.BoundingBox.Center.X, + part.Location.Y - part.BoundingBox.Center.Y); + + Plate.Parts.Add(part); + } + + public void RemoveSelectedParts() + { + foreach (var part in SelectedParts) + Plate.Parts.Remove(part.BasePart); + + DeselectAll(); + Invalidate(); + } + + private void redrawTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + Invalidate(); + } + + private void plate_PartAdded(object sender, PartAddedEventArgs e) + { + if (PartAdded != null) + PartAdded.Invoke(this, e); + + parts.Insert(e.Index, LayoutPart.Create(e.Part, this)); + redrawTimer.Start(); + } + + private void plate_PartRemoved(object sender, PartRemovedEventArgs e) + { + if (PartRemoved != null) + PartRemoved.Invoke(this, e); + + parts.RemoveAll(p => p.BasePart == e.Part); + } + + public void DeselectAll() + { + SelectedParts.ForEach(p => p.IsSelected = false); + SelectedParts.Clear(); + } + + public void SelectAll() + { + parts.ForEach(p => p.IsSelected = true); + SelectedParts.AddRange(parts); + } + + public override void ZoomToPoint(Vector pt, float zoomFactor, bool redraw = true) + { + base.ZoomToPoint(pt, zoomFactor, false); + + if (redraw) + Invalidate(); + } + + public override void ZoomToArea(double x, double y, double width, double height, bool redraw = true) + { + base.ZoomToArea(x, y, width, height, false); + + if (redraw) + Invalidate(); + } + + public virtual void ZoomToFit(bool redraw = true) + { + ZoomToArea(plate.BoundingBox(true), redraw); + } + + public virtual void ZoomToSelected(bool redraw = true) + { + ZoomToArea(SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox(), redraw); + } + + public virtual void ZoomToPlate(bool redraw = true) + { + ZoomToArea(plate.BoundingBox(false), redraw); + } + + public void PushSelected(PushDirection direction) + { + var boxes = new List(); + + foreach (var part in parts.Where(p => !p.IsSelected).ToList()) + { + if (!SelectedParts.Contains(part)) + boxes.Add(part.BoundingBox); + } + + var workArea = Plate.WorkArea(); + var distance = double.MaxValue; + var offset = new Vector(); + + switch (direction) + { + case PushDirection.Down: + SelectedParts.ForEach(part => + { + var d1 = Helper.ClosestDistanceDown(part.BoundingBox.Offset(Plate.PartSpacing), boxes); + + if (d1 < distance) + distance = d1; + + var d2 = part.Bottom - workArea.Bottom; + + if (d2 > 0 && d2 < distance) + distance = d2; + }); + offset.Y = -distance; + break; + + case PushDirection.Left: + SelectedParts.ForEach(part => + { + var d1 = Helper.ClosestDistanceLeft(part.BoundingBox.Offset(Plate.PartSpacing), boxes); + + if (d1 < distance) + distance = d1; + + var d2 = part.Left - workArea.Left; + + if (d2 > 0 && d2 < distance) + distance = d2; + }); + offset.X = -distance; + break; + + case PushDirection.Right: + SelectedParts.ForEach(part => + { + var d1 = Helper.ClosestDistanceRight(part.BoundingBox.Offset(Plate.PartSpacing), boxes); + + if (d1 < distance) + distance = d1; + + var d2 = workArea.Right - part.Right; + + if (d2 > 0 && d2 < distance) + distance = d2; + }); + offset.X = distance; + break; + + case PushDirection.Up: + SelectedParts.ForEach(part => + { + var d1 = Helper.ClosestDistanceUp(part.BoundingBox.Offset(Plate.PartSpacing), boxes); + + if (d1 < distance) + distance = d1; + + var d2 = workArea.Top - part.Top; + + if (d2 > 0 && d2 < distance) + distance = d2; + }); + offset.Y = distance; + break; + } + + if (distance < double.MaxValue) + { + SelectedParts.ForEach(p => p.Offset(offset)); + Invalidate(); + } + } + + private string GetDisplayName(Type type) + { + var attributes = type.GetCustomAttributes(true); + + foreach (var attr in attributes) + { + var displayNameAttr = attr as DisplayNameAttribute; + + if (displayNameAttr != null) + return displayNameAttr.DisplayName; + } + + return type.Name; + } + + public void RotateSelectedParts(double angle) + { + var pt1 = SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox().Location; + + for (int i = 0; i < SelectedParts.Count; ++i) + { + var part = SelectedParts[i]; + part.Rotate(angle); + } + + var pt2 = SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox().Location; + var diff = pt1 - pt2; + + for (int i = 0; i < SelectedParts.Count; ++i) + { + var part = SelectedParts[i]; + part.Offset(diff); + } + } + + protected override void UpdateMatrix() + { + base.UpdateMatrix(); + parts.ForEach(p => p.Update(this)); + } + } +} diff --git a/Source/OpenNest/Controls/QuadrantSelect.cs b/Source/OpenNest/Controls/QuadrantSelect.cs new file mode 100644 index 0000000..9c6c80a --- /dev/null +++ b/Source/OpenNest/Controls/QuadrantSelect.cs @@ -0,0 +1,82 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class QuadrantSelect : Control + { + private int quadrant; + + public QuadrantSelect() + { + } + + public int Quadrant + { + get { return quadrant; } + set { quadrant = value <= 4 && value > 0 ? value : 1; } + } + + protected override void OnPaint(PaintEventArgs e) + { + var midx = Width * 0.5f; + var midy = Height * 0.5f; + + RectangleF rect; + + switch (Quadrant) + { + case 1: + rect = new RectangleF(midx, 0, midx - 1, midy); + break; + + case 2: + rect = new RectangleF(0, 0, midx, midy); + break; + + case 3: + rect = new RectangleF(0, midy, midx, midy - 1); + break; + + case 4: + rect = new RectangleF(midx, midy, midx - 1, midy - 1); + break; + + default: + return; + } + + e.Graphics.DrawLine(Pens.Gray, midx, 0, midx, Height); + e.Graphics.DrawLine(Pens.Gray, 0, midy, Width, midy); + + e.Graphics.FillRectangle(Brushes.LightSkyBlue, rect); + e.Graphics.DrawRectangle(Pens.Blue, rect.X, rect.Y, rect.Width, rect.Height); + + e.Graphics.DrawString( + Quadrant.ToString(), + new Font(this.Font.FontFamily, 35, GraphicsUnit.Pixel), + Brushes.White, + rect, + new StringFormat() + { + Alignment = StringAlignment.Center, + LineAlignment = StringAlignment.Center + }); + } + + protected override void OnMouseClick(MouseEventArgs e) + { + base.OnMouseClick(e); + + var midx = Width / 2; + var midy = Height / 2; + + if (e.X < midx) + Quadrant = e.Y < midy ? 2 : 3; + else + Quadrant = e.Y < midy ? 1 : 4; + + Invalidate(); + } + } +} diff --git a/Source/OpenNest/Controls/VerticalLine.cs b/Source/OpenNest/Controls/VerticalLine.cs new file mode 100644 index 0000000..449fe70 --- /dev/null +++ b/Source/OpenNest/Controls/VerticalLine.cs @@ -0,0 +1,38 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class VerticalLine : Control + { + private readonly Pen lightPen; + private readonly Pen darkPen; + + public VerticalLine() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.Selectable, false); + + lightPen = new Pen(ProfessionalColors.SeparatorLight); + darkPen = new Pen(ProfessionalColors.SeparatorDark); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + float midpoint = Width * 0.5f; + + e.Graphics.DrawLine(darkPen, midpoint, 0, midpoint, Height); + midpoint++; + e.Graphics.DrawLine(lightPen, midpoint, 0, midpoint, Height); + } + } +} diff --git a/Source/OpenNest/Document.cs b/Source/OpenNest/Document.cs new file mode 100644 index 0000000..6b4b256 --- /dev/null +++ b/Source/OpenNest/Document.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using OpenNest.IO; + +namespace OpenNest +{ + public class Document + { + public Nest Nest { get; set; } + + public DateTime LastSaveDate { get; private set; } + + public string LastSavePath { get; private set; } + + public Units Units { get; set; } + + public void SaveAs(string path) + { + var name = Path.GetFileNameWithoutExtension(path); + Nest.Name = name; + LastSaveDate = DateTime.Now; + LastSavePath = path; + + var writer = new NestWriter(Nest); + writer.Write(path); + } + } +} diff --git a/Source/OpenNest/Forms/AutoNestForm.Designer.cs b/Source/OpenNest/Forms/AutoNestForm.Designer.cs new file mode 100644 index 0000000..14b87c5 --- /dev/null +++ b/Source/OpenNest/Forms/AutoNestForm.Designer.cs @@ -0,0 +1,127 @@ +namespace OpenNest.Forms +{ + partial class AutoNestForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.acceptButton = new System.Windows.Forms.Button(); + this.createNewPlatesAsNeededBox = new System.Windows.Forms.CheckBox(); + this.cancelButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // dataGridView1 + // + this.dataGridView1.AllowUserToDeleteRows = false; + this.dataGridView1.AllowUserToOrderColumns = true; + this.dataGridView1.AllowUserToResizeRows = false; + this.dataGridView1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dataGridView1.Location = new System.Drawing.Point(0, 0); + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.Size = new System.Drawing.Size(545, 385); + this.dataGridView1.TabIndex = 0; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.acceptButton); + this.bottomPanel1.Controls.Add(this.createNewPlatesAsNeededBox); + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 335); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(545, 50); + this.bottomPanel1.TabIndex = 9; + // + // acceptButton + // + this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.acceptButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.acceptButton.Location = new System.Drawing.Point(344, 9); + this.acceptButton.Margin = new System.Windows.Forms.Padding(4); + this.acceptButton.Name = "acceptButton"; + this.acceptButton.Size = new System.Drawing.Size(90, 28); + this.acceptButton.TabIndex = 6; + this.acceptButton.Text = "Accept"; + this.acceptButton.UseVisualStyleBackColor = true; + // + // createNewPlatesAsNeededBox + // + this.createNewPlatesAsNeededBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.createNewPlatesAsNeededBox.AutoSize = true; + this.createNewPlatesAsNeededBox.Location = new System.Drawing.Point(12, 15); + this.createNewPlatesAsNeededBox.Name = "createNewPlatesAsNeededBox"; + this.createNewPlatesAsNeededBox.Size = new System.Drawing.Size(202, 20); + this.createNewPlatesAsNeededBox.TabIndex = 8; + this.createNewPlatesAsNeededBox.Text = "Create new plates as needed"; + this.createNewPlatesAsNeededBox.UseVisualStyleBackColor = true; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(442, 9); + this.cancelButton.Margin = new System.Windows.Forms.Padding(4); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 7; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // AutoNestForm + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(545, 385); + this.Controls.Add(this.bottomPanel1); + this.Controls.Add(this.dataGridView1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "AutoNestForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "AutoNest"; + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + this.bottomPanel1.ResumeLayout(false); + this.bottomPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.DataGridView dataGridView1; + private System.Windows.Forms.Button acceptButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.CheckBox createNewPlatesAsNeededBox; + private Controls.BottomPanel bottomPanel1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/AutoNestForm.cs b/Source/OpenNest/Forms/AutoNestForm.cs new file mode 100644 index 0000000..4018d54 --- /dev/null +++ b/Source/OpenNest/Forms/AutoNestForm.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class AutoNestForm : Form + { + public AutoNestForm(Nest nest) + { + InitializeComponent(); + LoadDrawings(nest); + + dataGridView1.DataError += dataGridView1_DataError; + } + + public bool AllowPlateCreation + { + get { return createNewPlatesAsNeededBox.Checked; } + set { createNewPlatesAsNeededBox.Checked = value; } + } + + private void LoadDrawings(Nest nest) + { + var items = new List(); + dataGridView1.Rows.Clear(); + + foreach (var drawing in nest.Drawings) + items.Add(GetDataGridViewItem(drawing)); + + dataGridView1.DataSource = items; + } + + public List GetNestItems() + { + var nestItems = new List(); + var gridItems = dataGridView1.DataSource as List; + + if (gridItems == null) + return nestItems; + + foreach (var gridItem in gridItems) + { + if (gridItem.Quantity < 1) + continue; + + var nestItem = new NestItem(); + nestItem.Drawing = gridItem.RefDrawing; + nestItem.Priority = gridItem.Priority; + nestItem.Quantity = gridItem.Quantity; + nestItem.RotationEnd = gridItem.RotationEnd; + nestItem.RotationStart = gridItem.RotationStart; + nestItem.StepAngle = gridItem.StepAngle; + + nestItems.Add(nestItem); + } + + return nestItems; + } + + private DataGridViewItem GetDataGridViewItem(Drawing dwg) + { + var item = new DataGridViewItem(); + item.RefDrawing = dwg; + item.Quantity = dwg.Quantity.Remaining > 0 ? dwg.Quantity.Remaining : 0; + item.Priority = dwg.Priority; + item.RotationStart = dwg.Constraints.StartAngle; + item.RotationEnd = dwg.Constraints.EndAngle; + item.StepAngle = dwg.Constraints.StepAngle; + + return item; + } + + private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) + { + MessageBox.Show("Invalid input. Expected input type is " + dataGridView1[e.ColumnIndex, e.RowIndex].ValueType.Name); + } + + private class DataGridViewItem + { + internal Drawing RefDrawing { get; set; } + + [ReadOnly(true)] + [DisplayName("Drawing Name")] + public string DrawingName + { + get { return RefDrawing.Name; } + set { RefDrawing.Name = value; } + } + + public int Quantity { get; set; } + + public int Priority { get; set; } + + [Browsable(false)] // hide until implemented + [DisplayName("Rotation Start")] + public double RotationStart { get; set; } + + [Browsable(false)] // hide until implemented + [DisplayName("Rotation End")] + public double RotationEnd { get; set; } + + [Browsable(false)] // hide until implemented + [DisplayName("Step Angle")] + public double StepAngle { get; set; } + } + } +} diff --git a/Source/OpenNest/Forms/AutoNestForm.resx b/Source/OpenNest/Forms/AutoNestForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/AutoNestForm.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/Source/OpenNest/Forms/CadConverterForm.Designer.cs b/Source/OpenNest/Forms/CadConverterForm.Designer.cs new file mode 100644 index 0000000..6a9bd7c --- /dev/null +++ b/Source/OpenNest/Forms/CadConverterForm.Designer.cs @@ -0,0 +1,293 @@ +namespace OpenNest.Forms +{ + partial class CadConverterForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.checkedListBox1 = new System.Windows.Forms.CheckedListBox(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.checkedListBox2 = new System.Windows.Forms.CheckedListBox(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.checkedListBox3 = new System.Windows.Forms.CheckedListBox(); + this.entityView1 = new OpenNest.Controls.EntityView(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.cancelButton = new System.Windows.Forms.Button(); + this.acceptButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.dataGridView1); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.splitContainer2); + this.splitContainer1.Size = new System.Drawing.Size(928, 479); + this.splitContainer1.SplitterDistance = 225; + this.splitContainer1.TabIndex = 0; + // + // dataGridView1 + // + this.dataGridView1.AllowUserToAddRows = false; + this.dataGridView1.AllowUserToResizeRows = false; + this.dataGridView1.BackgroundColor = System.Drawing.Color.White; + this.dataGridView1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(192))))); + dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.Black; + dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle1; + this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dataGridView1.GridColor = System.Drawing.Color.Gainsboro; + this.dataGridView1.Location = new System.Drawing.Point(0, 0); + this.dataGridView1.MultiSelect = false; + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowTemplate.Height = 26; + this.dataGridView1.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dataGridView1.Size = new System.Drawing.Size(928, 225); + this.dataGridView1.TabIndex = 0; + this.dataGridView1.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(this.dataGridView1_DataBindingComplete); + this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_SelectionChanged); + // + // splitContainer2 + // + this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer2.Location = new System.Drawing.Point(0, 0); + this.splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.tabControl1); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.entityView1); + this.splitContainer2.Size = new System.Drawing.Size(928, 250); + this.splitContainer2.SplitterDistance = 309; + this.splitContainer2.TabIndex = 0; + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(309, 250); + this.tabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.checkedListBox1); + this.tabPage1.Location = new System.Drawing.Point(4, 25); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(301, 221); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Layers"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // checkedListBox1 + // + this.checkedListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.checkedListBox1.CheckOnClick = true; + this.checkedListBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.checkedListBox1.FormattingEnabled = true; + this.checkedListBox1.Location = new System.Drawing.Point(3, 3); + this.checkedListBox1.Name = "checkedListBox1"; + this.checkedListBox1.Size = new System.Drawing.Size(295, 215); + this.checkedListBox1.TabIndex = 0; + this.checkedListBox1.SelectedIndexChanged += new System.EventHandler(this.checkedListBox1_SelectedIndexChanged); + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.checkedListBox2); + this.tabPage2.Location = new System.Drawing.Point(4, 22); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(301, 294); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "Colors"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // checkedListBox2 + // + this.checkedListBox2.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.checkedListBox2.Dock = System.Windows.Forms.DockStyle.Fill; + this.checkedListBox2.FormattingEnabled = true; + this.checkedListBox2.Location = new System.Drawing.Point(3, 3); + this.checkedListBox2.Name = "checkedListBox2"; + this.checkedListBox2.Size = new System.Drawing.Size(295, 288); + this.checkedListBox2.TabIndex = 1; + // + // tabPage3 + // + this.tabPage3.Controls.Add(this.checkedListBox3); + this.tabPage3.Location = new System.Drawing.Point(4, 22); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Padding = new System.Windows.Forms.Padding(3); + this.tabPage3.Size = new System.Drawing.Size(301, 294); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "Line Types"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // checkedListBox3 + // + this.checkedListBox3.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.checkedListBox3.Dock = System.Windows.Forms.DockStyle.Fill; + this.checkedListBox3.FormattingEnabled = true; + this.checkedListBox3.Location = new System.Drawing.Point(3, 3); + this.checkedListBox3.Name = "checkedListBox3"; + this.checkedListBox3.Size = new System.Drawing.Size(295, 288); + this.checkedListBox3.TabIndex = 2; + // + // entityView1 + // + this.entityView1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(40)))), ((int)(((byte)(48))))); + this.entityView1.Cursor = System.Windows.Forms.Cursors.Cross; + this.entityView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.entityView1.Location = new System.Drawing.Point(0, 0); + this.entityView1.Name = "entityView1"; + this.entityView1.Size = new System.Drawing.Size(615, 250); + this.entityView1.TabIndex = 0; + this.entityView1.Text = "entityView1"; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Controls.Add(this.acceptButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 479); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(928, 50); + this.bottomPanel1.TabIndex = 1; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(826, 10); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // acceptButton + // + this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.acceptButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.acceptButton.Location = new System.Drawing.Point(730, 10); + this.acceptButton.Name = "acceptButton"; + this.acceptButton.Size = new System.Drawing.Size(90, 28); + this.acceptButton.TabIndex = 0; + this.acceptButton.Text = "Accept"; + this.acceptButton.UseVisualStyleBackColor = true; + // + // CadConverterForm + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(928, 529); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.MinimizeBox = false; + this.Name = "CadConverterForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "CAD Converter"; + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button acceptButton; + private System.Windows.Forms.Button cancelButton; + private Controls.BottomPanel bottomPanel1; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.DataGridView dataGridView1; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private Controls.EntityView entityView1; + private System.Windows.Forms.CheckedListBox checkedListBox1; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.CheckedListBox checkedListBox2; + private System.Windows.Forms.CheckedListBox checkedListBox3; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/CadConverterForm.cs b/Source/OpenNest/Forms/CadConverterForm.cs new file mode 100644 index 0000000..b731d11 --- /dev/null +++ b/Source/OpenNest/Forms/CadConverterForm.cs @@ -0,0 +1,388 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; +using OpenNest.CNC; +using OpenNest.Geometry; +using OpenNest.IO; +using OpenNest.Properties; +using System; +using System.Threading; + +namespace OpenNest.Forms +{ + public partial class CadConverterForm : Form + { + public CadConverterForm() + { + InitializeComponent(); + + Items = new BindingList(); + dataGridView1.DataSource = Items; + dataGridView1.DataError += dataGridView1_DataError; + } + + private BindingList Items { get; set; } + + private void SetRotation(Shape shape, RotationType rotation) + { + try + { + var dir = shape.ToPolygon(3).RotationDirection(); + + if (dir != RotationType.CW) + shape.Reverse(); + } + catch { } + } + + private void LoadItem(CadConverterItem item) + { + entityView1.Entities.Clear(); + entityView1.Entities.AddRange(item.Entities); + entityView1.ZoomToFit(); + + checkedListBox1.Items.Clear(); + + var layers = item.Entities + .Where(e => e.Layer != null) + .Select(e => e.Layer.Name) + .ToList() + .Distinct(); + + foreach (var layer in layers) + checkedListBox1.Items.Add(layer, true); + } + + private static Color GetNextColor() + { + //if (colorIndex >= Colors.Length) + // colorIndex = 0; + + //var color = Colors[colorIndex]; + + //colorIndex++; + + return new HSLColor(new Random().NextDouble() * 240, 240, 160); + + } + + public List GetDrawings() + { + var drawings = new List(); + + foreach (var item in Items) + { + var entities = item.Entities.Where(e => e.Layer.IsVisible).ToList(); + + if (entities.Count == 0) + continue; + + var drawing = new Drawing(item.Name); + drawing.Color = GetNextColor(); + drawing.Customer = item.Customer; + drawing.Source.Path = item.Path; + drawing.Quantity.Required = item.Quantity; + + var shape = new DefinedShape(entities); + + SetRotation(shape.Perimeter, RotationType.CW); + + foreach (var cutout in shape.Cutouts) + SetRotation(cutout, RotationType.CCW); + + entities = new List(); + entities.AddRange(shape.Perimeter.Entities); + + shape.Cutouts.ForEach(cutout => entities.AddRange(cutout.Entities)); + + var pgm = ConvertGeometry.ToProgram(entities); + var firstCode = pgm[0]; + + if (firstCode.Type == CodeType.RapidMove) + { + var rapid = (RapidMove)firstCode; + + drawing.Source.Offset = rapid.EndPoint; + + pgm.Offset(-rapid.EndPoint); + pgm.Codes.RemoveAt(0); + } + + drawing.Program = pgm; + drawings.Add(drawing); + + Thread.Sleep(20); + } + + return drawings; + } + + private CadConverterItem CurrentItem + { + get + { + return dataGridView1.SelectedRows.Count != 0 + ? Items[dataGridView1.SelectedRows[0].Index] + : null; + } + } + + public void AddFile(string file) + { + var importer = new DxfImporter(); + importer.SplinePrecision = Settings.Default.ImportSplinePrecision; + + var entities = new List(); + + if (!importer.GetGeometry(file, out entities)) + { + MessageBox.Show("Failed to import file \"" + file + "\""); + return; + } + + lock (Items) + { + Items.Add(new CadConverterItem + { + Name = Path.GetFileNameWithoutExtension(file), + Entities = entities, + Path = file, + Quantity = 1, + Customer = string.Empty + }); + } + } + + public void AddFiles(IEnumerable files) + { + Parallel.ForEach(files, AddFile); + } + + private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) + { + MessageBox.Show(e.Exception.Message); + } + + private void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) + { + dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); + } + + private void dataGridView1_SelectionChanged(object sender, System.EventArgs e) + { + var currentItem = CurrentItem; + + if (currentItem != null) + LoadItem(currentItem); + } + + #region Colors + + private static Color[] Colors = new Color[] + { + Color.FromArgb(160, 255, 255), + Color.FromArgb(160, 255, 160), + Color.FromArgb(160, 160, 255), + Color.FromArgb(255, 255, 160), + Color.FromArgb(255, 160, 255), + Color.FromArgb(255, 160, 160), + + Color.FromArgb(200, 255, 255), + Color.FromArgb(200, 255, 200), + Color.FromArgb(200, 200, 255), + Color.FromArgb(255, 255, 200), + Color.FromArgb(255, 200, 255), + Color.FromArgb(255, 200, 200), + }; + + #endregion + + private void checkedListBox1_SelectedIndexChanged(object sender, System.EventArgs e) + { + var index = checkedListBox1.SelectedIndex; + var layerName = checkedListBox1.Items[index].ToString(); + var isVisible = checkedListBox1.CheckedItems.Contains(layerName); + + CurrentItem.Entities.ForEach(entity => + { + if (entity.Layer.Name == layerName) + entity.Layer.IsVisible = isVisible; + }); + + entityView1.Invalidate(); + } + } + + class CadConverterItem + { + public string Name { get; set; } + + public string Customer { get; set; } + + public int Quantity { get; set; } + + [ReadOnly(true)] + public string Path { get; set; } + + [Browsable(false)] + public List Entities { get; set; } + } + + public class RandomColorGenerator + { + private readonly Random random; + + public RandomColorGenerator() + { + random = new Random(); + } + + public Color GetNext() + { + var r = random.Next(255); + Thread.Sleep(20); + var g = random.Next(255); + Thread.Sleep(20); + var b = random.Next(255); + + return Color.FromArgb(r, g, b); + } + } + + public class HSLColor + { + // Private data members below are on scale 0-1 + // They are scaled for use externally based on scale + private double hue = 1.0; + private double saturation = 1.0; + private double luminosity = 1.0; + + private const double scale = 240.0; + + public double Hue + { + get { return hue * scale; } + set { hue = CheckRange(value / scale); } + } + public double Saturation + { + get { return saturation * scale; } + set { saturation = CheckRange(value / scale); } + } + public double Luminosity + { + get { return luminosity * scale; } + set { luminosity = CheckRange(value / scale); } + } + + private double CheckRange(double value) + { + if (value < 0.0) + value = 0.0; + else if (value > 1.0) + value = 1.0; + return value; + } + + public override string ToString() + { + return String.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}", Hue, Saturation, Luminosity); + } + + public string ToRGBString() + { + Color color = (Color)this; + return String.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}", color.R, color.G, color.B); + } + + #region Casts to/from System.Drawing.Color + public static implicit operator Color(HSLColor hslColor) + { + double r = 0, g = 0, b = 0; + if (hslColor.luminosity != 0) + { + if (hslColor.saturation == 0) + r = g = b = hslColor.luminosity; + else + { + double temp2 = GetTemp2(hslColor); + double temp1 = 2.0 * hslColor.luminosity - temp2; + + r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0); + g = GetColorComponent(temp1, temp2, hslColor.hue); + b = GetColorComponent(temp1, temp2, hslColor.hue - 1.0 / 3.0); + } + } + return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b)); + } + + private static double GetColorComponent(double temp1, double temp2, double temp3) + { + temp3 = MoveIntoRange(temp3); + if (temp3 < 1.0 / 6.0) + return temp1 + (temp2 - temp1) * 6.0 * temp3; + else if (temp3 < 0.5) + return temp2; + else if (temp3 < 2.0 / 3.0) + return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0); + else + return temp1; + } + private static double MoveIntoRange(double temp3) + { + if (temp3 < 0.0) + temp3 += 1.0; + else if (temp3 > 1.0) + temp3 -= 1.0; + return temp3; + } + private static double GetTemp2(HSLColor hslColor) + { + double temp2; + if (hslColor.luminosity < 0.5) //<=?? + temp2 = hslColor.luminosity * (1.0 + hslColor.saturation); + else + temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation); + return temp2; + } + + public static implicit operator HSLColor(Color color) + { + HSLColor hslColor = new HSLColor(); + hslColor.hue = color.GetHue() / 360.0; // we store hue as 0-1 as opposed to 0-360 + hslColor.luminosity = color.GetBrightness(); + hslColor.saturation = color.GetSaturation(); + return hslColor; + } + #endregion + + public void SetRGB(int red, int green, int blue) + { + HSLColor hslColor = (HSLColor)Color.FromArgb(red, green, blue); + this.hue = hslColor.hue; + this.saturation = hslColor.saturation; + this.luminosity = hslColor.luminosity; + } + + public HSLColor() { } + public HSLColor(Color color) + { + SetRGB(color.R, color.G, color.B); + } + public HSLColor(int red, int green, int blue) + { + SetRGB(red, green, blue); + } + public HSLColor(double hue, double saturation, double luminosity) + { + this.Hue = hue; + this.Saturation = saturation; + this.Luminosity = luminosity; + } + + } +} diff --git a/Source/OpenNest/Forms/CadConverterForm.resx b/Source/OpenNest/Forms/CadConverterForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/CadConverterForm.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/Source/OpenNest/Forms/CutParametersForm.Designer.cs b/Source/OpenNest/Forms/CutParametersForm.Designer.cs new file mode 100644 index 0000000..8a02808 --- /dev/null +++ b/Source/OpenNest/Forms/CutParametersForm.Designer.cs @@ -0,0 +1,241 @@ +namespace OpenNest.Forms +{ + partial class CutParametersForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.numericUpDown1 = new OpenNest.Controls.NumericUpDown(); + this.numericUpDown2 = new OpenNest.Controls.NumericUpDown(); + this.numericUpDown3 = new OpenNest.Controls.NumericUpDown(); + this.acceptButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown3)).BeginInit(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.label6, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown1, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown2, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown3, 1, 2); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(5); + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(333, 120); + this.tableLayoutPanel1.TabIndex = 0; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 15); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(109, 16); + this.label1.TabIndex = 0; + this.label1.Text = "Feedrate :"; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(9, 51); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(109, 16); + this.label2.TabIndex = 0; + this.label2.Text = "Rapid Feedrate :"; + // + // label6 + // + this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(9, 88); + this.label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(109, 16); + this.label6.TabIndex = 0; + this.label6.Text = "Pierce Time :\r\n"; + // + // numericUpDown1 + // + this.numericUpDown1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown1.Location = new System.Drawing.Point(126, 12); + this.numericUpDown1.Margin = new System.Windows.Forms.Padding(4); + this.numericUpDown1.Maximum = new decimal(new int[] { + 9999, + 0, + 0, + 0}); + this.numericUpDown1.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(198, 22); + this.numericUpDown1.Suffix = ""; + this.numericUpDown1.TabIndex = 0; + this.numericUpDown1.Value = new decimal(new int[] { + 60, + 0, + 0, + 0}); + // + // numericUpDown2 + // + this.numericUpDown2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown2.Location = new System.Drawing.Point(126, 48); + this.numericUpDown2.Margin = new System.Windows.Forms.Padding(4); + this.numericUpDown2.Maximum = new decimal(new int[] { + 9999, + 0, + 0, + 0}); + this.numericUpDown2.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numericUpDown2.Name = "numericUpDown2"; + this.numericUpDown2.Size = new System.Drawing.Size(198, 22); + this.numericUpDown2.Suffix = ""; + this.numericUpDown2.TabIndex = 1; + this.numericUpDown2.Value = new decimal(new int[] { + 1200, + 0, + 0, + 0}); + // + // numericUpDown3 + // + this.numericUpDown3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown3.DecimalPlaces = 1; + this.numericUpDown3.Location = new System.Drawing.Point(126, 85); + this.numericUpDown3.Margin = new System.Windows.Forms.Padding(4); + this.numericUpDown3.Name = "numericUpDown3"; + this.numericUpDown3.Size = new System.Drawing.Size(198, 22); + this.numericUpDown3.Suffix = " seconds"; + this.numericUpDown3.TabIndex = 2; + // + // acceptButton + // + this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.acceptButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.acceptButton.Location = new System.Drawing.Point(132, 11); + this.acceptButton.Margin = new System.Windows.Forms.Padding(4); + this.acceptButton.Name = "acceptButton"; + this.acceptButton.Size = new System.Drawing.Size(90, 28); + this.acceptButton.TabIndex = 8; + this.acceptButton.Text = "Accept"; + this.acceptButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(230, 11); + this.cancelButton.Margin = new System.Windows.Forms.Padding(4); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 9; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.acceptButton); + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 120); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(333, 50); + this.bottomPanel1.TabIndex = 3; + // + // CutParametersForm + // + this.AcceptButton = this.acceptButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(333, 170); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Margin = new System.Windows.Forms.Padding(4); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CutParametersForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Cut Parameters"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown3)).EndInit(); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label6; + private Controls.NumericUpDown numericUpDown1; + private Controls.NumericUpDown numericUpDown2; + private Controls.NumericUpDown numericUpDown3; + private Controls.BottomPanel bottomPanel1; + private System.Windows.Forms.Button acceptButton; + private System.Windows.Forms.Button cancelButton; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/CutParametersForm.cs b/Source/OpenNest/Forms/CutParametersForm.cs new file mode 100644 index 0000000..ae0561d --- /dev/null +++ b/Source/OpenNest/Forms/CutParametersForm.cs @@ -0,0 +1,64 @@ +using System; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class CutParametersForm : Form + { + private Units units; + + public CutParametersForm() + { + InitializeComponent(); + Units = OpenNest.Units.Inches; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + numericUpDown1.Value = Properties.Settings.Default.LastFeedrate; + numericUpDown2.Value = Properties.Settings.Default.LastRapidFeedrate; + numericUpDown3.Value = Properties.Settings.Default.LastPierceTime; + } + + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + base.OnClosing(e); + + Properties.Settings.Default.LastFeedrate = numericUpDown1.Value; + Properties.Settings.Default.LastRapidFeedrate = numericUpDown2.Value; + Properties.Settings.Default.LastPierceTime = numericUpDown3.Value; + Properties.Settings.Default.Save(); + } + + public Units Units + { + get { return units; } + set + { + units = value; + SetUnits(value); + } + } + + private void SetUnits(Units units) + { + var suffix = UnitsHelper.GetShortTimeUnitPair(units); + + numericUpDown1.Suffix = " " + suffix; + numericUpDown2.Suffix = " " + suffix; + } + + public CutParameters GetCutParameters() + { + return new CutParameters() + { + Feedrate = (double)numericUpDown1.Value, + RapidTravelRate = (double)numericUpDown2.Value, + PierceTime = TimeSpan.FromSeconds((double)numericUpDown3.Value), + Units = units + }; + } + } +} diff --git a/Source/OpenNest/Forms/CutParametersForm.resx b/Source/OpenNest/Forms/CutParametersForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/CutParametersForm.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/Source/OpenNest/Forms/EditDrawingForm.Designer.cs b/Source/OpenNest/Forms/EditDrawingForm.Designer.cs new file mode 100644 index 0000000..3d0c08b --- /dev/null +++ b/Source/OpenNest/Forms/EditDrawingForm.Designer.cs @@ -0,0 +1,268 @@ +namespace OpenNest.Forms +{ + partial class EditDrawingForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.label1 = new System.Windows.Forms.Label(); + this.nameBox = new System.Windows.Forms.TextBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.label2 = new System.Windows.Forms.Label(); + this.qtyBox = new OpenNest.Controls.NumericUpDown(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.customerBox = new System.Windows.Forms.TextBox(); + this.priorityBox = new OpenNest.Controls.NumericUpDown(); + this.applyButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.colorDialog1 = new System.Windows.Forms.ColorDialog(); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.qtyBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.priorityBox)).BeginInit(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // pictureBox1 + // + this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBox1.BackColor = System.Drawing.Color.White; + this.pictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.pictureBox1.Location = new System.Drawing.Point(412, 12); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(289, 257); + this.pictureBox1.TabIndex = 1; + this.pictureBox1.TabStop = false; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 12); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(71, 16); + this.label1.TabIndex = 2; + this.label1.Text = "Name :"; + // + // nameBox + // + this.nameBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.nameBox.Location = new System.Drawing.Point(80, 9); + this.nameBox.Name = "nameBox"; + this.nameBox.Size = new System.Drawing.Size(311, 22); + this.nameBox.TabIndex = 3; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.nameBox, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.qtyBox, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.label3, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.label4, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.customerBox, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.priorityBox, 1, 3); + this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 12); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(394, 160); + this.tableLayoutPanel1.TabIndex = 4; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 52); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(71, 16); + this.label2.TabIndex = 2; + this.label2.Text = "Quantity :"; + // + // qtyBox + // + this.qtyBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.qtyBox.Location = new System.Drawing.Point(80, 49); + this.qtyBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.qtyBox.Name = "qtyBox"; + this.qtyBox.Size = new System.Drawing.Size(311, 22); + this.qtyBox.Suffix = ""; + this.qtyBox.TabIndex = 4; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 92); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(71, 16); + this.label3.TabIndex = 2; + this.label3.Text = "Customer :"; + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(3, 132); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(71, 16); + this.label4.TabIndex = 2; + this.label4.Text = "Priority :"; + // + // customerBox + // + this.customerBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.customerBox.Location = new System.Drawing.Point(80, 89); + this.customerBox.Name = "customerBox"; + this.customerBox.Size = new System.Drawing.Size(311, 22); + this.customerBox.TabIndex = 3; + // + // priorityBox + // + this.priorityBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.priorityBox.Location = new System.Drawing.Point(80, 129); + this.priorityBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.priorityBox.Name = "priorityBox"; + this.priorityBox.Size = new System.Drawing.Size(311, 22); + this.priorityBox.Suffix = ""; + this.priorityBox.TabIndex = 4; + // + // applyButton + // + this.applyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.applyButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.applyButton.Location = new System.Drawing.Point(515, 10); + this.applyButton.Name = "applyButton"; + this.applyButton.Size = new System.Drawing.Size(90, 28); + this.applyButton.TabIndex = 5; + this.applyButton.Text = "Done"; + this.applyButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(611, 10); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 6; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.applyButton); + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 298); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(713, 50); + this.bottomPanel1.TabIndex = 0; + // + // colorDialog1 + // + this.colorDialog1.AnyColor = true; + // + // linkLabel1 + // + this.linkLabel1.AutoSize = true; + this.linkLabel1.Location = new System.Drawing.Point(409, 272); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.Size = new System.Drawing.Size(90, 16); + this.linkLabel1.TabIndex = 5; + this.linkLabel1.TabStop = true; + this.linkLabel1.Text = "Change Color"; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // EditDrawingForm + // + this.AcceptButton = this.applyButton; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(713, 348); + this.Controls.Add(this.linkLabel1); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditDrawingForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Drawing"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.qtyBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.priorityBox)).EndInit(); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private Controls.BottomPanel bottomPanel1; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox nameBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label2; + private Controls.NumericUpDown qtyBox; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox customerBox; + private Controls.NumericUpDown priorityBox; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.ColorDialog colorDialog1; + private System.Windows.Forms.LinkLabel linkLabel1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditDrawingForm.cs b/Source/OpenNest/Forms/EditDrawingForm.cs new file mode 100644 index 0000000..cb8ce5a --- /dev/null +++ b/Source/OpenNest/Forms/EditDrawingForm.cs @@ -0,0 +1,84 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class EditDrawingForm : Form + { + private Drawing drawing; + + public EditDrawingForm() + { + InitializeComponent(); + } + + private string DrawingName + { + get { return nameBox.Text; } + set { nameBox.Text = value; } + } + + private string Customer + { + get { return customerBox.Text; } + set { customerBox.Text = value; } + } + + private int Quantity + { + get { return (int)qtyBox.Value; } + set { qtyBox.Value = value; } + } + + private int Priority + { + get { return (int)priorityBox.Value; } + set { priorityBox.Value = value; } + } + + private Image DrawingImage + { + get { return pictureBox1.Image; } + set { pictureBox1.Image = value; } + } + + private void UpdateImage() + { + var brush = new SolidBrush(colorDialog1.Color); + var pen = new Pen(ControlPaint.Dark(colorDialog1.Color)); + DrawingImage = drawing.Program.GetImage(pictureBox1.Size, pen, brush); + + pen.Dispose(); + brush.Dispose(); + } + + public void LoadDrawing(Drawing drawing) + { + this.drawing = drawing; + + colorDialog1.Color = drawing.Color; + DrawingName = drawing.Name; + Customer = drawing.Customer; + Quantity = drawing.Quantity.Required; + Priority = drawing.Priority; + UpdateImage(); + } + + public void SaveDrawing(Drawing drawing) + { + drawing.Name = DrawingName; + drawing.Customer = Customer; + drawing.Quantity.Required = Quantity; + drawing.Priority = Priority; + drawing.Color = colorDialog1.Color; + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + var result = colorDialog1.ShowDialog(); + + if (result == System.Windows.Forms.DialogResult.OK) + UpdateImage(); + } + } +} diff --git a/Source/OpenNest/Forms/EditDrawingForm.resx b/Source/OpenNest/Forms/EditDrawingForm.resx new file mode 100644 index 0000000..aa0ca0f --- /dev/null +++ b/Source/OpenNest/Forms/EditDrawingForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditNestForm.Designer.cs b/Source/OpenNest/Forms/EditNestForm.Designer.cs new file mode 100644 index 0000000..ad9014f --- /dev/null +++ b/Source/OpenNest/Forms/EditNestForm.Designer.cs @@ -0,0 +1,308 @@ +namespace OpenNest.Forms +{ + partial class EditNestForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EditNestForm)); + this.splitContainer = new System.Windows.Forms.SplitContainer(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.platesListView = new System.Windows.Forms.ListView(); + this.plateNumColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.sizeColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.qtyColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.toolStripButtonAddPlate = new System.Windows.Forms.ToolStripButton(); + this.toolStripButtonRemovePlate = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.drawingListBox1 = new OpenNest.Controls.DrawingListBox(); + this.toolStrip2 = new System.Windows.Forms.ToolStrip(); + this.toolStripButton2 = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripButton3 = new System.Windows.Forms.ToolStripButton(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); + this.splitContainer.Panel1.SuspendLayout(); + this.splitContainer.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.toolStrip1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.toolStrip2.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer + // + this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainer.Location = new System.Drawing.Point(0, 0); + this.splitContainer.Name = "splitContainer"; + // + // splitContainer.Panel1 + // + this.splitContainer.Panel1.Controls.Add(this.tabControl1); + this.splitContainer.Size = new System.Drawing.Size(735, 396); + this.splitContainer.SplitterDistance = 241; + this.splitContainer.TabIndex = 8; + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.ItemSize = new System.Drawing.Size(100, 22); + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(241, 396); + this.tabControl1.SizeMode = System.Windows.Forms.TabSizeMode.Fixed; + this.tabControl1.TabIndex = 1; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.platesListView); + this.tabPage1.Controls.Add(this.toolStrip1); + this.tabPage1.Location = new System.Drawing.Point(4, 22); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(233, 370); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Plates"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // platesListView + // + this.platesListView.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.platesListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.plateNumColumn, + this.sizeColumn, + this.qtyColumn}); + this.platesListView.Dock = System.Windows.Forms.DockStyle.Fill; + this.platesListView.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.platesListView.FullRowSelect = true; + this.platesListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.platesListView.HideSelection = false; + this.platesListView.Location = new System.Drawing.Point(3, 34); + this.platesListView.MultiSelect = false; + this.platesListView.Name = "platesListView"; + this.platesListView.Size = new System.Drawing.Size(227, 333); + this.platesListView.TabIndex = 1; + this.platesListView.UseCompatibleStateImageBehavior = false; + this.platesListView.View = System.Windows.Forms.View.Details; + this.platesListView.DoubleClick += new System.EventHandler(this.EditSelectedPlate_Click); + // + // plateNumColumn + // + this.plateNumColumn.Text = ""; + this.plateNumColumn.Width = 30; + // + // sizeColumn + // + this.sizeColumn.Text = "Size"; + this.sizeColumn.Width = 80; + // + // qtyColumn + // + this.qtyColumn.Text = "Qty"; + // + // toolStrip1 + // + this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripButtonAddPlate, + this.toolStripButtonRemovePlate, + this.toolStripSeparator2, + this.toolStripButton1}); + this.toolStrip1.Location = new System.Drawing.Point(3, 3); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Size = new System.Drawing.Size(227, 31); + this.toolStrip1.TabIndex = 2; + this.toolStrip1.Text = "toolStrip1"; + // + // toolStripButtonAddPlate + // + this.toolStripButtonAddPlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripButtonAddPlate.Image = global::OpenNest.Properties.Resources.add; + this.toolStripButtonAddPlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.toolStripButtonAddPlate.Name = "toolStripButtonAddPlate"; + this.toolStripButtonAddPlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.toolStripButtonAddPlate.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.toolStripButtonAddPlate.Size = new System.Drawing.Size(38, 28); + this.toolStripButtonAddPlate.Text = "Add Plate"; + this.toolStripButtonAddPlate.Click += new System.EventHandler(this.AddPlate_Click); + // + // toolStripButtonRemovePlate + // + this.toolStripButtonRemovePlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripButtonRemovePlate.Image = global::OpenNest.Properties.Resources.remove; + this.toolStripButtonRemovePlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.toolStripButtonRemovePlate.Name = "toolStripButtonRemovePlate"; + this.toolStripButtonRemovePlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.toolStripButtonRemovePlate.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.toolStripButtonRemovePlate.Size = new System.Drawing.Size(38, 28); + this.toolStripButtonRemovePlate.Text = "Remove Selected Plate"; + this.toolStripButtonRemovePlate.Click += new System.EventHandler(this.RemoveSelectedPlate_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(6, 31); + // + // toolStripButton1 + // + this.toolStripButton1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripButton1.Image = global::OpenNest.Properties.Resources.clock; + this.toolStripButton1.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton1.Name = "toolStripButton1"; + this.toolStripButton1.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.toolStripButton1.Size = new System.Drawing.Size(38, 28); + this.toolStripButton1.Text = "Calculate Cut Time"; + this.toolStripButton1.Click += new System.EventHandler(this.CalculateSelectedPlateCutTime_Click); + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.drawingListBox1); + this.tabPage2.Controls.Add(this.toolStrip2); + this.tabPage2.Location = new System.Drawing.Point(4, 26); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(233, 366); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "Drawings"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // drawingListBox1 + // + this.drawingListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.drawingListBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.drawingListBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; + this.drawingListBox1.FormattingEnabled = true; + this.drawingListBox1.ItemHeight = 85; + this.drawingListBox1.Location = new System.Drawing.Point(3, 34); + this.drawingListBox1.Name = "drawingListBox1"; + this.drawingListBox1.Size = new System.Drawing.Size(227, 329); + this.drawingListBox1.TabIndex = 1; + this.drawingListBox1.Units = OpenNest.Units.Inches; + this.drawingListBox1.Click += new System.EventHandler(this.drawingListBox1_Click); + this.drawingListBox1.DoubleClick += new System.EventHandler(this.drawingListBox1_DoubleClick); + // + // toolStrip2 + // + this.toolStrip2.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip2.ImageScalingSize = new System.Drawing.Size(20, 20); + this.toolStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripButton2, + this.toolStripSeparator1, + this.toolStripButton3}); + this.toolStrip2.Location = new System.Drawing.Point(3, 3); + this.toolStrip2.Name = "toolStrip2"; + this.toolStrip2.Size = new System.Drawing.Size(227, 31); + this.toolStrip2.TabIndex = 3; + this.toolStrip2.Text = "toolStrip2"; + // + // toolStripButton2 + // + this.toolStripButton2.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripButton2.Image = global::OpenNest.Properties.Resources.import; + this.toolStripButton2.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.toolStripButton2.Name = "toolStripButton2"; + this.toolStripButton2.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.toolStripButton2.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.toolStripButton2.Size = new System.Drawing.Size(38, 28); + this.toolStripButton2.Text = "Import Drawings"; + this.toolStripButton2.Click += new System.EventHandler(this.ImportDrawings_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 31); + // + // toolStripButton3 + // + this.toolStripButton3.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripButton3.Image = global::OpenNest.Properties.Resources.clear; + this.toolStripButton3.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.toolStripButton3.Name = "toolStripButton3"; + this.toolStripButton3.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.toolStripButton3.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.toolStripButton3.Size = new System.Drawing.Size(38, 28); + this.toolStripButton3.Text = "Cleanup unused Drawings"; + this.toolStripButton3.Click += new System.EventHandler(this.CleanUnusedDrawings_Click); + // + // EditNestForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(735, 396); + this.Controls.Add(this.splitContainer); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(200, 198); + this.Name = "EditNestForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "EditForm"; + this.splitContainer.Panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); + this.splitContainer.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage1.PerformLayout(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.tabPage2.ResumeLayout(false); + this.tabPage2.PerformLayout(); + this.toolStrip2.ResumeLayout(false); + this.toolStrip2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.ListView platesListView; + private System.Windows.Forms.ColumnHeader sizeColumn; + private System.Windows.Forms.ColumnHeader qtyColumn; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripButton toolStripButtonAddPlate; + private System.Windows.Forms.ToolStripButton toolStripButtonRemovePlate; + private System.Windows.Forms.ToolStripButton toolStripButton1; + private System.Windows.Forms.TabPage tabPage2; + private Controls.DrawingListBox drawingListBox1; + private System.Windows.Forms.ColumnHeader plateNumColumn; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStrip toolStrip2; + private System.Windows.Forms.ToolStripButton toolStripButton2; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripButton toolStripButton3; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditNestForm.cs b/Source/OpenNest/Forms/EditNestForm.cs new file mode 100644 index 0000000..f6233b5 --- /dev/null +++ b/Source/OpenNest/Forms/EditNestForm.cs @@ -0,0 +1,688 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using OpenNest.Actions; +using OpenNest.Collections; +using OpenNest.Controls; +using OpenNest.IO; +using OpenNest.Properties; +using Timer = System.Timers.Timer; + +namespace OpenNest.Forms +{ + public partial class EditNestForm : Form + { + public event EventHandler PlateChanged; + + public readonly Nest Nest; + public readonly PlateView PlateView; + + private readonly Timer updateDrawingListTimer; + + /// + /// Used to distinguish between single/double click on drawing within drawinglistbox. + /// If double click, this is set to false so the single click action won't be triggered. + /// + private bool addPart; + + private EditNestForm() + { + PlateView = new PlateView(); + PlateView.Enter += PlateView_Enter; + PlateView.PartAdded += PlateView_PartAdded; + PlateView.PartRemoved += PlateView_PartRemoved; + PlateView.Dock = DockStyle.Fill; + + InitializeComponent(); + + splitContainer.Panel2.Controls.Add(PlateView); + + var renderer = new ToolStripRenderer(ToolbarTheme.Toolbar); + toolStrip1.Renderer = renderer; + toolStrip2.Renderer = renderer; + + platesListView.SelectedIndexChanged += (sender, e) => + { + if (platesListView.SelectedIndices.Count == 0) + return; + + CurrentPlateIndex = platesListView.SelectedIndices[0]; + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + PlateView.ZoomToFit(); + FirePlateChanged(false); + }; + } + + public EditNestForm(Nest nest) + : this() + { + updateDrawingListTimer = new Timer() + { + AutoReset = false, + Enabled = true, + Interval = 50 + }; + updateDrawingListTimer.Elapsed += drawingListUpdateTimer_Elapsed; + + Nest = nest; + Nest.Plates.PlateAdded += Plates_PlateAdded; + Nest.Plates.PlateRemoved += Plates_PlateRemoved; + + if (Nest.Plates.Count == 0) + Nest.CreatePlate(); + + UpdatePlateList(); + UpdateDrawingList(); + + LoadFirstPlate(); + + Text = Nest.Name; + drawingListBox1.Units = Nest.Units; + } + + public string LastSavePath { get; private set; } + + public DateTime LastSaveDate { get; private set; } + + public int CurrentPlateIndex { get; private set; } + + public int PlateCount + { + get { return Nest.Plates.Count; } + } + + public void LoadFirstPlate() + { + if (Nest.Plates.Count > 0) + { + CurrentPlateIndex = 0; + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + PlateView.ZoomToFit(); + FirePlateChanged(); + } + } + + public void LoadLastPlate() + { + if (Nest.Plates.Count > 0) + { + CurrentPlateIndex = Nest.Plates.Count - 1; + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + PlateView.ZoomToFit(); + FirePlateChanged(); + } + } + + public bool LoadNextPlate() + { + if (CurrentPlateIndex + 1 >= Nest.Plates.Count) + return false; + + CurrentPlateIndex++; + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + PlateView.ZoomToFit(); + FirePlateChanged(); + + return true; + } + + public bool LoadPreviousPlate() + { + if (Nest.Plates.Count == 0 || CurrentPlateIndex - 1 < 0) + return false; + + CurrentPlateIndex--; + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + PlateView.ZoomToFit(); + FirePlateChanged(); + + return true; + } + + public bool IsFirstPlate() + { + return (Nest.Plates.Count == 0 || (CurrentPlateIndex - 1) < 0); + } + + public bool IsLastPlate() + { + return CurrentPlateIndex + 1 >= Nest.Plates.Count; + } + + public void UpdatePlateList() + { + platesListView.Items.Clear(); + + var items = new ListViewItem[Nest.Plates.Count]; + + for (int i = 0; i < items.Length; ++i) + { + var plate = Nest.Plates[i]; + var item = GetListViewItem(plate, i + 1); + items[i] = item; + } + + platesListView.Items.AddRange(items); + } + + public void UpdateDrawingList() + { + drawingListBox1.Items.Clear(); + + foreach (var dwg in Nest.Drawings.OrderBy(d => d.Name).ToList()) + drawingListBox1.Items.Add(dwg); + } + + public void Save() + { + if (File.Exists(LastSavePath)) + SaveAs(LastSavePath); + else + SaveAs(); + } + + public void SaveAs() + { + var dlg = new SaveFileDialog(); + dlg.Filter = "Nest Files|*.zip|Template File|*.nstdot"; + dlg.FileName = Nest.Name; + + if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + if (dlg.FilterIndex == 2) + SaveTemplate(dlg.FileName); + else + SaveAs(dlg.FileName); + } + } + + public void SaveAs(string path) + { + var name = Path.GetFileNameWithoutExtension(path); + Nest.Name = name; + Text = name; + LastSaveDate = DateTime.Now; + LastSavePath = path; + + var writer = new NestWriter(Nest); + writer.Write(path); + } + + public void SaveTemplate(string path) + { + var nst = new Nest(); + nst.Name = Path.GetFileNameWithoutExtension(path); + nst.PlateDefaults = Nest.PlateDefaults; + nst.Units = Nest.Units; + + var writer = new NestWriter(nst); + writer.Write(path); + } + + public void Import() + { + var dlg = new OpenFileDialog(); + dlg.Multiselect = true; + dlg.Filter = "DXF Files (*.dxf) | *.dxf"; + + if (dlg.ShowDialog() != DialogResult.OK) + return; + + var converter = new CadConverterForm(); + converter.AddFiles(dlg.FileNames); + + var result = converter.ShowDialog(); + + if (result != DialogResult.OK) + return; + + var drawings = converter.GetDrawings(); + drawings.ForEach(d => Nest.Drawings.Add(d)); + + UpdateDrawingList(); + tabControl1.SelectedIndex = 1; + } + + public bool Export() + { + var dlg = new SaveFileDialog(); + dlg.Filter = "DXF file (*.dxf)|*.dxf|" + + "Image as displayed (*.jpg)|*.jpg|" + + "Locations and rotations (*.txt)|*.txt"; + + dlg.FileName = string.Format("{0}-P{1}", Nest.Name, CurrentPlateIndex + 1); + dlg.AddExtension = true; + dlg.DefaultExt = "."; + + if (dlg.ShowDialog() == DialogResult.OK) + { + if (dlg.FilterIndex == 1) + { + var exporter = new DxfExporter(); + var success = exporter.ExportPlate(PlateView.Plate, dlg.FileName); + return success; + } + else if (dlg.FilterIndex == 2) + { + try + { + var img = new Bitmap(PlateView.Width, PlateView.Height); + PlateView.DrawToBitmap(img, new Rectangle(0, 0, PlateView.Width, PlateView.Height)); + img.Save(dlg.FileName); + } + catch { } + + return true; + } + else if (dlg.FilterIndex == 3) + { + StreamWriter writer = null; + + try + { + writer = new StreamWriter(dlg.FileName); + + foreach (var part in PlateView.Plate.Parts) + { + var pt = part.BaseDrawing.Source.Offset.Rotate(part.Rotation); + + writer.WriteLine("{0}|{1},{2}|{3}", + part.BaseDrawing.Source.Path, + Math.Round(part.Location.X - pt.X, 8), + Math.Round(part.Location.Y - pt.Y, 8), + Angle.ToDegrees(part.Rotation)); + } + } + catch { } + finally + { + if (writer != null) + writer.Dispose(); + } + } + } + + return false; + } + + public void ExportAll() + { + LoadFirstPlate(); + + do + { + if (!Export()) return; + } + while (LoadNextPlate()); + } + + public void RotateCw() + { + PlateView.Plate.Rotate90(RotationType.CW); + PlateView.ZoomToFit(); + } + + public void RotateCcw() + { + PlateView.Plate.Rotate90(RotationType.CCW); + PlateView.ZoomToFit(); + } + + public void Rotate180() + { + PlateView.Plate.Rotate180(); + PlateView.ZoomToFit(); + } + + public void ShowNestInfoEditor() + { + var form = new EditNestInfoForm(); + form.LoadNestInfo(Nest); + + if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + form.SaveNestInfo(Nest); + drawingListBox1.Units = Nest.Units; + drawingListBox1.Invalidate(); + } + } + + public void ResizePlateToFitParts() + { + PlateView.Plate.AutoSize(Settings.Default.AutoSizePlateFactor); + PlateView.ZoomToPlate(); + PlateView.Refresh(); + UpdatePlateList(); + } + + public void SelectAllParts() + { + PlateView.SelectAll(); + PlateView.Invalidate(); + } + + public void ToggleRapid() + { + PlateView.DrawRapid = !PlateView.DrawRapid; + PlateView.Invalidate(); + } + + public void ToggleDrawBounds() + { + PlateView.DrawBounds = !PlateView.DrawBounds; + PlateView.Invalidate(); + } + + public void ToggleFillParts() + { + PlateView.FillParts = !PlateView.FillParts; + PlateView.Invalidate(); + } + + public void SetCurrentPlateAsNestDefault() + { + Nest.PlateDefaults.SetFromExisting(PlateView.Plate); + } + + public void EditPlate() + { + var form = new EditPlateForm(PlateView.Plate); + form.Units = Nest.Units; + + if (form.ShowDialog() == DialogResult.OK) + { + PlateView.Invalidate(); + FirePlateChanged(false); + UpdatePlateList(); + } + + Nest.UpdateDrawingQuantities(); + drawingListBox1.Refresh(); + + if (PlateView.Plate.Parts.Count == 0) + Nest.PlateDefaults.SetFromExisting(PlateView.Plate); + } + + /// + /// Opens the current plate as a DXF file by the default program set in Windows. + /// + public void OpenCurrentPlate() + { + var plate = PlateView.Plate; + var name = string.Format("{0}-P{1}.dxf", Nest.Name, CurrentPlateIndex + 1); + var path = Path.Combine(Path.GetTempPath(), name); + var exporter = new DxfExporter(); + exporter.ExportPlate(plate, path); + + Process.Start(path); + } + + public void RemoveCurrentPlate() + { + if (Nest.Plates.Count < 2) + return; + + Nest.Plates.RemoveAt(CurrentPlateIndex); + } + + public void AutoSequenceCurrentPlate() + { + var seq = new SequenceByNearest(); + var parts = seq.SequenceParts(PlateView.Plate.Parts); + + PlateView.Plate.Parts.Clear(); + PlateView.Plate.Parts.AddRange(parts); + } + + public void AutoSequenceAllPlates() + { + var seq = new SequenceByNearest(); + + foreach (var plate in Nest.Plates) + { + var parts = seq.SequenceParts(plate.Parts); + plate.Parts.Clear(); + plate.Parts.AddRange(parts); + } + + PlateView.Invalidate(); + } + + public void CalculateCurrentPlateCutTime() + { + var cutParamsForm = new CutParametersForm(); + cutParamsForm.Units = Nest.Units; + + if (cutParamsForm.ShowDialog() == DialogResult.OK) + { + var cutparams = cutParamsForm.GetCutParameters(); + var info = Timing.GetTimingInfo(PlateView.Plate); + var time = Timing.CalculateTime(info, cutparams); + + var timingForm = new TimingForm(); + timingForm.Units = Nest.Units; + timingForm.SetCutDistance(info.CutDistance); + timingForm.SetCutTime(time); + timingForm.SetIntersectionCount(info.IntersectionCount); + timingForm.SetPierceCount(info.PierceCount); + timingForm.SetRapidDistance(info.TravelDistance); + timingForm.SetCutParameters(cutparams); + + timingForm.ShowDialog(); + } + } + + public void CalculateNestCutTime() + { + var cutParamsForm = new CutParametersForm(); + cutParamsForm.Units = Nest.Units; + + if (cutParamsForm.ShowDialog() == DialogResult.OK) + { + var cutparams = cutParamsForm.GetCutParameters(); + var info = Timing.GetTimingInfo(Nest); + var time = Timing.CalculateTime(info, cutparams); + + var timingForm = new TimingForm(); + timingForm.Units = Nest.Units; + timingForm.SetCutDistance(info.CutDistance); + timingForm.SetCutTime(time); + timingForm.SetIntersectionCount(info.IntersectionCount); + timingForm.SetPierceCount(info.PierceCount); + timingForm.SetRapidDistance(info.TravelDistance); + timingForm.SetCutParameters(cutparams); + + timingForm.ShowDialog(); + } + } + + private void FirePlateChanged(bool updateListView = true) + { + if (updateListView) + platesListView.Items[CurrentPlateIndex].Selected = true; + + if (PlateChanged != null) + PlateChanged.Invoke(this, EventArgs.Empty); + } + + #region Overrides + + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + PlateView.Invalidate(); + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Icon = Icon.Clone() as Icon; + PlateView.DrawBounds = Settings.Default.PlateViewDrawBounds; + PlateView.DrawRapid = Settings.Default.PlateViewDrawRapid; + splitContainer.SplitterDistance = Settings.Default.SplitterDistance; + } + + protected override void OnClosing(CancelEventArgs e) + { + base.OnClosing(e); + + Settings.Default.PlateViewDrawBounds = PlateView.DrawBounds; + Settings.Default.PlateViewDrawRapid = PlateView.DrawRapid; + Settings.Default.SplitterDistance = splitContainer.SplitterDistance; + Settings.Default.Save(); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + PlateView.Invalidate(); + } + + #endregion + + #region Plates/Drawings Panel Events + + private void AddPlate_Click(object sender, EventArgs e) + { + Nest.CreatePlate(); + } + + private void EditSelectedPlate_Click(object sender, EventArgs e) + { + if (platesListView.SelectedIndices.Count != 0) + EditPlate(); + } + + private void RemoveSelectedPlate_Click(object sender, EventArgs e) + { + RemoveCurrentPlate(); + } + + private void CalculateSelectedPlateCutTime_Click(object sender, EventArgs e) + { + CalculateCurrentPlateCutTime(); + } + + private void ImportDrawings_Click(object sender, EventArgs e) + { + Import(); + } + + private void CleanUnusedDrawings_Click(object sender, EventArgs e) + { + var result = MessageBox.Show( + "Remove unused drawings?", + "Clean Drawings", + MessageBoxButtons.YesNoCancel, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button1); + + if (result == DialogResult.Yes) + { + Nest.Drawings.RemoveWhere(d => d.Quantity.Nested == 0); + UpdateDrawingList(); + } + } + + #endregion + + #region Plate Collection Events + + private void Plates_PlateRemoved(object sender, PlateRemovedEventArgs e) + { + if (Nest.Plates.Count <= CurrentPlateIndex) + LoadLastPlate(); + else + PlateView.Plate = Nest.Plates[CurrentPlateIndex]; + + UpdatePlateList(); + PlateView.ZoomToFit(); + } + + private void Plates_PlateAdded(object sender, PlateAddedEventArgs e) + { + tabControl1.SelectedIndex = 0; + UpdatePlateList(); + LoadLastPlate(); + PlateView.ZoomToFit(); + } + + #endregion + + private static ListViewItem GetListViewItem(Plate plate, int id) + { + var item = new ListViewItem(); + item.Text = id.ToString(); + item.SubItems.Add(plate.Size.ToString()); + item.SubItems.Add(plate.Quantity.ToString()); + return item; + } + + private void PlateView_PartRemoved(object sender, PartRemovedEventArgs e) + { + updateDrawingListTimer.Stop(); + updateDrawingListTimer.Start(); + } + + private void PlateView_PartAdded(object sender, PartAddedEventArgs e) + { + updateDrawingListTimer.Stop(); + updateDrawingListTimer.Start(); + } + + private void drawingListUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + drawingListBox1.Invoke(new MethodInvoker(() => + { + drawingListBox1.Refresh(); + })); + } + + private void drawingListBox1_DoubleClick(object sender, EventArgs e) + { + addPart = false; + + var drawing = drawingListBox1.SelectedItem as Drawing; + + if (drawing == null) + return; + + var form = new EditDrawingForm(); + form.LoadDrawing(drawing); + + if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + form.SaveDrawing(drawing); + + foreach (var part in PlateView.Parts) + part.Update(); + + PlateView.Invalidate(); + } + } + + private void drawingListBox1_Click(object sender, EventArgs e) + { + addPart = true; + } + + private void PlateView_Enter(object sender, EventArgs e) + { + if (!addPart) + return; + + var drawing = drawingListBox1.SelectedItem as Drawing; + + if (drawing == null) + return; + + PlateView.SetAction(typeof(ActionAddPart), drawing); + + addPart = false; + } + } +} diff --git a/Source/OpenNest/Forms/EditNestForm.resx b/Source/OpenNest/Forms/EditNestForm.resx new file mode 100644 index 0000000..3739849 --- /dev/null +++ b/Source/OpenNest/Forms/EditNestForm.resx @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + + 122, 17 + + + + + AAABAAUAQEAAAAEAIAAoQgAAVgAAADAwAAABACAAqCUAAH5CAAAgIAAAAQAgAKgQAAAmaAAAGBgAAAEA + IACICQAAzngAABAQAAABACAAaAQAAFaCAAAoAAAAQAAAAIAAAAABACAAAAAAAABCAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAACAAAACQAAAAwAAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAA + AA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAA + AA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAA + AA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAMAAAACAAAAAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAADgAAABUAAAAZAAAAGgAAABsAAAAbAAAAGwAA + ABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAA + ABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAA + ABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAaAAAAGQAA + ABQAAAANAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAADwAAABgAAAAgAAAAJgAA + ACYAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAA + ACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAA + ACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAA + ACkAAAApAAAAJwAAACYAAAAfAAAAFwAAAA0AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAA + ABUAAAAgRUhITV5gYHRdYl93XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxg + XnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxg + XnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxgXnlcYF55XGBeeVxg + XnlcYF55XGBeeVxgXnlcYF55XGBeeV1hX3hfYWF1TE9PUAgICCEAAAATAAAACAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAA0gICAffoOCw4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJ + h/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJ + h/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJ + h/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+EiYf/hImH/4SJh/+BhoXSOzs7JgAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAANd317kIuPjvrU2Nb/8PPy//b39v/29/b/9vf2//b3 + 9v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b3 + 9v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b3 + 9v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//b39v/29/b/9vf2//L0 + 8//a3tz/j5SR+oCFg6gAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4SKiO2+xcH/9ff2/+Xp + 5//d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j + 4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j + 4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j4P/d4+D/3ePg/93j + 4P/d4+D/3ePg/93j4P/j6OX/9Pb1/8zRzv+Fioj7MjIyDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAaEiYf/2t3c/+zv7f/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ + 1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ + 1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ + 1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/+js6v/k5+b/hImH/3d3dyIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAhImH/93g3v/q7ez/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa + 1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa + 1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa + 1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//T2tf/09rX/9Pa1//o6+r/5+no/4SJ + h/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//d4N7/6u7s/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/6Ozq/+fp6P+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/3eDf/+vu + 7P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb + 2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb + 2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb + 2P/V29j/1dvY/9Xb2P/V29j/1dvY/+ns6v/n6ej/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAhImH/93g3//r7u3/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc + 2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc + 2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc + 2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/p7Ov/6Orp/4SJh/+Hh4ceAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//e4d//7O/t/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd + 2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd + 2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd + 2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/193a/9fd2v/X3dr/6u3r/+jq + 6f+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/3uHg/+zv7v/X3tv/197b/9fe + 2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe + 2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe + 2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe + 2//X3tv/197b/+ru7P/o6un/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/97h + 4P/s7+7/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je + 3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je + 3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je + 3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/q7uz/6Orq/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAISJh//e4eD/7fDv/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf + 3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf + 3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf + 3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/6+7t/+jr6v+EiYf/h4eHHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/3+Lg/+3w7//a4N3/2uDd/9rg3f/a4N3/2uDd/9rg + 3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg + 3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg + 3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/9rg3f/a4N3/2uDd/+vv + 7f/p6+r/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/9/i4f/u8O//2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/s7+7/6evq/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJ + h//f4uH/7vHw/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh + 3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh + 3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh + 3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//7O/u/+nr6/+EiYf/h4eHHgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACEiYf/4OLh/+/x8P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i + 4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i + 4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i + 4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/+3w7//q7Ov/hImH/4eH + hx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/+Dj4v/v8fH/3uPh/97j4f/e4+H/3uPh/97j + 4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j + 4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j + 4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j4f/e4+H/3uPh/97j + 4f/u8O//6uzr/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//g4+L/8PHx/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/7vDw/+rs7P+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACEiYf/4ePi//Dy8f/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk + 4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk + 4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk + 4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+/x8P/q7Oz/hImH/4eHhx4AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAhImH/+Hj4v/x8vH/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl + 4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl + 4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl + 4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//v8fD/6+zs/4SJ + h/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//h5OP/8fPy/+Hm5P/h5uT/4ebk/+Hm + 5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm + 5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm + 5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm5P/h5uT/4ebk/+Hm + 5P/h5uT/7/Lx/+vt7P+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/4eTj//Hz + 8v/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl//Dy8f/r7ez/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAhImH/+Lk4//x8/P/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn + 5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn + 5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn + 5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/w8vL/7O3t/4SJh/+Hh4ceAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//i5OP/8vTz/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To + 5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To + 5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To + 5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/8fPy/+zt + 7f+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/4uTj//L08//l6Of/5ejn/+Xo + 5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo + 5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo + 5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo5//l6Of/5ejn/+Xo + 5//l6Of/5ejn//Hz8v/s7e3/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/+Pk + 5P/z9PT/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/y8/P/7O7t/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAISJh//j5OT/8/X0/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq + 6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq + 6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq + 6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/8vTz/+3u7v+EiYf/h4eHHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/4+Tk//T19f/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq + 6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq + 6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq + 6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq//P0 + 9P/t7u7/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/+Tl5P/09fX/6evr/+nr + 6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr + 6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr + 6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr6//p6+v/6evr/+nr + 6//p6+v/6evr/+nr6//z9PT/7e7u/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJ + h//k5eX/9Pb2/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/8/X1/+3u7v+EiYf/h4eHHgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACEiYf/5OXl//X29v/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt + 7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt + 7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt + 7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s//T19f/u7+7/hImH/4eH + hx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhImH/+Tl5f/19vb/6+3t/+vt7f/r7e3/6+3t/+vt + 7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt + 7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt + 7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt + 7f/09fX/7u/v/4SJh/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//k5uX/9vf3/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/9fb2/+7v7/+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACEiYf/5Obl//b39//s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u//X29v/u7+//hImH/4eHhx4AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAhImH/+Tm5f/29/f/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/19vb/7u/v/4SJ + h/+Hh4ceAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//k5uX/9vf3/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/9fb2/+7v7/+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/5Obl//b3 + 9//s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u//X29v/u7+//hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAhImH/+Tm5f/29/f/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/19vb/7u/v/4SJh/+Hh4ceAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAISJh//h4+L/9fb2/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/9PX1/+vs + 7P+EiYf/h4eHHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiYf/3t/f//P19f/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u//P09P/o6en/hImH/4eHhx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi1s5/9K1 + nf/kzbb/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ez + kv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ez + kv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ez + kv/Xs5L/17OS/9ezkv/Xs5L/17OS/9ezkv/jy7P/28Ou/4xbOP+MbF0xAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIZJH//Ko4P/3LiY/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/27aU/9Owkv+GSR//iGhROAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGSR//yJ99/9u1k//PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/9qz + kP/Rq4z/hkkf/4hoUTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhkkf/8Wde//Ys5D/zJtt/8yb + bf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8yb + bf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8yb + bf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8ybbf/Mm23/zJtt/8yb + bf/Mm23/zJtt/8ybbf/XsY3/z6mK/4ZJH/+IaFE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIZJ + H//DmXj/1a6M/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iU + Z//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iU + Z//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/yJRn/8iU + Z//IlGf/yJRn/8iUZ//IlGf/yJRn/8iUZ//IlGf/1KuI/82lh/+GSR//iGhROAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACGSR//wZd1/9Kqh//Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KP + Yf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KP + Yf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KP + Yf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/8KPYf/Cj2H/wo9h/9Cng//Ko4P/hkkf/4Zn + TTEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh0wi/buQbv/QqIb/voha/76IWv++iFr/voha/76I + Wv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76I + Wv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76I + Wv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76IWv++iFr/voha/76I + Wv/Oo4D/xJt6/4ZKH/+HQyEPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIdPJ86jbkj/07CS/8ea + dP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KS + aP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KS + aP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KSaP/Ckmj/wpJo/8KS + aP/Ckmj/wpJo/8KSaP/FmHH/066Q/6t5U/+HTibnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACGRyE1hkof/ax7Vv/LpIX/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9Gt + kP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9Gt + kP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9Gt + kP/RrZD/0a2Q/9GtkP/RrZD/0a2Q/9GtkP/RrZD/zaaH/7KCXv+HSh/9hUkhTAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIdIIF6GSh/1hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJ + H/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJ + H/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJ + H/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSR//hkkf/4ZJH/+GSSD6hkgecwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAikUWC4dHITyGSiBVhkogVYZK + IFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZK + IFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZK + IFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZKIFWGSiBVhkogVYZK + IFWGSB5Cf0gjDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP////////////////////////////////gAAAAAAAAf8AAAAAAAAA/gAAAAAAAAB+AA + AAAAAAAH4AAAAAAAAAfAAAAAAAAAB+AAAAAAAAAH4AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAA + AAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AA + AAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAA + AAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AA + AAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAA + AAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AA + AAAAAAAH8AAAAAAAAAfwAAAAAAAAB/AAAAAAAAAP8AAAAAAAAA/4AAAAAAAAH/wAAAAAAAA///////// + ////////////////////////////////////////////////////////KAAAADAAAABgAAAAAQAgAAAA + AACAJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA7PDwEFRYWCBYWFgoWFxYKFhcWChcYGAoYGBgKGBkYChgZGAoYGRgKGBkYChgZGAoYGRgKGBkYChgZ + GAoYGRgKGBkYChgZGAoYGRgKGBkYChgZGAoYGRgKGBkYChgZGAoYGRgKGBkYChgZGAoYGRgKGBkYChgZ + GAoYGRgKGBkYChgZGAoYGRgKGBgYChcYFwoWFxcKFhcXChYXFgg9Pj4DAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAD5APwoAAAAUAAAAGwAAAB0AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAA + AB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAA + AB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHQAAABoAAAATQEFBCAAA + AAAAAAAAAAAAAAAAAAAAAAAASkxLBAAAABQREhIpKy0tRy4wL08uMC9RLjAvUS4wL1EuMC9RLjAvUS4w + L1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4w + L1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvUS4wL1EuMC9RLjAvTy0u + LkgVFRUpAQEBE0xOTgMAAAAAAAAAAAAAAAAAAAAAICEgCSkqKjxpbWy2eH172Xp/fd15fnzdeX583Xp+ + fN15fnzden583Xl+fN15fnzdeX583Xp+fN15fnzdeX583Xp+fN15fnzdeX583Xp+fN15fnzdeX583Xp+ + fN15fnzdeX583Xp+fN15fnzdeX583Xp+fN15fnzden583Xl+fN15fnzden583Xl+fN15fnzden583Xl+ + fN15fnzden993Xl9fNpscG+8OTo6QiEiIQcAAAAAAAAAAAAAAAAAAAAAIiMiCYGHhby6vrz96ezr//Dy + 8P/v8vD/7/Lw//Dy8P/v8vD/8PLw/+/y8P/v8vD/7/Lw//Dy8P/v8vD/7/Lw//Dy8P/v8vD/7/Lw//Dy + 8P/v8vD/7/Lw//Dy8P/v8vD/7/Lw//Dy8P/v8vD/7/Lw//Dy8P/v8vD/8PLw/+/y8P/v8vD/8PLw/+/y + 8P/v8vD/8PLw/+/y8P/v8vD/8PLw/+rt7P+/w8H9iI2LzCwtLQkAAAAAAAAAAAAAAAAAAAAAW11cBpab + mfje4uD+4OXj/tfe2//X3dr+193a/tfe2//X3dr+197b/9fd2v7X3dr+193a/tfe2//X3dr+193a/tfe + 2//X3dr+193a/tfe2//X3dr+193a/tfe2//X3dr+193a/tfe2//X3dr+193a/tfe2//X3dr+197b/9fd + 2v7X3dr+197b/9fd2v7X3dr+197b/9fd2v7X3dr+197b/9/k4f7i5uT+mZ6c/WNkZBIAAAAAAAAAAAAA + AAAAAAAAlZmXAZqenP/j5uT+2N7c/tLZ1v/S2db+0tnW/tLZ1v/S2db+0tnW/9LZ1v7S2db+0tnW/tLZ + 1v/S2db+0tnW/tLZ1v/S2db+0tnW/tLZ1v/S2db+0tnW/tLZ1v/S2db+0tnW/tLZ1v/S2db+0tnW/tLZ + 1v/S2db+0tnW/9LZ1v7S2db+0tnW/9LZ1v7S2db+0tnW/9LZ1v7S2db+0tnW/9je2/7n6ej+nKCf/4iI + iBcAAAAAAAAAAAAAAAAAAAAAAAAAAJqfnf/j5+X/2d/c/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9nf + 3P/n6un/nKGf/46PjhYAAAAAAAAAAAAAAAAAAAAAAAAAAJqenf/j5uX+2uDd/tXb2P/V29j+1dvY/tXb + 2P/V29j+1dvY/9Xb2P7V29j+1dvY/tXb2P/V29j+1dvY/tXb2P/V29j+1dvY/tXb2P/V29j+1dvY/tXb + 2P/V29j+1dvY/tXb2P/V29j+1dvY/tXb2P/V29j+1dvY/9Xb2P7V29j+1dvY/9Xb2P7V29j+1dvY/9Xb + 2P7V29j+1dvY/9rf3P7o6un+nKGf/5GSkhYAAAAAAAAAAAAAAAAAAAAAAAAAAJqfnf/k5+b/3OHe/9fd + 2v/W3Nn/1tzZ/9fd2v/W3Nn/193a/9bc2f/W3Nn/1tzZ/9fd2v/W3Nn/1tzZ/9fd2v/W3Nn/1tzZ/9fd + 2v/W3Nn/1tzZ/9fd2v/W3Nn/1tzZ/9fd2v/W3Nn/1tzZ/9fd2v/W3Nn/193a/9bc2f/W3Nn/193a/9bc + 2f/W3Nn/193a/9bc2f/W3Nn/193a/9vg3v/p6+r/naGf/5SVlRYAAAAAAAAAAAAAAAAAAAAAAAAAAJqf + nf/k5+b+3OLf/tfe2//X3dv+193b/tfe2//X3dv+197b/9fd2/7X3dv+193b/tfe2//X3dv+193b/tfe + 2//X3dv+193b/tfe2//X3dv+193b/tfe2//X3dv+193b/tfe2//X3dv+193b/tfe2//X3dv+197b/9fd + 2/7X3dv+197b/9fd2/7X3dv+197b/9fd2/7X3dv+197b/9vh3/7o6+r+naGf/5aXlxYAAAAAAAAAAAAA + AAAAAAAAAAAAAJqfnf/l6Of+3eLg/tje3P/Y3tz+2N7c/tje3P/Y3tz+2N7c/9je3P7Y3tz+2N7c/tje + 3P/Y3tz+2N7c/tje3P/Y3tz+2N7c/tje3P/Y3tz+2N7c/tje3P/Y3tz+2N7c/tje3P/Y3tz+2N7c/tje + 3P/Y3tz+2N7c/9je3P7Y3tz+2N7c/9je3P7Y3tz+2N7c/9je3P7Y3tz+2N7c/9zi4P7p7Ov+naGf/5eY + lxYAAAAAAAAAAAAAAAAAAAAAAAAAAJqfnf/l6Of+3uPh/tnf3f/Z39z+2d/c/tnf3f/Z39z+2d/d/9nf + 3P7Z39z+2d/c/tnf3f/Z39z+2d/c/tnf3f/Z39z+2d/c/tnf3f/Z39z+2d/c/tnf3f/Z39z+2d/c/tnf + 3f/Z39z+2d/c/tnf3f/Z39z+2d/d/9nf3P7Z39z+2d/d/9nf3P7Z39z+2d/d/9nf3P7Z39z+2d/d/97j + 4P7p7Ov+naGf/5eYlxYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnf/m6ej/4OTi/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9/k4v/q7ez/naGg/5eYmBYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnf/m6ej+4OXj/tzh + 3//c4d/+3OHf/tzh3//c4d/+3OHf/9zh3/7c4d/+3OHf/tzh3//c4d/+3OHf/tzh3//c4d/+3OHf/tzh + 3//c4d/+3OHf/tzh3//c4d/+3OHf/tzh3//c4d/+3OHf/tzh3//c4d/+3OHf/9zh3/7c4d/+3OHf/9zh + 3/7c4d/+3OHf/9zh3/7c4d/+3OHf/+Dk4/7q7ez+naGg/5eYmBYAAAAAAAAAAAAAAAAAAAAAAAAAAJuf + nf/n6en+4ubk/t3i4P/d4uD+3eLg/t3i4P/d4uD+3eLg/93i4P7d4uD+3eLg/t3i4P/d4uD+3eLg/t3i + 4P/d4uD+3eLg/t3i4P/d4uD+3eLg/t3i4P/d4uD+3eLg/t3i4P/d4uD+3eLg/t3i4P/d4uD+3eLg/93i + 4P7d4uD+3eLg/93i4P7d4uD+3eLg/93i4P7d4uD+3eLg/+Hm5P7r7ez+naGg/5eYmBYAAAAAAAAAAAAA + AAAAAAAAAAAAAJufnv/o6un/4+bl/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/+Pm5f/s7u7/naGg/5iZ + mBYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnf/o6un+5Ofm/uDk4v/g5OL+4OTi/uDk4v/g5OL+4OTi/+Dk + 4v7g5OL+4OTi/uDk4v/g5OL+4OTi/uDk4v/g5OL+4OTi/uDk4v/g5OL+4OTi/uDk4v/g5OL+4OTi/uDk + 4v/g5OL+4OTi/uDk4v/g5OL+4OTi/+Dk4v7g5OL+4OTi/+Dk4v7g5OL+4OTi/+Dk4v7g5OL+4OTi/+Tn + 5f7s7u3+naGg/5iZmBYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnv/o6+r+5Ojn/uHl4//g5eP+4OXj/uHl + 4//g5eP+4eXj/+Dl4/7g5eP+4OXj/uHl4//g5eP+4OXj/uHl4//g5eP+4OXj/uHl4//g5eP+4OXj/uHl + 4//g5eP+4OXj/uHl4//g5eP+4OXj/uHl4//g5eP+4eXj/+Dl4/7g5eP+4eXj/+Dl4/7g5eP+4eXj/+Dl + 4/7g5eP+4eXj/+To5v7s7+7+naKg/5iZmBYAAAAAAAAAAAAAAAAAAAAAAAAAAJugnv/p6+r/5uno/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Xp6P/t7+7/nqKg/5iZmRYAAAAAAAAAAAAAAAAAAAAAAAAAAJuf + nv/p6+r+5urp/uPn5v/j5+b+4+fm/uPn5v/j5+b+4+fm/+Pn5v7j5+b+4+fm/uPn5v/j5+b+4+fm/uPn + 5v/j5+b+4+fm/uPn5v/j5+b+4+fm/uPn5v/j5+b+4+fm/uPn5v/j5+b+4+fm/uPn5v/j5+b+4+fm/+Pn + 5v7j5+b+4+fm/+Pn5v7j5+b+4+fm/+Pn5v7j5+b+4+fm/+bq6f7u7+/+nqKg/5iZmRYAAAAAAAAAAAAA + AAAAAAAAAAAAAJufnv/p6+r+6Orp/uTo5//k5+b+5Ofm/uTo5//k5+b+5Ojn/+Tn5v7k5+b+5Ofm/uTo + 5//k5+b+5Ofm/uTo5//k5+b+5Ofm/uTo5//k5+b+5Ofm/uTo5//k5+b+5Ofm/uTo5//k5+b+5Ofm/uTo + 5//k5+b+5Ojn/+Tn5v7k5+b+5Ojn/+Tn5v7k5+b+5Ojn/+Tn5v7k5+b+5Ojn/+fq6f7u7+/+nqKg/5iZ + mRYAAAAAAAAAAAAAAAAAAAAAAAAAAJygnv/r7Oz/6ezr/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+nr + 6//v8PD/nqKg/5mZmRYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnv/r7Oz+6uzs/ufq6f/n6en+5+np/ufq + 6f/n6en+5+rp/+fp6f7n6en+5+np/ufq6f/n6en+5+np/ufq6f/n6en+5+np/ufq6f/n6en+5+np/ufq + 6f/n6en+5+np/ufq6f/n6en+5+np/ufq6f/n6en+5+rp/+fp6f7n6en+5+rp/+fp6f7n6en+5+rp/+fp + 6f7n6en+5+rp/+rs6/7v8PD+nqKg/5mZmRYAAAAAAAAAAAAAAAAAAAAAAAAAAJygnv/r7Oz+6+3t/ujq + 6v/o6ur+6Orq/ujq6v/o6ur+6Orq/+jq6v7o6ur+6Orq/ujq6v/o6ur+6Orq/ujq6v/o6ur+6Orq/ujq + 6v/o6ur+6Orq/ujq6v/o6ur+6Orq/ujq6v/o6ur+6Orq/ujq6v/o6ur+6Orq/+jq6v7o6ur+6Orq/+jq + 6v7o6ur+6Orq/+jq6v7o6ur+6Orq/+vt7f7v8PD+nqKg/5mamRYAAAAAAAAAAAAAAAAAAAAAAAAAAJyg + nv/s7e3/7O7u/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+vu7v/w8fH/nqKg/5mamhYAAAAAAAAAAAAA + AAAAAAAAAAAAAJygnv/s7e3+7e/u/urt7P/q7Oz+6uzs/urt7P/q7Oz+6u3s/+rs7P7q7Oz+6uzs/urt + 7P/q7Oz+6uzs/urt7P/q7Oz+6uzs/urt7P/q7Oz+6uzs/urt7P/q7Oz+6uzs/urt7P/q7Oz+6uzs/urt + 7P/q7Oz+6u3s/+rs7P7q7Oz+6u3s/+rs7P7q7Oz+6u3s/+rs7P7q7Oz+6u3s/+zu7v7w8fH+nqKg/5ma + mhYAAAAAAAAAAAAAAAAAAAAAAAAAAJygnv/s7u3+7u/v/uvt7f/r7e3+6+3t/uvt7f/r7e3+6+3t/+vt + 7f7r7e3+6+3t/uvt7f/r7e3+6+3t/uvt7f/r7e3+6+3t/uvt7f/r7e3+6+3t/uvt7f/r7e3+6+3t/uvt + 7f/r7e3+6+3t/uvt7f/r7e3+6+3t/+vt7f7r7e3+6+3t/+vt7f7r7e3+6+3t/+vt7f7r7e3+6+3t/+3v + 7/7x8vL+nqKh/5mamhYAAAAAAAAAAAAAAAAAAAAAAAAAAJygnv/t7u7/7vDw/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+7w8P/x8vL/nqKh/5mZmBYAAAAAAAAAAAAAAAAAAAAAAAAAAJygnv/s7u3+7vDw/uzu + 7v/r7e3+6+3t/uzu7v/r7e3+7O7u/+vt7f7r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu + 7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+7O7u/+vt7f7r7e3+7O7u/+vt + 7f7r7e3+7O7u/+vt7f7r7e3+7O7u/+7v7/7x8vL+nqKh/5mYlhYAAAAAAAAAAAAAAAAAAAAAAAAAAJyg + nv/t7u7/7vDw/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+7w8P/x8vL/nqKh/5iWlBYAAAAAAAAAAAAA + AAAAAAAAAAAAAJugnv/s7e3+7vDw/uzu7v/r7e3+6+3t/uzu7v/r7e3+7O7u/+vt7f7r7e3+6+3t/uzu + 7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu + 7v/r7e3+7O7u/+vt7f7r7e3+7O7u/+vt7f7r7e3+7O7u/+vt7f7r7e3+7O7u/+7v7/7w8fH+nqKg/5iU + kRYAAAAAAAAAAAAAAAAAAAAAAAAAAJufnf/p6+r+7e/v/uzu7v/r7e3+6+3t/uzu7v/r7e3+7O7u/+vt + 7f7r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu7v/r7e3+6+3t/uzu + 7v/r7e3+6+3t/uzu7v/r7e3+7O7u/+vt7f7r7e3+7O7u/+vt7f7r7e3+7O7u/+vt7f7r7e3+7O7u/+3v + 7/7u7+/+naGf/5eTjxYAAAAAAAAAAAAAAAAAAAAAAAAAAJx8ZP/ey7n/38ew/9zBqf/cwan/3MGp/9zB + qf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zB + qf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zBqf/cwan/3MGp/9zB + qf/cwan/3MGp/9/Gr//i0MD/n4Bo/5qCdSEAAAAAAAAAAAAAAAAAAAAAAAAAAJdfN//SrIz+0qR6/s+e + cf/OnXD+zp1w/s+ecf/OnXD+z55x/86dcP7OnXD+zp1w/s+ecf/OnXD+zp1w/s+ecf/OnXD+zp1w/s+e + cf/OnXD+zp1w/s+ecf/OnXD+zp1w/s+ecf/OnXD+zp1w/s+ecf/OnXD+z55x/86dcP7OnXD+z55x/86d + cP7OnXD+z55x/86dcP7OnXD+z55x/9Gjef7WsZH+mWI7/5d4YikAAAAAAAAAAAAAAAAAAAAAAAAAAJZe + Nv/PqIb+0KJ3/s2cb//NnG7+zZxu/s2cb//NnG7+zZxv/82cbv7NnG7+zZxu/s2cb//NnG7+zZxu/s2c + b//NnG7+zZxu/s2cb//NnG7+zZxu/s2cb//NnG7+zZxu/s2cb//NnG7+zZxu/s2cb//NnG7+zZxv/82c + bv7NnG7+zZxv/82cbv7NnG7+zZxv/82cbv7NnG7+zZxv/9Chdv7UrYz+mGE6/5V2XykAAAAAAAAAAAAA + AAAAAAAAAAAAAJVdNf/MpIL/zJxx/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mV + aP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mV + aP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8mVaP/JlWj/yZVo/8ybcP/RqYj/mGA5/5V1 + XCkAAAAAAAAAAAAAAAAAAAAAAAAAAJRcNP7In3z+xZRp/sGNX//AjV/+wI1f/sGNX//AjV/+wY1f/8CN + X/7AjV/+wI1f/sGNX//AjV/+wI1f/sGNX//AjV/+wI1f/sGNX//AjV/+wI1f/sGNX//AjV/+wI1f/sGN + X//AjV/+wI1f/sGNX//AjV/+wY1f/8CNX/7AjV/+wY1f/8CNX/7AjV/+wY1f/8CNX/7AjV/+wY1f/8ST + aP7Lo4H+ll83/5NrTx4AAAAAAAAAAAAAAAAAAAAAAAAAAJFZMuzAlXP+xpdw/sCNYf+/jGD+v4xg/sCN + Yf+/jGD+wI1h/7+MYP6/jGD+v4xg/sCNYf+/jGD+v4xg/sCNYf+/jGD+v4xg/sCNYf+/jGD+v4xg/sCN + Yf+/jGD+v4xg/sCNYf+/jGD+v4xg/sCNYf+/jGD+wI1h/7+MYP6/jGD+wI1h/7+MYP6/jGD+wI1h/7+M + YP6/jGD+wI1h/8WWbv7DmXf+klsz9aVxTgUAAAAAAAAAAAAAAAAAAAAAAAAAAIhLJIOhbUf+xZt5/82m + hv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82m + hv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82mhv/Npob/zaaG/82m + hv/Npob/zaaG/82mhv/Npob/zaaG/8ade/+lcUv+iE0klQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx3 + ThGMTyWAhkgeyYZJH9SFSR/UhUkf1IZJH9SFSR/Uhkkf1IVJH9SFSR/UhUkf1IZJH9SFSR/UhUkf1IZJ + H9SFSR/UhUkf1IZJH9SFSR/UhUkf1IZJH9SFSR/UhUkf1IZJH9SFSR/UhUkf1IZJH9SFSR/Uhkkf1IVJ + H9SFSR/Uhkkf1IVJH9SFSR/Uhkkf1IVJH9SFSR/Uhkkf1IVIH8uKTyaKqnZOFQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACocEUCnGI6F5xkOyqcZDsqnGU7Kp1lOyqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5m + PCqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5m + PCqeZjwqnmY8Kp5mPCqeZjwqnmY8Kp5mPCqeZjwqnmY8KpxkOyqcZDsqm2Q7KppjOhqkcEkDAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /////wAA////////AADwAAAAAA8AAOAAAAAABwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAAD + AADAAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAA + AAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAAD + AADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAA + AAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAAD + AADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAcAAOAAAAAABwAA8AAAAAAPAAD///////8AAP// + /////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACstLAUsLS0HLS4uBzAxMAcxMjEHMTIxBzEyMQcxMjEHMTIxBzEy + MQcxMjEHMTIxBzEyMQcxMjEHMTIxBzEyMQcxMjEHMTIxBzEyMQcxMjEHMTIxBzEyMQcwMTEHLjAvBy0u + LgctLi0FAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwOAAAAHQAAACEAAAAiAAAAIgAAACIAAAAiAAAAIgAA + ACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAA + ACIAAAAiAAAAIQAAABwcHR0NAAAAAAAAAAAAAAAAP0FABScoKEVqbm2vcHVyu3B0crxwdHK8cHRyvHB0 + crxwdHK8cHRyvHB0crxwdHK8cHRyvHB0crxwdHK8cHRyvHB0crxwdHK8cHRyvHB0crxwdHK8cHRyvHB0 + crxwdHK8cHRyvHB0crxwdHK7bHBvsDEyMktCQ0IEAAAAAAAAAABFR0YGkZaU3efq6f/p7ev/6e3r/+nt + 6//p7ev/6e3r/+nt6//p7ev/6e3r/+nt6//p7ev/6e3r/+nt6//p7ev/6e3r/+nt6//p7ev/6e3r/+nt + 6//p7ev/6e3r/+nt6//p7ev/6e3r/+nt6//o7Or/mJ2a51NVVAYAAAAAAAAAAJSYlgGvs7L/3uPh/9LZ + 1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ + 1v/S2db/0tnW/9LZ1v/S2db/0tnW/9LZ1v/S2db/0tnW/93i4P+0uLf/ioyLEAAAAAAAAAAAAAAAALC0 + sv/f5OH/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb + 1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/1NvX/9Tb1//U29f/3uPg/7W5t/+WmJcPAAAAAAAA + AAAAAAAAsLSz/+Hl4//W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc + 2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/W3Nn/1tzZ/9bc2f/g5OL/trm4/6Cj + og8AAAAAAAAAAAAAAACxtbP/4ebk/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe + 2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/9fe2//X3tv/197b/+Dm + 4/+2ubj/pqinDwAAAAAAAAAAAAAAALG1s//j5+b/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf + 3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf3f/Z393/2d/d/9nf + 3f/Z393/4ufl/7a6uP+nqagPAAAAAAAAAAAAAAAAsbW0/+To5//b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg3v/b4N7/2+De/9vg + 3v/b4N7/2+De/9vg3v/j5+b/trq4/6iqqQ8AAAAAAAAAAAAAAACytbT/5uno/93i4P/d4uD/3eLg/93i + 4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i4P/d4uD/3eLg/93i + 4P/d4uD/3eLg/93i4P/d4uD/3eLg/+Xp5/+3urn/qKqpDwAAAAAAAAAAAAAAALK2tP/n6un/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j + 4v/f4+L/3+Pi/9/j4v/f4+L/3+Pi/9/j4v/f4+L/5+rp/7e6uf+pq6oPAAAAAAAAAAAAAAAAsra0/+ns + 6v/h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl + 4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//h5eP/4eXj/+Hl4//o6+r/t7q5/6mrqg8AAAAAAAAAAAAA + AACytrX/6ezs/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm + 5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+Lm5f/i5uX/4ubl/+ns6/+3u7n/qqurDwAA + AAAAAAAAAAAAALO2tf/r7u3/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To + 5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/5Ojn/+To5//k6Of/6u3s/7i7 + uv+qrKsPAAAAAAAAAAAAAAAAs7a1/+zv7v/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp6P/m6ej/5uno/+bp + 6P/s7u3/uLu6/6usrA8AAAAAAAAAAAAAAACztrX/7u/v/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq + 6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq6v/o6ur/6Orq/+jq + 6v/o6ur/6Orq/+3v7/+4u7r/q62sDwAAAAAAAAAAAAAAALS3tv/v8fH/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns7P/p7Oz/6ezs/+ns + 7P/p7Oz/6ezs/+ns7P/p7Oz/7vDw/7i7uv+sra0PAAAAAAAAAAAAAAAAtLe2//Dy8v/r7e3/6+3t/+vt + 7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt + 7f/r7e3/6+3t/+vt7f/r7e3/6+3t/+vt7f/w8fH/uby7/6ytrQ8AAAAAAAAAAAAAAAC0t7b/8fLy/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u//Dy8v+5vLv/q6upDwAAAAAAAAAAAAAAALS3 + tv/x8vL/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/8PLy/7m8u/+qp6IPAAAAAAAA + AAAAAAAAs7a1//Dy8v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu7v/w8fH/uLu6/6mi + mw8AAAAAAAAAAAAAAACvno//5tjK/+HQwP/h0MD/4dDA/+HQwP/h0MD/4dDA/+HQwP/h0MD/4dDA/+HQ + wP/h0MD/4dDA/+HQwP/h0MD/4dDA/+HQwP/h0MD/4dDA/+HQwP/h0MD/4dDA/+HQwP/h0MD/4dDA/+bY + yf+0pJX/qJaJEwAAAAAAAAAAAAAAAKd1T//VqoP/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+ecf/PnnH/z55x/8+e + cf/PnnH/1KmB/6x7V/+liHIcAAAAAAAAAAAAAAAApXJM/9CkfP/Kl2r/ypdq/8qXav/Kl2r/ypdq/8qX + av/Kl2r/ypdq/8qXav/Kl2r/ypdq/8qXav/Kl2r/ypdq/8qXav/Kl2r/ypdq/8qXav/Kl2r/ypdq/8qX + av/Kl2r/ypdq/8qXav/Ponr/qnhT/6KCaRwAAAAAAAAAAAAAAACib0n+yJpy/8CLXf/Ai13/wItd/8CL + Xf/Ai13/wItd/8CLXf/Ai13/wItd/8CLXf/Ai13/wItd/8CLXf/Ai13/wItd/8CLXf/Ai13/wItd/8CL + Xf/Ai13/wItd/8CLXf/Ai13/wItd/8eYb/+mdE7/n3RWEAAAAAAAAAAAAAAAAI1TK7/Emnj/yZ98/8mf + fP/Jn3z/yZ98/8mffP/Jn3z/yZ98/8mffP/Jn3z/yZ98/8mffP/Jn3z/yZ98/8mffP/Jn3z/yZ98/8mf + fP/Jn3z/yZ98/8mffP/Jn3z/yZ98/8mffP/Jn3z/xZt5/49WLssAAAAAAAAAAAAAAAAAAAAAqHNKF4dH + HY6GSR+qhkkfqoZJH6qGSR+qhkkfqoZJH6qGSR+qhkkfqoZJH6qGSR+qhkkfqoZJH6qGSR+qhkkfqoZJ + H6qGSR+qhkkfqoZJH6qGSR+qhkkfqoZJH6qGSR+qhkkfqoZJH6qESCCSp3JKHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////+AAAAfAAAADgAAAAYAAAAGAAAABwAAAAcAAAAHAAAABwAAAAcAA + AAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAA + AAHAAAABwAAAA8AAAAP///////////////8oAAAAGAAAADAAAAABACAAAAAAAGAJAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFpdXAIUFBQOCwsLFAsL + CxQMDAwUDAwMFAwMDBQMDAwUDAwMFAwMDBQMDAwUDAwMFAwMDBQMDAwUDAwMFAwMDBQMDAwUDAwMFAsM + DBQLCwsUFRUVDl5gXwIAAAAAAAAAACUmJRdHSkmAVFdWllRXVZdTV1WXVFdVl1RXVZdTV1WXVFdVl1RX + VZdUV1WXU1dVl1RXVZdTV1WXVFdVl1RXVZdTV1WXVFdVl1RXVZdUV1WWSkxLgiorKhgAAAAAAAAAAGVo + Z3HY3Nr+4+jl/+Po5f/j5+X+4+jl/+Po5f/j5+X+4+jl/+Po5f/j6OX/4+fl/uPo5f/j5+X+4+jl/+Po + 5f/j5+X+4+jl/+Po5f/j5+X+29/d/mxvbnkAAAAAAAAAAJufnX/e4uD+09rX/9Pa1//T2tb+09rX/9Pa + 1//T2tb+09rX/9Pa1//T2tf/09rW/tPa1//T2tb+09rX/9Pa1//T2tb+09rX/9Pa1//T2tb+4OTi/5OW + lYoAAAAAAAAAAKmurH/f4+H+1tzZ/tbc2f7W3Nn+1tzZ/tbc2f7W3Nn+1tzZ/tbc2f7W3Nn+1tzZ/tbc + 2f7W3Nn+1tzZ/tbc2f7W3Nn+1tzZ/tbc2f7W3Nn+4eXj/peamYoAAAAAAAAAALO4tn/h5eP+197b/9fe + 2//X3tv+197b/9fe2//X3tv+197b/9fe2//X3tv/197b/tfe2//X3tv+197b/9fe2//X3tv+197b/9fe + 2//X3tv+4ufl/5mcm4oAAAAAAAAAALW6uH/i5uT+2uDd/9rg3f/a393+2uDd/9rg3f/a393+2uDd/9rg + 3f/a4N3/2t/d/trg3f/a393+2uDd/9rg3f/a393+2uDd/9rg3f/a393+5Ojm/5qcm4oAAAAAAAAAALa6 + uX/k5+b+3eLg/t3i4P7d4uD+3eLg/t3i4P7d4uD+3eLg/t3i4P7d4uD+3eLg/t3i4P7d4uD+3eLg/t3i + 4P7d4uD+3eLg/t3i4P7d4uD+5uno/pqdnIoAAAAAAAAAALe7uX/m6Of+3+Pi/9/j4v/f4+L+3+Pi/9/j + 4v/f4+L+3+Pi/9/j4v/f4+L/3+Pi/t/j4v/f4+L+3+Pi/9/j4v/f4+L+3+Pi/9/j4v/f4+L+6Orp/5qd + nIoAAAAAAAAAALi8un/n6un+4ebk/+Hm5P/h5eT+4ebk/+Hm5P/h5eT+4ebk/+Hm5P/h5uT/4eXk/uHm + 5P/h5eT+4ebk/+Hm5P/h5eT+4ebk/+Hm5P/h5eT+6ezr/5udnIoAAAAAAAAAALm9u3/o6+r+5Ofm/+Tn + 5v/k5+b+5Ofm/+Tn5v/k5+b+5Ofm/+Tn5v/k5+b/5Ofm/uTn5v/k5+b+5Ofm/+Tn5v/k5+b+5Ofm/+Tn + 5v/k5+b+6u3s/5udnIoAAAAAAAAAALq9vH/q7Ov+5uno/ubp6P7m6ej+5uno/ubp6P7m6ej+5uno/ubp + 6P7m6ej+5uno/ubp6P7m6ej+5uno/ubp6P7m6ej+5uno/ubp6P7m6ej+7O7t/puenYoAAAAAAAAAALq+ + vX/r7e3+6evr/+nr6//o6+v+6evr/+nr6//o6+v+6evr/+nr6//p6+v/6Ovr/unr6//o6+v+6evr/+nr + 6//o6+v+6evr/+nr6//o6+v+7e/v/5uenYoAAAAAAAAAALu+vX/t7u7+6+3t/uvt7f7r7e3+6+3t/uvt + 7f7r7e3+6+3t/uvt7f7r7e3+6+3t/uvt7f7r7e3+6+3t/uvt7f7r7e3+6+3t/uvt7f7r7e3+7/Dw/pye + nYoAAAAAAAAAALq7uH/t7+/+7O7u/+zu7v/r7e3+7O7u/+zu7v/r7e3+7O7u/+zu7v/s7u7/6+3t/uzu + 7v/r7e3+7O7u/+zu7v/r7e3+7O7u/+zu7v/r7e3+7/Hx/5udnIoAAAAAAAAAALi1rn/t7+7+7O7u/+zu + 7v/r7e3+7O7u/+zu7v/r7e3+7O7u/+zu7v/s7u7/6+3t/uzu7v/r7e3+7O7u/+zu7v/r7e3+7O7u/+zu + 7v/r7e3+7/Hx/5ucmooAAAAAAAAAALWllX/l29H+5NfL/uTXy/7k18v+5NfL/uTXy/7k18v+5NfL/uTX + y/7k18v+5NfL/uTXy/7k18v+5NfL/uTXy/7k18v+5NfL/uTXy/7k18v+593T/puNg40AAAAAAAAAAK6E + ZH/Rp4H+zp1w/86dcP/OnW/+zp1w/86dcP/OnW/+zp1w/86dcP/OnXD/zp1v/s6dcP/OnW/+zp1w/86d + cP/OnW/+zp1w/86dcP/OnW/+06mD/5dsTZQAAAAAAAAAAKp8WX/JnXb+xZFj/8WRY//EkWP+xZFj/8WR + Y//EkWP+xZFj/8WRY//FkWP/xJFj/sWRY//EkWP+xZFj/8WRY//EkWP+xZFj/8WRY//EkWP+y554/5Vo + R5EAAAAAAAAAAKJvSFu7jWn+xplz/saZc/7GmXP+xplz/saZc/7GmXP+xplz/saZc/7GmXP+xplz/saZ + c/7GmXP+xplz/saZc/7GmXP+xplz/saZc/7GmXP+vY9r/p1oQWQAAAAAAAAAALB9VASWWjFZkVYtf5FX + LX+RVy1/klctf5JXLX+RVy1/klctf5JXLX+SVy1/kVctf5JXLX+RVy1/klctf5JXLX+RVy1/klctf5FX + LX+QVi1/k1oyXa98VAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAA + AQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAP// + /wD///8AKAAAABAAAAAgAAAAAQAgAAAAAABABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8Pj0BMjMzAzY3 + NgM3ODcDNzg3Azc4NwM3ODcDNzg3Azc4NwM3ODcDNzg3AzY4NwMzNDQDPkA/AQAAAABlZ2YBKywsSDg6 + OW44OjlvODo5bzg6OW84OjlvODo5bzg6OW84OjlvODo5bzg6OW84OjlvODo5bi4wL0lqbGsBe359AcHG + xPbe4+D/3uPg/97j4P/e4+D/3uPg/97j4P/e4+D/3uPg/97j4P/e4+D/3uPg/97j4P/Eycf5f4GABQAA + AADIzMr/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/1dvY/9Xb2P/V29j/ys7M/6Om + pQcAAAAAyc7M/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/9je3P/Y3tz/2N7c/8vQ + zv+0t7YHAAAAAMvPzf/c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh3//c4d//3OHf/9zh + 3//N0c//trm4BwAAAADN0M//4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk4v/g5OL/4OTi/+Dk + 4v/g5OL/z9LR/7i6uQcAAAAAztHQ/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn5v/j5+b/4+fm/+Pn + 5v/j5+b/4+fm/9DT0v+5u7sHAAAAANDT0v/n6un/5+rp/+fq6f/n6un/5+rp/+fq6f/n6un/5+rp/+fq + 6f/n6un/5+rp/+fq6f/S1dT/u7y8BwAAAADR1NP/6u3s/+rt7P/q7ez/6u3s/+rt7P/q7ez/6u3s/+rt + 7P/q7ez/6u3s/+rt7P/q7ez/1NbV/7y9vQcAAAAA0tXU/+zu7v/s7u7/7O7u/+zu7v/s7u7/7O7u/+zu + 7v/s7u7/7O7u/+zu7v/s7u7/7O7u/9TX1v+6t7MHAAAAAM7HwP/m39f/5t/X/+bf1//m39f/5t/X/+bf + 1//m39f/5t/X/+bf1//m39f/5t/X/+bf1//QysL/tqicCAAAAAC8jWb/zJpt/8yabf/Mmm3/zJpt/8ya + bf/Mmm3/zJpt/8yabf/Mmm3/zJpt/8yabf/Mmm3/vo9p/7CSeg4AAAAAr31X78SVbP/ElWz/xJVs/8SV + bP/ElWz/xJVs/8SVbP/ElWz/xJVs/8SVbP/ElWz/xJVs/7B/WfKvhGMEAAAAAKRsQymbYzpVnGQ7VZ1l + O1WdZTtVnWU7VZ1lO1WdZTtVnWU7VZ1lO1WdZTtVnWU7VZtjOlWibEQrAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAAAAAAAAA + AACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIABAAD//wAA + + + \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditNestInfoForm.Designer.cs b/Source/OpenNest/Forms/EditNestInfoForm.Designer.cs new file mode 100644 index 0000000..77e60b6 --- /dev/null +++ b/Source/OpenNest/Forms/EditNestInfoForm.Designer.cs @@ -0,0 +1,709 @@ +namespace OpenNest.Forms +{ + partial class EditNestInfoForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.nameBox = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.labelEdgeSpacingLeft = new System.Windows.Forms.Label(); + this.labelEdgeSpacingTop = new System.Windows.Forms.Label(); + this.labelEdgeSpacingRight = new System.Windows.Forms.Label(); + this.labelEdgeSpacingBottom = new System.Windows.Forms.Label(); + this.leftSpacingBox = new OpenNest.Controls.NumericUpDown(); + this.topSpacingBox = new OpenNest.Controls.NumericUpDown(); + this.rightSpacingBox = new OpenNest.Controls.NumericUpDown(); + this.bottomSpacingBox = new OpenNest.Controls.NumericUpDown(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.labelSize = new System.Windows.Forms.Label(); + this.sizeBox = new System.Windows.Forms.TextBox(); + this.labelPartSpacing = new System.Windows.Forms.Label(); + this.partSpacingBox = new OpenNest.Controls.NumericUpDown(); + this.labelThk = new System.Windows.Forms.Label(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.quadrantSelect1 = new OpenNest.Controls.QuadrantSelect(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel(); + this.radioButton1 = new System.Windows.Forms.RadioButton(); + this.radioButton2 = new System.Windows.Forms.RadioButton(); + this.label2 = new System.Windows.Forms.Label(); + this.thicknessBox = new OpenNest.Controls.NumericUpDown(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.customerBox = new System.Windows.Forms.TextBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.notesBox = new System.Windows.Forms.TextBox(); + this.cancelButton = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.applyButton = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.leftSpacingBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.topSpacingBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.rightSpacingBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.bottomSpacingBox)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.partSpacingBox)).BeginInit(); + this.groupBox3.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.tableLayoutPanel4.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.thicknessBox)).BeginInit(); + this.tabPage2.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 11); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(126, 16); + this.label1.TabIndex = 0; + this.label1.Text = "Name :"; + // + // nameBox + // + this.nameBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.nameBox.Location = new System.Drawing.Point(135, 8); + this.nameBox.Name = "nameBox"; + this.nameBox.ReadOnly = true; + this.nameBox.Size = new System.Drawing.Size(224, 22); + this.nameBox.TabIndex = 1; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.tableLayoutPanel2); + this.groupBox1.Location = new System.Drawing.Point(6, 100); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(279, 176); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Edge Spacing"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingLeft, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingTop, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingRight, 0, 2); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingBottom, 0, 3); + this.tableLayoutPanel2.Controls.Add(this.leftSpacingBox, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.topSpacingBox, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.rightSpacingBox, 1, 2); + this.tableLayoutPanel2.Controls.Add(this.bottomSpacingBox, 1, 3); + this.tableLayoutPanel2.Location = new System.Drawing.Point(6, 19); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 4; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(267, 151); + this.tableLayoutPanel2.TabIndex = 0; + // + // labelEdgeSpacingLeft + // + this.labelEdgeSpacingLeft.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingLeft.AutoSize = true; + this.labelEdgeSpacingLeft.Location = new System.Drawing.Point(3, 10); + this.labelEdgeSpacingLeft.Name = "labelEdgeSpacingLeft"; + this.labelEdgeSpacingLeft.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingLeft.TabIndex = 0; + this.labelEdgeSpacingLeft.Text = "Left :"; + this.labelEdgeSpacingLeft.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingTop + // + this.labelEdgeSpacingTop.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingTop.AutoSize = true; + this.labelEdgeSpacingTop.Location = new System.Drawing.Point(3, 47); + this.labelEdgeSpacingTop.Name = "labelEdgeSpacingTop"; + this.labelEdgeSpacingTop.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingTop.TabIndex = 2; + this.labelEdgeSpacingTop.Text = "Top :"; + this.labelEdgeSpacingTop.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingRight + // + this.labelEdgeSpacingRight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingRight.AutoSize = true; + this.labelEdgeSpacingRight.Location = new System.Drawing.Point(3, 84); + this.labelEdgeSpacingRight.Name = "labelEdgeSpacingRight"; + this.labelEdgeSpacingRight.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingRight.TabIndex = 4; + this.labelEdgeSpacingRight.Text = "Right :"; + this.labelEdgeSpacingRight.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingBottom + // + this.labelEdgeSpacingBottom.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingBottom.AutoSize = true; + this.labelEdgeSpacingBottom.Location = new System.Drawing.Point(3, 123); + this.labelEdgeSpacingBottom.Name = "labelEdgeSpacingBottom"; + this.labelEdgeSpacingBottom.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingBottom.TabIndex = 6; + this.labelEdgeSpacingBottom.Text = "Bottom :"; + this.labelEdgeSpacingBottom.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // leftSpacingBox + // + this.leftSpacingBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.leftSpacingBox.DecimalPlaces = 4; + this.leftSpacingBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.leftSpacingBox.Location = new System.Drawing.Point(65, 7); + this.leftSpacingBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.leftSpacingBox.Name = "leftSpacingBox"; + this.leftSpacingBox.Size = new System.Drawing.Size(199, 22); + this.leftSpacingBox.Suffix = ""; + this.leftSpacingBox.TabIndex = 1; + // + // topSpacingBox + // + this.topSpacingBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.topSpacingBox.DecimalPlaces = 4; + this.topSpacingBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.topSpacingBox.Location = new System.Drawing.Point(65, 44); + this.topSpacingBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.topSpacingBox.Name = "topSpacingBox"; + this.topSpacingBox.Size = new System.Drawing.Size(199, 22); + this.topSpacingBox.Suffix = ""; + this.topSpacingBox.TabIndex = 3; + // + // rightSpacingBox + // + this.rightSpacingBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.rightSpacingBox.DecimalPlaces = 4; + this.rightSpacingBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.rightSpacingBox.Location = new System.Drawing.Point(65, 81); + this.rightSpacingBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.rightSpacingBox.Name = "rightSpacingBox"; + this.rightSpacingBox.Size = new System.Drawing.Size(199, 22); + this.rightSpacingBox.Suffix = ""; + this.rightSpacingBox.TabIndex = 5; + // + // bottomSpacingBox + // + this.bottomSpacingBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.bottomSpacingBox.DecimalPlaces = 4; + this.bottomSpacingBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.bottomSpacingBox.Location = new System.Drawing.Point(65, 120); + this.bottomSpacingBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.bottomSpacingBox.Name = "bottomSpacingBox"; + this.bottomSpacingBox.Size = new System.Drawing.Size(199, 22); + this.bottomSpacingBox.Suffix = ""; + this.bottomSpacingBox.TabIndex = 7; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.labelSize, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.sizeBox, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.labelPartSpacing, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.partSpacingBox, 1, 1); + this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 6); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(267, 78); + this.tableLayoutPanel1.TabIndex = 2; + // + // labelSize + // + this.labelSize.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelSize.AutoSize = true; + this.labelSize.Location = new System.Drawing.Point(3, 11); + this.labelSize.Name = "labelSize"; + this.labelSize.Size = new System.Drawing.Size(91, 16); + this.labelSize.TabIndex = 0; + this.labelSize.Text = "Size :"; + this.labelSize.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // sizeBox + // + this.sizeBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.sizeBox.Location = new System.Drawing.Point(100, 8); + this.sizeBox.Name = "sizeBox"; + this.sizeBox.Size = new System.Drawing.Size(164, 22); + this.sizeBox.TabIndex = 1; + this.sizeBox.TextChanged += new System.EventHandler(this.sizeBox_TextChanged); + // + // labelPartSpacing + // + this.labelPartSpacing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelPartSpacing.AutoSize = true; + this.labelPartSpacing.Location = new System.Drawing.Point(3, 50); + this.labelPartSpacing.Name = "labelPartSpacing"; + this.labelPartSpacing.Size = new System.Drawing.Size(91, 16); + this.labelPartSpacing.TabIndex = 2; + this.labelPartSpacing.Text = "Part Spacing :"; + this.labelPartSpacing.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // partSpacingBox + // + this.partSpacingBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.partSpacingBox.DecimalPlaces = 4; + this.partSpacingBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.partSpacingBox.Location = new System.Drawing.Point(100, 47); + this.partSpacingBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.partSpacingBox.Name = "partSpacingBox"; + this.partSpacingBox.Size = new System.Drawing.Size(164, 22); + this.partSpacingBox.Suffix = ""; + this.partSpacingBox.TabIndex = 1; + // + // labelThk + // + this.labelThk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelThk.AutoSize = true; + this.labelThk.Location = new System.Drawing.Point(3, 128); + this.labelThk.Name = "labelThk"; + this.labelThk.Size = new System.Drawing.Size(126, 16); + this.labelThk.TabIndex = 6; + this.labelThk.Text = "Thickness :"; + // + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox3.Controls.Add(this.quadrantSelect1); + this.groupBox3.Location = new System.Drawing.Point(306, 6); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(315, 270); + this.groupBox3.TabIndex = 7; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Quadrant"; + // + // quadrantSelect1 + // + this.quadrantSelect1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.quadrantSelect1.BackColor = System.Drawing.Color.White; + this.quadrantSelect1.Location = new System.Drawing.Point(6, 19); + this.quadrantSelect1.Name = "quadrantSelect1"; + this.quadrantSelect1.Quadrant = 1; + this.quadrantSelect1.Size = new System.Drawing.Size(303, 245); + this.quadrantSelect1.TabIndex = 5; + this.quadrantSelect1.Text = "quadrantSelect1"; + // + // tabControl1 + // + this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.ItemSize = new System.Drawing.Size(100, 22); + this.tabControl1.Location = new System.Drawing.Point(12, 12); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(635, 453); + this.tabControl1.SizeMode = System.Windows.Forms.TabSizeMode.Fixed; + this.tabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.tableLayoutPanel3); + this.tabPage1.Location = new System.Drawing.Point(4, 26); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(627, 423); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Info"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 2; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Controls.Add(this.tableLayoutPanel4, 1, 5); + this.tableLayoutPanel3.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel3.Controls.Add(this.nameBox, 1, 0); + this.tableLayoutPanel3.Controls.Add(this.label2, 0, 4); + this.tableLayoutPanel3.Controls.Add(this.labelThk, 0, 3); + this.tableLayoutPanel3.Controls.Add(this.thicknessBox, 1, 3); + this.tableLayoutPanel3.Controls.Add(this.label3, 0, 1); + this.tableLayoutPanel3.Controls.Add(this.label4, 0, 2); + this.tableLayoutPanel3.Controls.Add(this.customerBox, 1, 4); + this.tableLayoutPanel3.Controls.Add(this.textBox1, 1, 1); + this.tableLayoutPanel3.Controls.Add(this.textBox2, 1, 2); + this.tableLayoutPanel3.Controls.Add(this.label5, 0, 5); + this.tableLayoutPanel3.Location = new System.Drawing.Point(6, 6); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 6; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(362, 240); + this.tableLayoutPanel3.TabIndex = 0; + // + // tableLayoutPanel4 + // + this.tableLayoutPanel4.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel4.ColumnCount = 2; + this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel4.Controls.Add(this.radioButton1, 0, 0); + this.tableLayoutPanel4.Controls.Add(this.radioButton2, 1, 0); + this.tableLayoutPanel4.Location = new System.Drawing.Point(135, 198); + this.tableLayoutPanel4.Name = "tableLayoutPanel4"; + this.tableLayoutPanel4.RowCount = 1; + this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel4.Size = new System.Drawing.Size(224, 39); + this.tableLayoutPanel4.TabIndex = 1; + // + // radioButton1 + // + this.radioButton1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.radioButton1.AutoSize = true; + this.radioButton1.Location = new System.Drawing.Point(3, 9); + this.radioButton1.Name = "radioButton1"; + this.radioButton1.Size = new System.Drawing.Size(106, 20); + this.radioButton1.TabIndex = 0; + this.radioButton1.TabStop = true; + this.radioButton1.Text = "Inches"; + this.radioButton1.UseVisualStyleBackColor = true; + this.radioButton1.CheckedChanged += new System.EventHandler(this.Units_Changed); + // + // radioButton2 + // + this.radioButton2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.radioButton2.AutoSize = true; + this.radioButton2.Location = new System.Drawing.Point(115, 9); + this.radioButton2.Name = "radioButton2"; + this.radioButton2.Size = new System.Drawing.Size(106, 20); + this.radioButton2.TabIndex = 0; + this.radioButton2.TabStop = true; + this.radioButton2.Text = "Millimeters"; + this.radioButton2.UseVisualStyleBackColor = true; + this.radioButton2.CheckedChanged += new System.EventHandler(this.Units_Changed); + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 167); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(126, 16); + this.label2.TabIndex = 8; + this.label2.Text = "Customer :"; + // + // thicknessBox + // + this.thicknessBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.thicknessBox.DecimalPlaces = 4; + this.thicknessBox.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.thicknessBox.Location = new System.Drawing.Point(135, 125); + this.thicknessBox.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.thicknessBox.Name = "thicknessBox"; + this.thicknessBox.Size = new System.Drawing.Size(224, 22); + this.thicknessBox.Suffix = ""; + this.thicknessBox.TabIndex = 7; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 50); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(126, 16); + this.label3.TabIndex = 2; + this.label3.Text = "Date Created :"; + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(3, 89); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(126, 16); + this.label4.TabIndex = 4; + this.label4.Text = "Date Last Modified :"; + // + // customerBox + // + this.customerBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.customerBox.Location = new System.Drawing.Point(135, 164); + this.customerBox.Name = "customerBox"; + this.customerBox.Size = new System.Drawing.Size(224, 22); + this.customerBox.TabIndex = 9; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(135, 47); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + this.textBox1.Size = new System.Drawing.Size(224, 22); + this.textBox1.TabIndex = 3; + // + // textBox2 + // + this.textBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.textBox2.Location = new System.Drawing.Point(135, 86); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + this.textBox2.Size = new System.Drawing.Size(224, 22); + this.textBox2.TabIndex = 5; + // + // label5 + // + this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(3, 209); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(126, 16); + this.label5.TabIndex = 8; + this.label5.Text = "Units :"; + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.groupBox3); + this.tabPage2.Controls.Add(this.groupBox1); + this.tabPage2.Controls.Add(this.tableLayoutPanel1); + this.tabPage2.Location = new System.Drawing.Point(4, 26); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(627, 423); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "Defaults"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // tabPage3 + // + this.tabPage3.Controls.Add(this.notesBox); + this.tabPage3.Location = new System.Drawing.Point(4, 26); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Padding = new System.Windows.Forms.Padding(3); + this.tabPage3.Size = new System.Drawing.Size(627, 423); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "Notes"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // notesBox + // + this.notesBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.notesBox.Location = new System.Drawing.Point(6, 6); + this.notesBox.Multiline = true; + this.notesBox.Name = "notesBox"; + this.notesBox.Size = new System.Drawing.Size(615, 408); + this.notesBox.TabIndex = 2; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(557, 10); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.applyButton); + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 471); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(659, 50); + this.bottomPanel1.TabIndex = 1; + // + // applyButton + // + this.applyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.applyButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.applyButton.Enabled = false; + this.applyButton.Location = new System.Drawing.Point(461, 10); + this.applyButton.Name = "applyButton"; + this.applyButton.Size = new System.Drawing.Size(90, 28); + this.applyButton.TabIndex = 0; + this.applyButton.Text = "Done"; + this.applyButton.UseVisualStyleBackColor = true; + // + // EditNestInfoForm + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(659, 521); + this.Controls.Add(this.tabControl1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditNestInfoForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Nest"; + this.groupBox1.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.leftSpacingBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.topSpacingBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.rightSpacingBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.bottomSpacingBox)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.partSpacingBox)).EndInit(); + this.groupBox3.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.tableLayoutPanel4.ResumeLayout(false); + this.tableLayoutPanel4.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.thicknessBox)).EndInit(); + this.tabPage2.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.tabPage3.PerformLayout(); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox nameBox; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.Label labelEdgeSpacingLeft; + private System.Windows.Forms.Label labelEdgeSpacingTop; + private System.Windows.Forms.Label labelEdgeSpacingRight; + private System.Windows.Forms.Label labelEdgeSpacingBottom; + private Controls.NumericUpDown leftSpacingBox; + private Controls.NumericUpDown topSpacingBox; + private Controls.NumericUpDown rightSpacingBox; + private Controls.NumericUpDown bottomSpacingBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label labelSize; + private System.Windows.Forms.TextBox sizeBox; + private Controls.NumericUpDown thicknessBox; + private System.Windows.Forms.Label labelThk; + private System.Windows.Forms.Label labelPartSpacing; + private Controls.NumericUpDown partSpacingBox; + private Controls.BottomPanel bottomPanel1; + private System.Windows.Forms.GroupBox groupBox3; + private Controls.QuadrantSelect quadrantSelect1; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.TextBox notesBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox customerBox; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; + private System.Windows.Forms.RadioButton radioButton1; + private System.Windows.Forms.RadioButton radioButton2; + private System.Windows.Forms.Label label5; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditNestInfoForm.cs b/Source/OpenNest/Forms/EditNestInfoForm.cs new file mode 100644 index 0000000..76b26f4 --- /dev/null +++ b/Source/OpenNest/Forms/EditNestInfoForm.cs @@ -0,0 +1,234 @@ +using System; +using System.Windows.Forms; +using Timer = System.Timers.Timer; + +namespace OpenNest.Forms +{ + public partial class EditNestInfoForm : Form + { + private readonly Timer timer; + private DateTime dateCreated; + private DateTime dateLastModified; + + public EditNestInfoForm() + { + InitializeComponent(); + + timer = new Timer + { + SynchronizingObject = this, + Enabled = true, + AutoReset = false, + Interval = SystemInformation.KeyboardDelay + 1 + }; + timer.Elapsed += (sender, e) => EnableCheck(); + EnableCheck(); + } + + public string NestName + { + get { return nameBox.Text; } + private set { nameBox.Text = value; } + } + + public string Customer + { + get { return customerBox.Text; } + set { customerBox.Text = value; } + } + + public string Notes + { + get { return notesBox.Text; } + set { notesBox.Text = value; } + } + + public string SizeString + { + get { return sizeBox.Text; } + set { sizeBox.Text = value; } + } + + public DateTime DateCreated + { + get { return dateCreated; } + set + { + dateCreated = value; + textBox1.Text = dateCreated.ToShortDateString(); + } + } + + public DateTime DateLastModified + { + get { return dateLastModified; } + set + { + dateLastModified = value; + textBox2.Text = dateLastModified.ToShortDateString(); + } + } + + public double LeftSpacing + { + get { return (double)leftSpacingBox.Value; } + set { leftSpacingBox.Value = (decimal)value; } + } + + public double TopSpacing + { + get { return (double)topSpacingBox.Value; } + set { topSpacingBox.Value = (decimal)value; } + } + + public double RightSpacing + { + get { return (double)rightSpacingBox.Value; } + set { rightSpacingBox.Value = (decimal)value; } + } + + public double BottomSpacing + { + get { return (double)bottomSpacingBox.Value; } + set { bottomSpacingBox.Value = (decimal)value; } + } + + public double PartSpacing + { + get { return (double)partSpacingBox.Value; } + set { partSpacingBox.Value = (decimal)value; } + } + + public double Thickness + { + get { return (double)thicknessBox.Value; } + set { thicknessBox.Value = (decimal)value; } + } + + public void SetUnits(Units units) + { + switch (units) + { + case OpenNest.Units.Inches: + radioButton1.Checked = true; + break; + + case OpenNest.Units.Millimeters: + radioButton2.Checked = true; + break; + } + + UpdateUnitSuffix(); + } + + public Units GetUnits() + { + if (radioButton1.Checked) + return Units.Inches; + + if (radioButton2.Checked) + return Units.Millimeters; + + return OpenNest.Units.Inches; + } + + private void UpdateUnitSuffix() + { + var unitBoxes = new Controls.NumericUpDown[] + { + thicknessBox, + partSpacingBox, + leftSpacingBox, + topSpacingBox, + rightSpacingBox, + bottomSpacingBox + }; + + var unitString = " " + UnitsHelper.GetShortString(GetUnits()); + + foreach (var box in unitBoxes) + box.Suffix = unitString; + } + + public int Quadrant + { + get { return quadrantSelect1.Quadrant; } + set { quadrantSelect1.Quadrant = value; } + } + + public void EnableCheck() + { + Size size; + + if (!OpenNest.Size.TryParse(sizeBox.Text, out size)) + { + applyButton.Enabled = false; + return; + } + + if (LeftSpacing + RightSpacing >= size.Width) + { + applyButton.Enabled = false; + return; + } + + if (TopSpacing + BottomSpacing >= size.Height) + { + applyButton.Enabled = false; + return; + } + + applyButton.Enabled = true; + } + + public void LoadNestInfo(Nest nest) + { + NestName = nest.Name; + Notes = nest.Notes; + Customer = nest.Customer; + DateCreated = nest.DateCreated; + DateLastModified = nest.DateLastModified; + Thickness = nest.PlateDefaults.Thickness; + SizeString = nest.PlateDefaults.Size.ToString(); + PartSpacing = nest.PlateDefaults.PartSpacing; + LeftSpacing = nest.PlateDefaults.EdgeSpacing.Left; + TopSpacing = nest.PlateDefaults.EdgeSpacing.Top; + RightSpacing = nest.PlateDefaults.EdgeSpacing.Right; + BottomSpacing = nest.PlateDefaults.EdgeSpacing.Bottom; + Quadrant = nest.PlateDefaults.Quadrant; + + SetUnits(nest.Units); + } + + public void SaveNestInfo(Nest nest) + { + nest.Name = NestName; + nest.Units = GetUnits(); + nest.Notes = Notes; + nest.Customer = Customer; + nest.DateCreated = DateCreated; + nest.DateLastModified = DateLastModified; + nest.PlateDefaults.Thickness = Thickness; + nest.PlateDefaults.Size = OpenNest.Size.Parse(SizeString); + nest.PlateDefaults.PartSpacing = PartSpacing; + nest.PlateDefaults.EdgeSpacing = new Spacing(LeftSpacing, BottomSpacing, RightSpacing, TopSpacing); + nest.PlateDefaults.Quadrant = Quadrant; + } + + private void sizeBox_TextChanged(object sender, System.EventArgs e) + { + timer.Stop(); + timer.Start(); + } + + private void Units_Changed(object sender, EventArgs e) + { + var radioButton = sender as RadioButton; + + if (radioButton == null || !radioButton.Checked) + return; + + UpdateUnitSuffix(); + } + } +} diff --git a/Source/OpenNest/Forms/EditNestInfoForm.resx b/Source/OpenNest/Forms/EditNestInfoForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/EditNestInfoForm.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/Source/OpenNest/Forms/EditPlateForm.Designer.cs b/Source/OpenNest/Forms/EditPlateForm.Designer.cs new file mode 100644 index 0000000..415b10a --- /dev/null +++ b/Source/OpenNest/Forms/EditPlateForm.Designer.cs @@ -0,0 +1,450 @@ +namespace OpenNest.Forms +{ + partial class EditPlateForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.labelQty = new System.Windows.Forms.Label(); + this.labelSize = new System.Windows.Forms.Label(); + this.textBoxSize = new System.Windows.Forms.TextBox(); + this.labelThk = new System.Windows.Forms.Label(); + this.labelPartSpacing = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.labelEdgeSpacingLeft = new System.Windows.Forms.Label(); + this.labelEdgeSpacingTop = new System.Windows.Forms.Label(); + this.labelEdgeSpacingRight = new System.Windows.Forms.Label(); + this.labelEdgeSpacingBottom = new System.Windows.Forms.Label(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.applyButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.quadrantSelect1 = new OpenNest.Controls.QuadrantSelect(); + this.numericUpDownEdgeSpacingLeft = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownEdgeSpacingTop = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownEdgeSpacingRight = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownEdgeSpacingBottom = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownQty = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownThickness = new OpenNest.Controls.NumericUpDown(); + this.numericUpDownPartSpacing = new OpenNest.Controls.NumericUpDown(); + this.tableLayoutPanel1.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.bottomPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingLeft)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingTop)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingRight)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingBottom)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownQty)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownThickness)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownPartSpacing)).BeginInit(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.labelQty, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.labelSize, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.textBoxSize, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.numericUpDownQty, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.numericUpDownThickness, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.labelThk, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.labelPartSpacing, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.numericUpDownPartSpacing, 1, 3); + this.tableLayoutPanel1.Location = new System.Drawing.Point(18, 12); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(236, 144); + this.tableLayoutPanel1.TabIndex = 0; + // + // labelQty + // + this.labelQty.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelQty.AutoSize = true; + this.labelQty.Location = new System.Drawing.Point(3, 46); + this.labelQty.Name = "labelQty"; + this.labelQty.Size = new System.Drawing.Size(91, 16); + this.labelQty.TabIndex = 2; + this.labelQty.Text = "Quantity :"; + this.labelQty.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelSize + // + this.labelSize.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelSize.AutoSize = true; + this.labelSize.Location = new System.Drawing.Point(3, 10); + this.labelSize.Name = "labelSize"; + this.labelSize.Size = new System.Drawing.Size(91, 16); + this.labelSize.TabIndex = 0; + this.labelSize.Text = "Size :"; + this.labelSize.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // textBoxSize + // + this.textBoxSize.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.textBoxSize.Location = new System.Drawing.Point(100, 7); + this.textBoxSize.Name = "textBoxSize"; + this.textBoxSize.Size = new System.Drawing.Size(133, 22); + this.textBoxSize.TabIndex = 1; + this.textBoxSize.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // labelThk + // + this.labelThk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelThk.AutoSize = true; + this.labelThk.Location = new System.Drawing.Point(3, 82); + this.labelThk.Name = "labelThk"; + this.labelThk.Size = new System.Drawing.Size(91, 16); + this.labelThk.TabIndex = 4; + this.labelThk.Text = "Thickness :"; + this.labelThk.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelPartSpacing + // + this.labelPartSpacing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelPartSpacing.AutoSize = true; + this.labelPartSpacing.Location = new System.Drawing.Point(3, 118); + this.labelPartSpacing.Name = "labelPartSpacing"; + this.labelPartSpacing.Size = new System.Drawing.Size(91, 16); + this.labelPartSpacing.TabIndex = 6; + this.labelPartSpacing.Text = "Part Spacing :"; + this.labelPartSpacing.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.tableLayoutPanel2); + this.groupBox1.Location = new System.Drawing.Point(12, 173); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(248, 175); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Edge Spacing"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingLeft, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingTop, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingRight, 0, 2); + this.tableLayoutPanel2.Controls.Add(this.labelEdgeSpacingBottom, 0, 3); + this.tableLayoutPanel2.Controls.Add(this.numericUpDownEdgeSpacingLeft, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.numericUpDownEdgeSpacingTop, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.numericUpDownEdgeSpacingRight, 1, 2); + this.tableLayoutPanel2.Controls.Add(this.numericUpDownEdgeSpacingBottom, 1, 3); + this.tableLayoutPanel2.Location = new System.Drawing.Point(6, 21); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 4; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(236, 148); + this.tableLayoutPanel2.TabIndex = 0; + // + // labelEdgeSpacingLeft + // + this.labelEdgeSpacingLeft.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingLeft.AutoSize = true; + this.labelEdgeSpacingLeft.Location = new System.Drawing.Point(3, 10); + this.labelEdgeSpacingLeft.Name = "labelEdgeSpacingLeft"; + this.labelEdgeSpacingLeft.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingLeft.TabIndex = 0; + this.labelEdgeSpacingLeft.Text = "Left :"; + this.labelEdgeSpacingLeft.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingTop + // + this.labelEdgeSpacingTop.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingTop.AutoSize = true; + this.labelEdgeSpacingTop.Location = new System.Drawing.Point(3, 47); + this.labelEdgeSpacingTop.Name = "labelEdgeSpacingTop"; + this.labelEdgeSpacingTop.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingTop.TabIndex = 2; + this.labelEdgeSpacingTop.Text = "Top :"; + this.labelEdgeSpacingTop.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingRight + // + this.labelEdgeSpacingRight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingRight.AutoSize = true; + this.labelEdgeSpacingRight.Location = new System.Drawing.Point(3, 84); + this.labelEdgeSpacingRight.Name = "labelEdgeSpacingRight"; + this.labelEdgeSpacingRight.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingRight.TabIndex = 4; + this.labelEdgeSpacingRight.Text = "Right :"; + this.labelEdgeSpacingRight.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // labelEdgeSpacingBottom + // + this.labelEdgeSpacingBottom.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelEdgeSpacingBottom.AutoSize = true; + this.labelEdgeSpacingBottom.Location = new System.Drawing.Point(3, 121); + this.labelEdgeSpacingBottom.Name = "labelEdgeSpacingBottom"; + this.labelEdgeSpacingBottom.Size = new System.Drawing.Size(56, 16); + this.labelEdgeSpacingBottom.TabIndex = 6; + this.labelEdgeSpacingBottom.Text = "Bottom :"; + this.labelEdgeSpacingBottom.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.quadrantSelect1); + this.groupBox2.Location = new System.Drawing.Point(277, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(261, 209); + this.groupBox2.TabIndex = 6; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Quadrant"; + // + // applyButton + // + this.applyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.applyButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.applyButton.Enabled = false; + this.applyButton.Location = new System.Drawing.Point(352, 11); + this.applyButton.Name = "applyButton"; + this.applyButton.Size = new System.Drawing.Size(90, 28); + this.applyButton.TabIndex = 3; + this.applyButton.Text = "Done"; + this.applyButton.UseVisualStyleBackColor = true; + this.applyButton.Click += new System.EventHandler(this.button1_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(448, 11); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.applyButton); + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 359); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(550, 50); + this.bottomPanel1.TabIndex = 7; + // + // quadrantSelect1 + // + this.quadrantSelect1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.quadrantSelect1.Location = new System.Drawing.Point(6, 19); + this.quadrantSelect1.Name = "quadrantSelect1"; + this.quadrantSelect1.Quadrant = 1; + this.quadrantSelect1.Size = new System.Drawing.Size(249, 184); + this.quadrantSelect1.TabIndex = 5; + this.quadrantSelect1.Text = "quadrantSelect1"; + // + // numericUpDownEdgeSpacingLeft + // + this.numericUpDownEdgeSpacingLeft.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownEdgeSpacingLeft.DecimalPlaces = 4; + this.numericUpDownEdgeSpacingLeft.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownEdgeSpacingLeft.Location = new System.Drawing.Point(65, 7); + this.numericUpDownEdgeSpacingLeft.Name = "numericUpDownEdgeSpacingLeft"; + this.numericUpDownEdgeSpacingLeft.Size = new System.Drawing.Size(168, 22); + this.numericUpDownEdgeSpacingLeft.Suffix = ""; + this.numericUpDownEdgeSpacingLeft.TabIndex = 1; + this.numericUpDownEdgeSpacingLeft.ValueChanged += new System.EventHandler(this.spacing_ValueChanged); + // + // numericUpDownEdgeSpacingTop + // + this.numericUpDownEdgeSpacingTop.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownEdgeSpacingTop.DecimalPlaces = 4; + this.numericUpDownEdgeSpacingTop.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownEdgeSpacingTop.Location = new System.Drawing.Point(65, 44); + this.numericUpDownEdgeSpacingTop.Name = "numericUpDownEdgeSpacingTop"; + this.numericUpDownEdgeSpacingTop.Size = new System.Drawing.Size(168, 22); + this.numericUpDownEdgeSpacingTop.Suffix = ""; + this.numericUpDownEdgeSpacingTop.TabIndex = 3; + this.numericUpDownEdgeSpacingTop.ValueChanged += new System.EventHandler(this.spacing_ValueChanged); + // + // numericUpDownEdgeSpacingRight + // + this.numericUpDownEdgeSpacingRight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownEdgeSpacingRight.DecimalPlaces = 4; + this.numericUpDownEdgeSpacingRight.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownEdgeSpacingRight.Location = new System.Drawing.Point(65, 81); + this.numericUpDownEdgeSpacingRight.Name = "numericUpDownEdgeSpacingRight"; + this.numericUpDownEdgeSpacingRight.Size = new System.Drawing.Size(168, 22); + this.numericUpDownEdgeSpacingRight.Suffix = ""; + this.numericUpDownEdgeSpacingRight.TabIndex = 5; + this.numericUpDownEdgeSpacingRight.ValueChanged += new System.EventHandler(this.spacing_ValueChanged); + // + // numericUpDownEdgeSpacingBottom + // + this.numericUpDownEdgeSpacingBottom.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownEdgeSpacingBottom.DecimalPlaces = 4; + this.numericUpDownEdgeSpacingBottom.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownEdgeSpacingBottom.Location = new System.Drawing.Point(65, 118); + this.numericUpDownEdgeSpacingBottom.Name = "numericUpDownEdgeSpacingBottom"; + this.numericUpDownEdgeSpacingBottom.Size = new System.Drawing.Size(168, 22); + this.numericUpDownEdgeSpacingBottom.Suffix = ""; + this.numericUpDownEdgeSpacingBottom.TabIndex = 7; + this.numericUpDownEdgeSpacingBottom.ValueChanged += new System.EventHandler(this.spacing_ValueChanged); + // + // numericUpDownQty + // + this.numericUpDownQty.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownQty.Location = new System.Drawing.Point(100, 43); + this.numericUpDownQty.Name = "numericUpDownQty"; + this.numericUpDownQty.Size = new System.Drawing.Size(133, 22); + this.numericUpDownQty.Suffix = ""; + this.numericUpDownQty.TabIndex = 3; + // + // numericUpDownThickness + // + this.numericUpDownThickness.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownThickness.DecimalPlaces = 4; + this.numericUpDownThickness.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownThickness.Location = new System.Drawing.Point(100, 79); + this.numericUpDownThickness.Name = "numericUpDownThickness"; + this.numericUpDownThickness.Size = new System.Drawing.Size(133, 22); + this.numericUpDownThickness.Suffix = ""; + this.numericUpDownThickness.TabIndex = 5; + // + // numericUpDownPartSpacing + // + this.numericUpDownPartSpacing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownPartSpacing.DecimalPlaces = 4; + this.numericUpDownPartSpacing.Increment = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + this.numericUpDownPartSpacing.Location = new System.Drawing.Point(100, 115); + this.numericUpDownPartSpacing.Name = "numericUpDownPartSpacing"; + this.numericUpDownPartSpacing.Size = new System.Drawing.Size(133, 22); + this.numericUpDownPartSpacing.Suffix = ""; + this.numericUpDownPartSpacing.TabIndex = 7; + // + // EditPlateForm + // + this.AcceptButton = this.applyButton; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(550, 409); + this.Controls.Add(this.bottomPanel1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.tableLayoutPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditPlateForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Plate"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.bottomPanel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingLeft)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingTop)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingRight)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownEdgeSpacingBottom)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownQty)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownThickness)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownPartSpacing)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label labelSize; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.TextBox textBoxSize; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.Label labelEdgeSpacingLeft; + private System.Windows.Forms.Label labelEdgeSpacingTop; + private System.Windows.Forms.Label labelEdgeSpacingRight; + private System.Windows.Forms.Label labelEdgeSpacingBottom; + private Controls.NumericUpDown numericUpDownEdgeSpacingLeft; + private Controls.NumericUpDown numericUpDownEdgeSpacingTop; + private Controls.NumericUpDown numericUpDownEdgeSpacingRight; + private Controls.NumericUpDown numericUpDownEdgeSpacingBottom; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Label labelQty; + private Controls.NumericUpDown numericUpDownQty; + private Controls.QuadrantSelect quadrantSelect1; + private System.Windows.Forms.GroupBox groupBox2; + private Controls.NumericUpDown numericUpDownThickness; + private System.Windows.Forms.Label labelThk; + private System.Windows.Forms.Label labelPartSpacing; + private Controls.NumericUpDown numericUpDownPartSpacing; + private Controls.BottomPanel bottomPanel1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/EditPlateForm.cs b/Source/OpenNest/Forms/EditPlateForm.cs new file mode 100644 index 0000000..112afad --- /dev/null +++ b/Source/OpenNest/Forms/EditPlateForm.cs @@ -0,0 +1,198 @@ +using System; +using System.Windows.Forms; +using Timer = System.Timers.Timer; + +namespace OpenNest.Forms +{ + public partial class EditPlateForm : Form + { + private readonly Plate plate; + private readonly Timer timer; + + private Units units; + + public EditPlateForm(Plate plate) + { + InitializeComponent(); + + this.plate = plate; + + timer = new Timer + { + SynchronizingObject = this, + Enabled = true, + AutoReset = false, + Interval = SystemInformation.KeyboardDelay + 1 + }; + timer.Elapsed += (sender, e) => EnableCheck(); + + Load(plate); + EnableCheck(); + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + SetUnits(units); + } + + public Units Units + { + get { return units; } + set + { + units = value; + SetUnits(value); + } + } + + private void SetUnits(Units units) + { + string suffix = " " + UnitsHelper.GetShortString(units); + decimal increment; + + if (units == Units.Inches) + increment = 0.25m; + else + increment = 1; + + var controls = new [] + { + numericUpDownThickness, + numericUpDownPartSpacing, + numericUpDownEdgeSpacingBottom, + numericUpDownEdgeSpacingLeft, + numericUpDownEdgeSpacingRight, + numericUpDownEdgeSpacingTop + }; + + foreach (var control in controls) + { + control.Suffix = suffix; + control.Increment = increment; + control.Invalidate(true); + } + } + + public string SizeString + { + get { return textBoxSize.Text; } + set { textBoxSize.Text = value; } + } + + public double LeftSpacing + { + get { return (double)numericUpDownEdgeSpacingLeft.Value; } + set { numericUpDownEdgeSpacingLeft.Value = (decimal)value; } + } + + public double TopSpacing + { + get { return (double)numericUpDownEdgeSpacingTop.Value; } + set { numericUpDownEdgeSpacingTop.Value = (decimal)value; } + } + + public double RightSpacing + { + get { return (double)numericUpDownEdgeSpacingRight.Value; } + set { numericUpDownEdgeSpacingRight.Value = (decimal)value; } + } + + public double BottomSpacing + { + get { return (double)numericUpDownEdgeSpacingBottom.Value; } + set { numericUpDownEdgeSpacingBottom.Value = (decimal)value; } + } + + public double PartSpacing + { + get { return (double)numericUpDownPartSpacing.Value; } + set { numericUpDownPartSpacing.Value = (decimal)value; } + } + + public double Thickness + { + get { return (double)numericUpDownThickness.Value; } + set { numericUpDownThickness.Value = (decimal)value; } + } + + public int Quantity + { + get { return (int)numericUpDownQty.Value; } + set { numericUpDownQty.Value = value; } + } + + public int Quadrant + { + get { return quadrantSelect1.Quadrant; } + set { quadrantSelect1.Quadrant = value; } + } + + public void EnableCheck() + { + OpenNest.Size size; + + if (!OpenNest.Size.TryParse(SizeString, out size)) + { + applyButton.Enabled = false; + return; + } + + if (LeftSpacing + RightSpacing >= size.Width) + { + applyButton.Enabled = false; + return; + } + + if (TopSpacing + BottomSpacing >= size.Height) + { + applyButton.Enabled = false; + return; + } + + applyButton.Enabled = true; + } + + private new void Load(Plate plate) + { + textBoxSize.Text = plate.Size.ToString(); + LeftSpacing = plate.EdgeSpacing.Left; + TopSpacing = plate.EdgeSpacing.Top; + RightSpacing = plate.EdgeSpacing.Right; + BottomSpacing = plate.EdgeSpacing.Bottom; + PartSpacing = plate.PartSpacing; + Quantity = plate.Quantity; + Quadrant = plate.Quadrant; + Thickness = plate.Thickness; + } + + private void Save() + { + plate.Size = OpenNest.Size.Parse(SizeString); + plate.EdgeSpacing.Left = LeftSpacing; + plate.EdgeSpacing.Top = TopSpacing; + plate.EdgeSpacing.Right = RightSpacing; + plate.EdgeSpacing.Bottom = BottomSpacing; + plate.PartSpacing = PartSpacing; + plate.Quantity = Quantity; + plate.Quadrant = Quadrant; + plate.Thickness = Thickness; + } + + private void textBox1_TextChanged(object sender, EventArgs e) + { + timer.Stop(); + timer.Start(); + } + + private void button1_Click(object sender, EventArgs e) + { + Save(); + } + + private void spacing_ValueChanged(object sender, EventArgs e) + { + EnableCheck(); + } + } +} diff --git a/Source/OpenNest/Forms/EditPlateForm.resx b/Source/OpenNest/Forms/EditPlateForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/EditPlateForm.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/Source/OpenNest/Forms/FillPlateForm.Designer.cs b/Source/OpenNest/Forms/FillPlateForm.Designer.cs new file mode 100644 index 0000000..b228ee3 --- /dev/null +++ b/Source/OpenNest/Forms/FillPlateForm.Designer.cs @@ -0,0 +1,64 @@ +namespace OpenNest.Forms +{ + partial class FillPlateForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.AutoScroll = true; + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(377, 525); + this.tableLayoutPanel1.TabIndex = 2; + // + // FillPlateForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(377, 525); + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "FillPlateForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "FillPlateForm"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/FillPlateForm.cs b/Source/OpenNest/Forms/FillPlateForm.cs new file mode 100644 index 0000000..4663d33 --- /dev/null +++ b/Source/OpenNest/Forms/FillPlateForm.cs @@ -0,0 +1,55 @@ +using System.Drawing; +using System.Windows.Forms; +using OpenNest.Collections; +using OpenNest.Controls; + +namespace OpenNest.Forms +{ + public partial class FillPlateForm : Form + { + private DrawingCollection Drawings; + + public FillPlateForm(DrawingCollection drawings) + { + InitializeComponent(); + Drawings = drawings; + UpdateDrawingList(); + } + + public Drawing SelectedDrawing { get; protected set; } + + public void UpdateDrawingList() + { + tableLayoutPanel1.Controls.Clear(); + tableLayoutPanel1.RowStyles.Clear(); + tableLayoutPanel1.RowCount = Drawings.Count + 1; + + var controls = new PlateView[Drawings.Count]; + + int index = 0; + + foreach (var dwg in Drawings) + { + var control = new PlateView(); + control.DrawOrigin = false; + control.AllowPan = false; + control.AllowSelect = false; + control.AllowZoom = false; + control.BackColor = Color.White; + control.Plate.Size = new OpenNest.Size(0, 0); + control.AddPartFromDrawing(dwg, Vector.Zero); + control.MouseDoubleClick += (sender, e) => + { + SelectedDrawing = control.Plate.Parts.Count > 0 ? control.Plate.Parts[0].BaseDrawing : null; + Close(); + }; + control.Dock = DockStyle.Fill; + controls[index] = control; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200)); + index++; + } + + tableLayoutPanel1.Controls.AddRange(controls); + } + } +} diff --git a/Source/OpenNest/Forms/FillPlateForm.resx b/Source/OpenNest/Forms/FillPlateForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/FillPlateForm.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/Source/OpenNest/Forms/MainForm.Designer.cs b/Source/OpenNest/Forms/MainForm.Designer.cs new file mode 100644 index 0000000..1cd9a23 --- /dev/null +++ b/Source/OpenNest/Forms/MainForm.Designer.cs @@ -0,0 +1,1282 @@ +namespace OpenNest.Forms +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.mnuFile = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuFileNew = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuFileOpen = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuFileSave = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuFileSaveAs = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuFileExport = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuFileExportAll = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuFileExit = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuEdit = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuEditCut = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuEditCopy = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuEditPaste = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuEditSelectAll = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuView = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewDrawRapids = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewDrawBounds = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuViewZoomTo = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomToArea = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomToFit = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomToPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomToSelected = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomIn = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuViewZoomOut = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuTools = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsMeasureArea = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlign = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlignLeft = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlignRight = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlignTop = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlignBottom = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem11 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuToolsAlignHorizontal = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsAlignVertically = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuToolsEvenlySpaceHorizontal = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuToolsEvenlySpaceVertical = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem14 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuSetOffsetIncrement = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuSetRotationIncrement = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem15 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuToolsOptions = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNest = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestEdit = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestImportDrawing = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuNestFirstPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestLastPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuNestNextPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestPreviousPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem12 = new System.Windows.Forms.ToolStripSeparator(); + this.runAutoNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.autoSequenceAllPlatesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestRemoveEmptyPlates = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNestPost = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem19 = new System.Windows.Forms.ToolStripSeparator(); + this.calculateCutTimeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlate = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateEdit = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateSetAsDefault = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem18 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuPlateAdd = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateRemove = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem16 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuPlateFill = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateRotate = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateRotateCw = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuPlateRotateCcw = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuPlateRotate180 = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuResizeToFitParts = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem13 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuPlateViewInCad = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem20 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuSequenceParts = new System.Windows.Forms.ToolStripMenuItem(); + this.autoSequenceCurrentPlateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.manualSequenceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.calculateCutTimeToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.centerPartsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWindow = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWindowCascade = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWindowTileVertical = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWindowTileHorizontal = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripSeparator(); + this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuCloseAll = new System.Windows.Forms.ToolStripMenuItem(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.statusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.locationStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.spacerLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.plateIndexStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.plateSizeStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.plateQtyStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.btnNew = new System.Windows.Forms.ToolStripButton(); + this.btnOpen = new System.Windows.Forms.ToolStripButton(); + this.btnSave = new System.Windows.Forms.ToolStripButton(); + this.btnSaveAs = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.btnFirstPlate = new System.Windows.Forms.ToolStripButton(); + this.btnPreviousPlate = new System.Windows.Forms.ToolStripButton(); + this.btnNextPlate = new System.Windows.Forms.ToolStripButton(); + this.btnLastPlate = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.btnZoomOut = new System.Windows.Forms.ToolStripButton(); + this.btnZoomIn = new System.Windows.Forms.ToolStripButton(); + this.btnZoomToFit = new System.Windows.Forms.ToolStripButton(); + this.pEPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.menuStrip1.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + this.toolStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // menuStrip1 + // + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuFile, + this.mnuEdit, + this.mnuView, + this.mnuTools, + this.mnuNest, + this.mnuPlate, + this.mnuWindow}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(1098, 24); + this.menuStrip1.TabIndex = 7; + this.menuStrip1.Text = "menuStrip1"; + // + // mnuFile + // + this.mnuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuFileNew, + this.mnuFileOpen, + this.toolStripMenuItem1, + this.mnuFileSave, + this.mnuFileSaveAs, + this.toolStripMenuItem2, + this.mnuFileExport, + this.mnuFileExportAll, + this.toolStripMenuItem3, + this.mnuFileExit}); + this.mnuFile.Name = "mnuFile"; + this.mnuFile.Size = new System.Drawing.Size(37, 20); + this.mnuFile.Text = "&File"; + // + // mnuFileNew + // + this.mnuFileNew.Image = global::OpenNest.Properties.Resources.doc_new; + this.mnuFileNew.Name = "mnuFileNew"; + this.mnuFileNew.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); + this.mnuFileNew.Size = new System.Drawing.Size(146, 22); + this.mnuFileNew.Text = "New"; + this.mnuFileNew.Click += new System.EventHandler(this.New_Click); + // + // mnuFileOpen + // + this.mnuFileOpen.Image = global::OpenNest.Properties.Resources.doc_open; + this.mnuFileOpen.Name = "mnuFileOpen"; + this.mnuFileOpen.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.mnuFileOpen.Size = new System.Drawing.Size(146, 22); + this.mnuFileOpen.Text = "Open"; + this.mnuFileOpen.Click += new System.EventHandler(this.Open_Click); + // + // toolStripMenuItem1 + // + this.toolStripMenuItem1.Name = "toolStripMenuItem1"; + this.toolStripMenuItem1.Size = new System.Drawing.Size(143, 6); + // + // mnuFileSave + // + this.mnuFileSave.Enabled = false; + this.mnuFileSave.Image = global::OpenNest.Properties.Resources.save; + this.mnuFileSave.Name = "mnuFileSave"; + this.mnuFileSave.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); + this.mnuFileSave.Size = new System.Drawing.Size(146, 22); + this.mnuFileSave.Text = "Save"; + this.mnuFileSave.Click += new System.EventHandler(this.Save_Click); + // + // mnuFileSaveAs + // + this.mnuFileSaveAs.Enabled = false; + this.mnuFileSaveAs.Image = global::OpenNest.Properties.Resources.save_as; + this.mnuFileSaveAs.Name = "mnuFileSaveAs"; + this.mnuFileSaveAs.Size = new System.Drawing.Size(146, 22); + this.mnuFileSaveAs.Text = "Save As"; + this.mnuFileSaveAs.Click += new System.EventHandler(this.SaveAs_Click); + // + // toolStripMenuItem2 + // + this.toolStripMenuItem2.Name = "toolStripMenuItem2"; + this.toolStripMenuItem2.Size = new System.Drawing.Size(143, 6); + // + // mnuFileExport + // + this.mnuFileExport.Name = "mnuFileExport"; + this.mnuFileExport.Size = new System.Drawing.Size(146, 22); + this.mnuFileExport.Text = "Export"; + this.mnuFileExport.Click += new System.EventHandler(this.Export_Click); + // + // mnuFileExportAll + // + this.mnuFileExportAll.Name = "mnuFileExportAll"; + this.mnuFileExportAll.Size = new System.Drawing.Size(146, 22); + this.mnuFileExportAll.Text = "Export All"; + this.mnuFileExportAll.Click += new System.EventHandler(this.ExportAll_Click); + // + // toolStripMenuItem3 + // + this.toolStripMenuItem3.Name = "toolStripMenuItem3"; + this.toolStripMenuItem3.Size = new System.Drawing.Size(143, 6); + // + // mnuFileExit + // + this.mnuFileExit.Name = "mnuFileExit"; + this.mnuFileExit.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Q))); + this.mnuFileExit.Size = new System.Drawing.Size(146, 22); + this.mnuFileExit.Text = "Exit"; + this.mnuFileExit.Click += new System.EventHandler(this.Exit_Click); + // + // mnuEdit + // + this.mnuEdit.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuEditCut, + this.mnuEditCopy, + this.mnuEditPaste, + this.toolStripMenuItem4, + this.mnuEditSelectAll}); + this.mnuEdit.Name = "mnuEdit"; + this.mnuEdit.Size = new System.Drawing.Size(39, 20); + this.mnuEdit.Text = "&Edit"; + // + // mnuEditCut + // + this.mnuEditCut.Name = "mnuEditCut"; + this.mnuEditCut.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.X))); + this.mnuEditCut.Size = new System.Drawing.Size(164, 22); + this.mnuEditCut.Text = "Cut"; + this.mnuEditCut.Click += new System.EventHandler(this.EditCut_Click); + // + // mnuEditCopy + // + this.mnuEditCopy.Name = "mnuEditCopy"; + this.mnuEditCopy.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C))); + this.mnuEditCopy.Size = new System.Drawing.Size(164, 22); + this.mnuEditCopy.Text = "Copy"; + this.mnuEditCopy.Click += new System.EventHandler(this.EditCopy_Click); + // + // mnuEditPaste + // + this.mnuEditPaste.Enabled = false; + this.mnuEditPaste.Name = "mnuEditPaste"; + this.mnuEditPaste.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.V))); + this.mnuEditPaste.Size = new System.Drawing.Size(164, 22); + this.mnuEditPaste.Text = "Paste"; + this.mnuEditPaste.Visible = false; + this.mnuEditPaste.Click += new System.EventHandler(this.EditPaste_Click); + // + // toolStripMenuItem4 + // + this.toolStripMenuItem4.Name = "toolStripMenuItem4"; + this.toolStripMenuItem4.Size = new System.Drawing.Size(161, 6); + // + // mnuEditSelectAll + // + this.mnuEditSelectAll.Name = "mnuEditSelectAll"; + this.mnuEditSelectAll.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.A))); + this.mnuEditSelectAll.Size = new System.Drawing.Size(164, 22); + this.mnuEditSelectAll.Text = "Select All"; + this.mnuEditSelectAll.Click += new System.EventHandler(this.EditSelectAll_Click); + // + // mnuView + // + this.mnuView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuViewDrawRapids, + this.mnuViewDrawBounds, + this.toolStripMenuItem5, + this.mnuViewZoomTo, + this.mnuViewZoomIn, + this.mnuViewZoomOut}); + this.mnuView.Name = "mnuView"; + this.mnuView.Size = new System.Drawing.Size(44, 20); + this.mnuView.Text = "&View"; + // + // mnuViewDrawRapids + // + this.mnuViewDrawRapids.Name = "mnuViewDrawRapids"; + this.mnuViewDrawRapids.Size = new System.Drawing.Size(222, 22); + this.mnuViewDrawRapids.Text = "Draw Rapids"; + this.mnuViewDrawRapids.Click += new System.EventHandler(this.ToggleDrawRapids_Click); + // + // mnuViewDrawBounds + // + this.mnuViewDrawBounds.CheckOnClick = true; + this.mnuViewDrawBounds.Name = "mnuViewDrawBounds"; + this.mnuViewDrawBounds.Size = new System.Drawing.Size(222, 22); + this.mnuViewDrawBounds.Text = "Draw Bounds"; + this.mnuViewDrawBounds.Click += new System.EventHandler(this.ToggleDrawBounds_Click); + // + // toolStripMenuItem5 + // + this.toolStripMenuItem5.Name = "toolStripMenuItem5"; + this.toolStripMenuItem5.Size = new System.Drawing.Size(219, 6); + // + // mnuViewZoomTo + // + this.mnuViewZoomTo.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuViewZoomToArea, + this.mnuViewZoomToFit, + this.mnuViewZoomToPlate, + this.mnuViewZoomToSelected}); + this.mnuViewZoomTo.Name = "mnuViewZoomTo"; + this.mnuViewZoomTo.Size = new System.Drawing.Size(222, 22); + this.mnuViewZoomTo.Text = "Zoom To"; + // + // mnuViewZoomToArea + // + this.mnuViewZoomToArea.Name = "mnuViewZoomToArea"; + this.mnuViewZoomToArea.Size = new System.Drawing.Size(118, 22); + this.mnuViewZoomToArea.Text = "Area"; + this.mnuViewZoomToArea.Click += new System.EventHandler(this.ZoomToArea_Click); + // + // mnuViewZoomToFit + // + this.mnuViewZoomToFit.Image = global::OpenNest.Properties.Resources.zoom_all; + this.mnuViewZoomToFit.Name = "mnuViewZoomToFit"; + this.mnuViewZoomToFit.Size = new System.Drawing.Size(118, 22); + this.mnuViewZoomToFit.Text = "Fit"; + this.mnuViewZoomToFit.Click += new System.EventHandler(this.ZoomToFit_Click); + // + // mnuViewZoomToPlate + // + this.mnuViewZoomToPlate.Name = "mnuViewZoomToPlate"; + this.mnuViewZoomToPlate.Size = new System.Drawing.Size(118, 22); + this.mnuViewZoomToPlate.Text = "Plate"; + this.mnuViewZoomToPlate.Click += new System.EventHandler(this.ZoomToPlate_Click); + // + // mnuViewZoomToSelected + // + this.mnuViewZoomToSelected.Name = "mnuViewZoomToSelected"; + this.mnuViewZoomToSelected.Size = new System.Drawing.Size(118, 22); + this.mnuViewZoomToSelected.Text = "Selected"; + this.mnuViewZoomToSelected.Click += new System.EventHandler(this.ZoomToSelected_Click); + // + // mnuViewZoomIn + // + this.mnuViewZoomIn.Image = global::OpenNest.Properties.Resources.zoom_in; + this.mnuViewZoomIn.Name = "mnuViewZoomIn"; + this.mnuViewZoomIn.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Oemplus))); + this.mnuViewZoomIn.Size = new System.Drawing.Size(222, 22); + this.mnuViewZoomIn.Text = "Zoom In"; + this.mnuViewZoomIn.Click += new System.EventHandler(this.ZoomIn_Click); + // + // mnuViewZoomOut + // + this.mnuViewZoomOut.Image = global::OpenNest.Properties.Resources.zoom_out; + this.mnuViewZoomOut.Name = "mnuViewZoomOut"; + this.mnuViewZoomOut.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.OemMinus))); + this.mnuViewZoomOut.Size = new System.Drawing.Size(222, 22); + this.mnuViewZoomOut.Text = "Zoom Out"; + this.mnuViewZoomOut.Click += new System.EventHandler(this.ZoomOut_Click); + // + // mnuTools + // + this.mnuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuToolsMeasureArea, + this.mnuToolsAlign, + this.toolStripMenuItem14, + this.mnuSetOffsetIncrement, + this.mnuSetRotationIncrement, + this.toolStripMenuItem15, + this.mnuToolsOptions}); + this.mnuTools.Name = "mnuTools"; + this.mnuTools.Size = new System.Drawing.Size(47, 20); + this.mnuTools.Text = "&Tools"; + // + // mnuToolsMeasureArea + // + this.mnuToolsMeasureArea.Name = "mnuToolsMeasureArea"; + this.mnuToolsMeasureArea.Size = new System.Drawing.Size(214, 22); + this.mnuToolsMeasureArea.Text = "Measure Area"; + this.mnuToolsMeasureArea.Click += new System.EventHandler(this.MeasureArea_Click); + // + // mnuToolsAlign + // + this.mnuToolsAlign.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuToolsAlignLeft, + this.mnuToolsAlignRight, + this.mnuToolsAlignTop, + this.mnuToolsAlignBottom, + this.toolStripMenuItem11, + this.mnuToolsAlignHorizontal, + this.mnuToolsAlignVertically, + this.toolStripMenuItem8, + this.mnuToolsEvenlySpaceHorizontal, + this.mnuToolsEvenlySpaceVertical}); + this.mnuToolsAlign.Name = "mnuToolsAlign"; + this.mnuToolsAlign.Size = new System.Drawing.Size(214, 22); + this.mnuToolsAlign.Text = "Align Selected"; + // + // mnuToolsAlignLeft + // + this.mnuToolsAlignLeft.Name = "mnuToolsAlignLeft"; + this.mnuToolsAlignLeft.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignLeft.Text = "Left"; + this.mnuToolsAlignLeft.Click += new System.EventHandler(this.AlignLeft_Click); + // + // mnuToolsAlignRight + // + this.mnuToolsAlignRight.Name = "mnuToolsAlignRight"; + this.mnuToolsAlignRight.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignRight.Text = "Right"; + this.mnuToolsAlignRight.Click += new System.EventHandler(this.AlignRight_Click); + // + // mnuToolsAlignTop + // + this.mnuToolsAlignTop.Name = "mnuToolsAlignTop"; + this.mnuToolsAlignTop.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignTop.Text = "Top"; + this.mnuToolsAlignTop.Click += new System.EventHandler(this.AlignTop_Click); + // + // mnuToolsAlignBottom + // + this.mnuToolsAlignBottom.Name = "mnuToolsAlignBottom"; + this.mnuToolsAlignBottom.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignBottom.Text = "Bottom"; + this.mnuToolsAlignBottom.Click += new System.EventHandler(this.AlignBottom_Click); + // + // toolStripMenuItem11 + // + this.toolStripMenuItem11.Name = "toolStripMenuItem11"; + this.toolStripMenuItem11.Size = new System.Drawing.Size(206, 6); + // + // mnuToolsAlignHorizontal + // + this.mnuToolsAlignHorizontal.Name = "mnuToolsAlignHorizontal"; + this.mnuToolsAlignHorizontal.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignHorizontal.Text = "Horizontally"; + this.mnuToolsAlignHorizontal.Click += new System.EventHandler(this.AlignHorizontal_Click); + // + // mnuToolsAlignVertically + // + this.mnuToolsAlignVertically.Name = "mnuToolsAlignVertically"; + this.mnuToolsAlignVertically.Size = new System.Drawing.Size(209, 22); + this.mnuToolsAlignVertically.Text = "Vertically"; + this.mnuToolsAlignVertically.Click += new System.EventHandler(this.AlignVertical_Click); + // + // toolStripMenuItem8 + // + this.toolStripMenuItem8.Name = "toolStripMenuItem8"; + this.toolStripMenuItem8.Size = new System.Drawing.Size(206, 6); + // + // mnuToolsEvenlySpaceHorizontal + // + this.mnuToolsEvenlySpaceHorizontal.Name = "mnuToolsEvenlySpaceHorizontal"; + this.mnuToolsEvenlySpaceHorizontal.Size = new System.Drawing.Size(209, 22); + this.mnuToolsEvenlySpaceHorizontal.Text = "Evenly Space Horizontally"; + this.mnuToolsEvenlySpaceHorizontal.Click += new System.EventHandler(this.EvenlySpaceHorizontally_Click); + // + // mnuToolsEvenlySpaceVertical + // + this.mnuToolsEvenlySpaceVertical.Name = "mnuToolsEvenlySpaceVertical"; + this.mnuToolsEvenlySpaceVertical.Size = new System.Drawing.Size(209, 22); + this.mnuToolsEvenlySpaceVertical.Text = "Evenly Space Vertically"; + this.mnuToolsEvenlySpaceVertical.Click += new System.EventHandler(this.EvenlySpaceVertically_Click); + // + // toolStripMenuItem14 + // + this.toolStripMenuItem14.Name = "toolStripMenuItem14"; + this.toolStripMenuItem14.Size = new System.Drawing.Size(211, 6); + // + // mnuSetOffsetIncrement + // + this.mnuSetOffsetIncrement.Name = "mnuSetOffsetIncrement"; + this.mnuSetOffsetIncrement.ShortcutKeys = System.Windows.Forms.Keys.F5; + this.mnuSetOffsetIncrement.Size = new System.Drawing.Size(214, 22); + this.mnuSetOffsetIncrement.Text = "Set Offset Increment"; + this.mnuSetOffsetIncrement.Click += new System.EventHandler(this.SetOffsetIncrement_Click); + // + // mnuSetRotationIncrement + // + this.mnuSetRotationIncrement.Name = "mnuSetRotationIncrement"; + this.mnuSetRotationIncrement.ShortcutKeys = System.Windows.Forms.Keys.F6; + this.mnuSetRotationIncrement.Size = new System.Drawing.Size(214, 22); + this.mnuSetRotationIncrement.Text = "Set Rotation Increment"; + this.mnuSetRotationIncrement.Click += new System.EventHandler(this.SetRotationIncrement_Click); + // + // toolStripMenuItem15 + // + this.toolStripMenuItem15.Name = "toolStripMenuItem15"; + this.toolStripMenuItem15.Size = new System.Drawing.Size(211, 6); + // + // mnuToolsOptions + // + this.mnuToolsOptions.Name = "mnuToolsOptions"; + this.mnuToolsOptions.Size = new System.Drawing.Size(214, 22); + this.mnuToolsOptions.Text = "Options"; + this.mnuToolsOptions.Click += new System.EventHandler(this.Options_Click); + // + // mnuNest + // + this.mnuNest.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuNestEdit, + this.mnuNestImportDrawing, + this.toolStripMenuItem7, + this.mnuNestFirstPlate, + this.mnuNestLastPlate, + this.toolStripMenuItem6, + this.mnuNestNextPlate, + this.mnuNestPreviousPlate, + this.toolStripMenuItem12, + this.runAutoNestToolStripMenuItem, + this.autoSequenceAllPlatesToolStripMenuItem, + this.mnuNestRemoveEmptyPlates, + this.mnuNestPost, + this.toolStripMenuItem19, + this.calculateCutTimeToolStripMenuItem}); + this.mnuNest.Name = "mnuNest"; + this.mnuNest.Size = new System.Drawing.Size(43, 20); + this.mnuNest.Text = "&Nest"; + // + // mnuNestEdit + // + this.mnuNestEdit.Name = "mnuNestEdit"; + this.mnuNestEdit.Size = new System.Drawing.Size(205, 22); + this.mnuNestEdit.Text = "Edit"; + this.mnuNestEdit.Click += new System.EventHandler(this.EditNest_Click); + // + // mnuNestImportDrawing + // + this.mnuNestImportDrawing.Image = global::OpenNest.Properties.Resources.import; + this.mnuNestImportDrawing.Name = "mnuNestImportDrawing"; + this.mnuNestImportDrawing.Size = new System.Drawing.Size(205, 22); + this.mnuNestImportDrawing.Text = "Import Drawing"; + this.mnuNestImportDrawing.Click += new System.EventHandler(this.Import_Click); + // + // toolStripMenuItem7 + // + this.toolStripMenuItem7.Name = "toolStripMenuItem7"; + this.toolStripMenuItem7.Size = new System.Drawing.Size(202, 6); + // + // mnuNestFirstPlate + // + this.mnuNestFirstPlate.Image = global::OpenNest.Properties.Resources.move_first; + this.mnuNestFirstPlate.Name = "mnuNestFirstPlate"; + this.mnuNestFirstPlate.Size = new System.Drawing.Size(205, 22); + this.mnuNestFirstPlate.Text = "First Plate"; + this.mnuNestFirstPlate.Click += new System.EventHandler(this.LoadFirstPlate_Click); + // + // mnuNestLastPlate + // + this.mnuNestLastPlate.Image = global::OpenNest.Properties.Resources.move_last; + this.mnuNestLastPlate.Name = "mnuNestLastPlate"; + this.mnuNestLastPlate.Size = new System.Drawing.Size(205, 22); + this.mnuNestLastPlate.Text = "Last Plate"; + this.mnuNestLastPlate.Click += new System.EventHandler(this.LoadLastPlate_Click); + // + // toolStripMenuItem6 + // + this.toolStripMenuItem6.Name = "toolStripMenuItem6"; + this.toolStripMenuItem6.Size = new System.Drawing.Size(202, 6); + // + // mnuNestNextPlate + // + this.mnuNestNextPlate.Image = global::OpenNest.Properties.Resources.move_next; + this.mnuNestNextPlate.Name = "mnuNestNextPlate"; + this.mnuNestNextPlate.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Right))); + this.mnuNestNextPlate.Size = new System.Drawing.Size(205, 22); + this.mnuNestNextPlate.Text = "Next Plate"; + this.mnuNestNextPlate.Click += new System.EventHandler(this.LoadNextPlate_Click); + // + // mnuNestPreviousPlate + // + this.mnuNestPreviousPlate.Image = global::OpenNest.Properties.Resources.move_previous; + this.mnuNestPreviousPlate.Name = "mnuNestPreviousPlate"; + this.mnuNestPreviousPlate.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Left))); + this.mnuNestPreviousPlate.Size = new System.Drawing.Size(205, 22); + this.mnuNestPreviousPlate.Text = "Previous Plate"; + this.mnuNestPreviousPlate.Click += new System.EventHandler(this.LoadPreviousPlate_Click); + // + // toolStripMenuItem12 + // + this.toolStripMenuItem12.Name = "toolStripMenuItem12"; + this.toolStripMenuItem12.Size = new System.Drawing.Size(202, 6); + // + // runAutoNestToolStripMenuItem + // + this.runAutoNestToolStripMenuItem.Name = "runAutoNestToolStripMenuItem"; + this.runAutoNestToolStripMenuItem.Size = new System.Drawing.Size(205, 22); + this.runAutoNestToolStripMenuItem.Text = "Auto Nest"; + this.runAutoNestToolStripMenuItem.Click += new System.EventHandler(this.RunAutoNest_Click); + // + // autoSequenceAllPlatesToolStripMenuItem + // + this.autoSequenceAllPlatesToolStripMenuItem.Name = "autoSequenceAllPlatesToolStripMenuItem"; + this.autoSequenceAllPlatesToolStripMenuItem.Size = new System.Drawing.Size(205, 22); + this.autoSequenceAllPlatesToolStripMenuItem.Text = "Auto Sequence All Plates"; + this.autoSequenceAllPlatesToolStripMenuItem.Click += new System.EventHandler(this.SequenceAllPlates_Click); + // + // mnuNestRemoveEmptyPlates + // + this.mnuNestRemoveEmptyPlates.Name = "mnuNestRemoveEmptyPlates"; + this.mnuNestRemoveEmptyPlates.Size = new System.Drawing.Size(205, 22); + this.mnuNestRemoveEmptyPlates.Text = "Remove Empty Plates"; + this.mnuNestRemoveEmptyPlates.Click += new System.EventHandler(this.RemoveEmptyPlates_Click); + // + // mnuNestPost + // + this.mnuNestPost.Name = "mnuNestPost"; + this.mnuNestPost.Size = new System.Drawing.Size(205, 22); + this.mnuNestPost.Text = "Post"; + // + // toolStripMenuItem19 + // + this.toolStripMenuItem19.Name = "toolStripMenuItem19"; + this.toolStripMenuItem19.Size = new System.Drawing.Size(202, 6); + // + // calculateCutTimeToolStripMenuItem + // + this.calculateCutTimeToolStripMenuItem.Name = "calculateCutTimeToolStripMenuItem"; + this.calculateCutTimeToolStripMenuItem.Size = new System.Drawing.Size(205, 22); + this.calculateCutTimeToolStripMenuItem.Text = "Calculate Cut Time"; + this.calculateCutTimeToolStripMenuItem.Click += new System.EventHandler(this.CalculateNestCutTime_Click); + // + // mnuPlate + // + this.mnuPlate.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuPlateEdit, + this.mnuPlateSetAsDefault, + this.toolStripMenuItem18, + this.mnuPlateAdd, + this.mnuPlateRemove, + this.toolStripMenuItem16, + this.mnuPlateFill, + this.toolStripMenuItem9, + this.mnuPlateRotate, + this.mnuResizeToFitParts, + this.toolStripMenuItem13, + this.mnuPlateViewInCad, + this.toolStripMenuItem20, + this.mnuSequenceParts, + this.calculateCutTimeToolStripMenuItem1, + this.centerPartsToolStripMenuItem}); + this.mnuPlate.Name = "mnuPlate"; + this.mnuPlate.Size = new System.Drawing.Size(45, 20); + this.mnuPlate.Text = "&Plate"; + // + // mnuPlateEdit + // + this.mnuPlateEdit.Name = "mnuPlateEdit"; + this.mnuPlateEdit.Size = new System.Drawing.Size(177, 22); + this.mnuPlateEdit.Text = "Edit"; + this.mnuPlateEdit.Click += new System.EventHandler(this.EditPlate_Click); + // + // mnuPlateSetAsDefault + // + this.mnuPlateSetAsDefault.Name = "mnuPlateSetAsDefault"; + this.mnuPlateSetAsDefault.Size = new System.Drawing.Size(177, 22); + this.mnuPlateSetAsDefault.Text = "Set as Default"; + this.mnuPlateSetAsDefault.Click += new System.EventHandler(this.SetAsNestDefault_Click); + // + // toolStripMenuItem18 + // + this.toolStripMenuItem18.Name = "toolStripMenuItem18"; + this.toolStripMenuItem18.Size = new System.Drawing.Size(174, 6); + // + // mnuPlateAdd + // + this.mnuPlateAdd.Image = global::OpenNest.Properties.Resources.add; + this.mnuPlateAdd.Name = "mnuPlateAdd"; + this.mnuPlateAdd.Size = new System.Drawing.Size(177, 22); + this.mnuPlateAdd.Text = "Add"; + this.mnuPlateAdd.Click += new System.EventHandler(this.AddPlate_Click); + // + // mnuPlateRemove + // + this.mnuPlateRemove.Image = global::OpenNest.Properties.Resources.remove; + this.mnuPlateRemove.Name = "mnuPlateRemove"; + this.mnuPlateRemove.Size = new System.Drawing.Size(177, 22); + this.mnuPlateRemove.Text = "Remove"; + this.mnuPlateRemove.Click += new System.EventHandler(this.RemovePlate_Click); + // + // toolStripMenuItem16 + // + this.toolStripMenuItem16.Name = "toolStripMenuItem16"; + this.toolStripMenuItem16.Size = new System.Drawing.Size(174, 6); + // + // mnuPlateFill + // + this.mnuPlateFill.Name = "mnuPlateFill"; + this.mnuPlateFill.Size = new System.Drawing.Size(177, 22); + this.mnuPlateFill.Text = "Fill"; + this.mnuPlateFill.Click += new System.EventHandler(this.FillPlate_Click); + // + // toolStripMenuItem9 + // + this.toolStripMenuItem9.Name = "toolStripMenuItem9"; + this.toolStripMenuItem9.Size = new System.Drawing.Size(177, 22); + this.toolStripMenuItem9.Text = "Fill Area"; + this.toolStripMenuItem9.Click += new System.EventHandler(this.FillArea_Click); + // + // mnuPlateRotate + // + this.mnuPlateRotate.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuPlateRotateCw, + this.mnuPlateRotateCcw, + this.toolStripSeparator2, + this.mnuPlateRotate180}); + this.mnuPlateRotate.Name = "mnuPlateRotate"; + this.mnuPlateRotate.Size = new System.Drawing.Size(177, 22); + this.mnuPlateRotate.Text = "Rotate"; + // + // mnuPlateRotateCw + // + this.mnuPlateRotateCw.Image = global::OpenNest.Properties.Resources.rotate_cw; + this.mnuPlateRotateCw.Name = "mnuPlateRotateCw"; + this.mnuPlateRotateCw.Size = new System.Drawing.Size(121, 22); + this.mnuPlateRotateCw.Text = "90° CW"; + this.mnuPlateRotateCw.Click += new System.EventHandler(this.RotateCw_Click); + // + // mnuPlateRotateCcw + // + this.mnuPlateRotateCcw.Image = global::OpenNest.Properties.Resources.rotate_ccw; + this.mnuPlateRotateCcw.Name = "mnuPlateRotateCcw"; + this.mnuPlateRotateCcw.Size = new System.Drawing.Size(121, 22); + this.mnuPlateRotateCcw.Text = "90° CCW"; + this.mnuPlateRotateCcw.Click += new System.EventHandler(this.RotateCcw_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(118, 6); + // + // mnuPlateRotate180 + // + this.mnuPlateRotate180.Name = "mnuPlateRotate180"; + this.mnuPlateRotate180.Size = new System.Drawing.Size(121, 22); + this.mnuPlateRotate180.Text = "180°"; + this.mnuPlateRotate180.Click += new System.EventHandler(this.Rotate180_Click); + // + // mnuResizeToFitParts + // + this.mnuResizeToFitParts.Name = "mnuResizeToFitParts"; + this.mnuResizeToFitParts.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.P))); + this.mnuResizeToFitParts.Size = new System.Drawing.Size(177, 22); + this.mnuResizeToFitParts.Text = "Resize to Fit"; + this.mnuResizeToFitParts.Click += new System.EventHandler(this.ResizeToFitParts_Click); + // + // toolStripMenuItem13 + // + this.toolStripMenuItem13.Name = "toolStripMenuItem13"; + this.toolStripMenuItem13.Size = new System.Drawing.Size(174, 6); + // + // mnuPlateViewInCad + // + this.mnuPlateViewInCad.Name = "mnuPlateViewInCad"; + this.mnuPlateViewInCad.Size = new System.Drawing.Size(177, 22); + this.mnuPlateViewInCad.Text = "View in CAD"; + this.mnuPlateViewInCad.Click += new System.EventHandler(this.OpenInExternalCad_Click); + // + // toolStripMenuItem20 + // + this.toolStripMenuItem20.Name = "toolStripMenuItem20"; + this.toolStripMenuItem20.Size = new System.Drawing.Size(174, 6); + // + // mnuSequenceParts + // + this.mnuSequenceParts.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.autoSequenceCurrentPlateToolStripMenuItem, + this.manualSequenceToolStripMenuItem}); + this.mnuSequenceParts.Name = "mnuSequenceParts"; + this.mnuSequenceParts.Size = new System.Drawing.Size(177, 22); + this.mnuSequenceParts.Text = "Sequence Parts"; + // + // autoSequenceCurrentPlateToolStripMenuItem + // + this.autoSequenceCurrentPlateToolStripMenuItem.Name = "autoSequenceCurrentPlateToolStripMenuItem"; + this.autoSequenceCurrentPlateToolStripMenuItem.Size = new System.Drawing.Size(168, 22); + this.autoSequenceCurrentPlateToolStripMenuItem.Text = "Auto Sequence"; + this.autoSequenceCurrentPlateToolStripMenuItem.Click += new System.EventHandler(this.AutoSequenceCurrentPlate_Click); + // + // manualSequenceToolStripMenuItem + // + this.manualSequenceToolStripMenuItem.Name = "manualSequenceToolStripMenuItem"; + this.manualSequenceToolStripMenuItem.Size = new System.Drawing.Size(168, 22); + this.manualSequenceToolStripMenuItem.Text = "Manual Sequence"; + this.manualSequenceToolStripMenuItem.Click += new System.EventHandler(this.ManualSequenceParts_Click); + // + // calculateCutTimeToolStripMenuItem1 + // + this.calculateCutTimeToolStripMenuItem1.Name = "calculateCutTimeToolStripMenuItem1"; + this.calculateCutTimeToolStripMenuItem1.Size = new System.Drawing.Size(177, 22); + this.calculateCutTimeToolStripMenuItem1.Text = "Calculate Cut Time"; + this.calculateCutTimeToolStripMenuItem1.Click += new System.EventHandler(this.CalculatePlateCutTime_Click); + // + // centerPartsToolStripMenuItem + // + this.centerPartsToolStripMenuItem.Name = "centerPartsToolStripMenuItem"; + this.centerPartsToolStripMenuItem.Size = new System.Drawing.Size(177, 22); + this.centerPartsToolStripMenuItem.Text = "Center Parts"; + this.centerPartsToolStripMenuItem.Click += new System.EventHandler(this.centerPartsToolStripMenuItem_Click); + // + // mnuWindow + // + this.mnuWindow.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuWindowCascade, + this.mnuWindowTileVertical, + this.mnuWindowTileHorizontal, + this.toolStripMenuItem10, + this.closeToolStripMenuItem, + this.mnuCloseAll}); + this.mnuWindow.Name = "mnuWindow"; + this.mnuWindow.Size = new System.Drawing.Size(63, 20); + this.mnuWindow.Text = "&Window"; + // + // mnuWindowCascade + // + this.mnuWindowCascade.Name = "mnuWindowCascade"; + this.mnuWindowCascade.Size = new System.Drawing.Size(151, 22); + this.mnuWindowCascade.Text = "Cascade"; + this.mnuWindowCascade.Click += new System.EventHandler(this.CascadeWindows_Click); + // + // mnuWindowTileVertical + // + this.mnuWindowTileVertical.Name = "mnuWindowTileVertical"; + this.mnuWindowTileVertical.Size = new System.Drawing.Size(151, 22); + this.mnuWindowTileVertical.Text = "Tile Vertical"; + this.mnuWindowTileVertical.Click += new System.EventHandler(this.TileVertical_Click); + // + // mnuWindowTileHorizontal + // + this.mnuWindowTileHorizontal.Name = "mnuWindowTileHorizontal"; + this.mnuWindowTileHorizontal.Size = new System.Drawing.Size(151, 22); + this.mnuWindowTileHorizontal.Text = "Tile Horizontal"; + this.mnuWindowTileHorizontal.Click += new System.EventHandler(this.TileHorizontal_Click); + // + // toolStripMenuItem10 + // + this.toolStripMenuItem10.Name = "toolStripMenuItem10"; + this.toolStripMenuItem10.Size = new System.Drawing.Size(148, 6); + // + // closeToolStripMenuItem + // + this.closeToolStripMenuItem.Name = "closeToolStripMenuItem"; + this.closeToolStripMenuItem.Size = new System.Drawing.Size(151, 22); + this.closeToolStripMenuItem.Text = "Close"; + this.closeToolStripMenuItem.Click += new System.EventHandler(this.Close_Click); + // + // mnuCloseAll + // + this.mnuCloseAll.Name = "mnuCloseAll"; + this.mnuCloseAll.Size = new System.Drawing.Size(151, 22); + this.mnuCloseAll.Text = "Close All"; + this.mnuCloseAll.Click += new System.EventHandler(this.CloseAll_Click); + // + // statusStrip1 + // + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.statusLabel1, + this.locationStatusLabel, + this.spacerLabel, + this.plateIndexStatusLabel, + this.plateSizeStatusLabel, + this.plateQtyStatusLabel}); + this.statusStrip1.Location = new System.Drawing.Point(0, 543); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(1098, 24); + this.statusStrip1.TabIndex = 9; + this.statusStrip1.Text = "statusStrip1"; + // + // statusLabel1 + // + this.statusLabel1.Name = "statusLabel1"; + this.statusLabel1.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.statusLabel1.Size = new System.Drawing.Size(10, 19); + // + // locationStatusLabel + // + this.locationStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + this.locationStatusLabel.Name = "locationStatusLabel"; + this.locationStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.locationStatusLabel.Size = new System.Drawing.Size(102, 19); + this.locationStatusLabel.Text = "Location : [0, 0]"; + this.locationStatusLabel.Click += new System.EventHandler(this.LocationStatusLabel_Click); + // + // spacerLabel + // + this.spacerLabel.Name = "spacerLabel"; + this.spacerLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.spacerLabel.Size = new System.Drawing.Size(764, 19); + this.spacerLabel.Spring = true; + // + // plateIndexStatusLabel + // + this.plateIndexStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + this.plateIndexStatusLabel.Name = "plateIndexStatusLabel"; + this.plateIndexStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.plateIndexStatusLabel.Size = new System.Drawing.Size(85, 19); + this.plateIndexStatusLabel.Text = "Plate : 0 of 0"; + // + // plateSizeStatusLabel + // + this.plateSizeStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + this.plateSizeStatusLabel.Name = "plateSizeStatusLabel"; + this.plateSizeStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.plateSizeStatusLabel.Size = new System.Drawing.Size(67, 19); + this.plateSizeStatusLabel.Text = "Size : 0x0"; + // + // plateQtyStatusLabel + // + this.plateQtyStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + this.plateQtyStatusLabel.Name = "plateQtyStatusLabel"; + this.plateQtyStatusLabel.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.plateQtyStatusLabel.Size = new System.Drawing.Size(55, 19); + this.plateQtyStatusLabel.Text = "Qty : 0"; + // + // toolStrip1 + // + this.toolStrip1.AutoSize = false; + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.btnNew, + this.btnOpen, + this.btnSave, + this.btnSaveAs, + this.toolStripSeparator1, + this.btnFirstPlate, + this.btnPreviousPlate, + this.btnNextPlate, + this.btnLastPlate, + this.toolStripSeparator3, + this.btnZoomOut, + this.btnZoomIn, + this.btnZoomToFit}); + this.toolStrip1.Location = new System.Drawing.Point(0, 24); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Size = new System.Drawing.Size(1098, 35); + this.toolStrip1.TabIndex = 10; + this.toolStrip1.Text = "toolStrip1"; + // + // btnNew + // + this.btnNew.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnNew.Image = global::OpenNest.Properties.Resources.doc_new; + this.btnNew.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnNew.Name = "btnNew"; + this.btnNew.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnNew.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnNew.Size = new System.Drawing.Size(38, 32); + this.btnNew.Text = "New"; + this.btnNew.Click += new System.EventHandler(this.New_Click); + // + // btnOpen + // + this.btnOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnOpen.Image = global::OpenNest.Properties.Resources.doc_open; + this.btnOpen.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnOpen.Name = "btnOpen"; + this.btnOpen.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnOpen.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnOpen.Size = new System.Drawing.Size(38, 32); + this.btnOpen.Text = "Open"; + this.btnOpen.Click += new System.EventHandler(this.Open_Click); + // + // btnSave + // + this.btnSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnSave.Image = global::OpenNest.Properties.Resources.save; + this.btnSave.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnSave.Name = "btnSave"; + this.btnSave.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnSave.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnSave.Size = new System.Drawing.Size(38, 32); + this.btnSave.Text = "Save"; + this.btnSave.Click += new System.EventHandler(this.Save_Click); + // + // btnSaveAs + // + this.btnSaveAs.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnSaveAs.Image = global::OpenNest.Properties.Resources.save_as; + this.btnSaveAs.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnSaveAs.Name = "btnSaveAs"; + this.btnSaveAs.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnSaveAs.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnSaveAs.Size = new System.Drawing.Size(38, 32); + this.btnSaveAs.Text = "Save As"; + this.btnSaveAs.Click += new System.EventHandler(this.SaveAs_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 35); + // + // btnFirstPlate + // + this.btnFirstPlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnFirstPlate.Image = global::OpenNest.Properties.Resources.move_first; + this.btnFirstPlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnFirstPlate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnFirstPlate.Name = "btnFirstPlate"; + this.btnFirstPlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnFirstPlate.Size = new System.Drawing.Size(38, 32); + this.btnFirstPlate.Text = "Go to First Plate"; + this.btnFirstPlate.Click += new System.EventHandler(this.LoadFirstPlate_Click); + // + // btnPreviousPlate + // + this.btnPreviousPlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnPreviousPlate.Image = global::OpenNest.Properties.Resources.move_previous; + this.btnPreviousPlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnPreviousPlate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnPreviousPlate.Name = "btnPreviousPlate"; + this.btnPreviousPlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnPreviousPlate.Size = new System.Drawing.Size(38, 32); + this.btnPreviousPlate.Text = "Go to Previous Plate"; + this.btnPreviousPlate.Click += new System.EventHandler(this.LoadPreviousPlate_Click); + // + // btnNextPlate + // + this.btnNextPlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnNextPlate.Image = global::OpenNest.Properties.Resources.move_next; + this.btnNextPlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnNextPlate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnNextPlate.Name = "btnNextPlate"; + this.btnNextPlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnNextPlate.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnNextPlate.Size = new System.Drawing.Size(38, 32); + this.btnNextPlate.Text = "Go to Next Plate"; + this.btnNextPlate.Click += new System.EventHandler(this.LoadNextPlate_Click); + // + // btnLastPlate + // + this.btnLastPlate.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnLastPlate.Image = global::OpenNest.Properties.Resources.move_last; + this.btnLastPlate.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnLastPlate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnLastPlate.Name = "btnLastPlate"; + this.btnLastPlate.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnLastPlate.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnLastPlate.Size = new System.Drawing.Size(38, 32); + this.btnLastPlate.Text = "Go to Last Plate"; + this.btnLastPlate.Click += new System.EventHandler(this.LoadLastPlate_Click); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(6, 35); + // + // btnZoomOut + // + this.btnZoomOut.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnZoomOut.Image = global::OpenNest.Properties.Resources.zoom_out; + this.btnZoomOut.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnZoomOut.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnZoomOut.Name = "btnZoomOut"; + this.btnZoomOut.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnZoomOut.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnZoomOut.Size = new System.Drawing.Size(38, 32); + this.btnZoomOut.Text = "Zoom Out"; + this.btnZoomOut.Click += new System.EventHandler(this.ZoomOut_Click); + // + // btnZoomIn + // + this.btnZoomIn.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnZoomIn.Image = global::OpenNest.Properties.Resources.zoom_in; + this.btnZoomIn.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnZoomIn.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnZoomIn.Name = "btnZoomIn"; + this.btnZoomIn.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnZoomIn.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnZoomIn.Size = new System.Drawing.Size(38, 32); + this.btnZoomIn.Text = "Zoom In"; + this.btnZoomIn.Click += new System.EventHandler(this.ZoomIn_Click); + // + // btnZoomToFit + // + this.btnZoomToFit.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnZoomToFit.Image = global::OpenNest.Properties.Resources.zoom_all; + this.btnZoomToFit.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.btnZoomToFit.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnZoomToFit.Name = "btnZoomToFit"; + this.btnZoomToFit.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0); + this.btnZoomToFit.RightToLeft = System.Windows.Forms.RightToLeft.No; + this.btnZoomToFit.Size = new System.Drawing.Size(38, 32); + this.btnZoomToFit.Text = "Zoom To Fit"; + this.btnZoomToFit.Click += new System.EventHandler(this.ZoomToFit_Click); + // + // pEPToolStripMenuItem + // + this.pEPToolStripMenuItem.Name = "pEPToolStripMenuItem"; + this.pEPToolStripMenuItem.Size = new System.Drawing.Size(32, 19); + // + // openNestToolStripMenuItem + // + this.openNestToolStripMenuItem.Name = "openNestToolStripMenuItem"; + this.openNestToolStripMenuItem.Size = new System.Drawing.Size(32, 19); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackgroundImage = global::OpenNest.Properties.Resources.watermark; + this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; + this.ClientSize = new System.Drawing.Size(1098, 567); + this.Controls.Add(this.toolStrip1); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.menuStrip1); + this.DoubleBuffered = true; + this.IsMdiContainer = true; + this.MainMenuStrip = this.menuStrip1; + this.MinimumSize = new System.Drawing.Size(506, 335); + this.Name = "MainForm"; + this.Text = "OpenNest"; + this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem mnuFile; + private System.Windows.Forms.ToolStripMenuItem mnuFileNew; + private System.Windows.Forms.ToolStripMenuItem mnuFileOpen; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem mnuFileSave; + private System.Windows.Forms.ToolStripMenuItem mnuFileSaveAs; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2; + private System.Windows.Forms.ToolStripMenuItem mnuFileExport; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3; + private System.Windows.Forms.ToolStripMenuItem mnuFileExit; + private System.Windows.Forms.ToolStripMenuItem mnuEdit; + private System.Windows.Forms.ToolStripMenuItem mnuEditCut; + private System.Windows.Forms.ToolStripMenuItem mnuEditCopy; + private System.Windows.Forms.ToolStripMenuItem mnuEditPaste; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4; + private System.Windows.Forms.ToolStripMenuItem mnuEditSelectAll; + private System.Windows.Forms.ToolStripMenuItem mnuView; + private System.Windows.Forms.ToolStripMenuItem mnuViewDrawRapids; + private System.Windows.Forms.ToolStripMenuItem mnuViewDrawBounds; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem5; + private System.Windows.Forms.ToolStripMenuItem mnuTools; + private System.Windows.Forms.ToolStripMenuItem mnuToolsOptions; + private System.Windows.Forms.ToolStripMenuItem mnuNest; + private System.Windows.Forms.ToolStripMenuItem mnuNestEdit; + private System.Windows.Forms.ToolStripMenuItem mnuNestImportDrawing; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; + private System.Windows.Forms.ToolStripMenuItem mnuNestFirstPlate; + private System.Windows.Forms.ToolStripMenuItem mnuNestLastPlate; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem6; + private System.Windows.Forms.ToolStripMenuItem mnuNestNextPlate; + private System.Windows.Forms.ToolStripMenuItem mnuNestPreviousPlate; + private System.Windows.Forms.ToolStripMenuItem mnuPlate; + private System.Windows.Forms.ToolStripMenuItem mnuPlateEdit; + private System.Windows.Forms.ToolStripMenuItem mnuPlateRotate; + private System.Windows.Forms.ToolStripMenuItem mnuPlateRotateCw; + private System.Windows.Forms.ToolStripMenuItem mnuPlateRotateCcw; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem mnuPlateRotate180; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel plateSizeStatusLabel; + private System.Windows.Forms.ToolStripStatusLabel locationStatusLabel; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripButton btnOpen; + private System.Windows.Forms.ToolStripStatusLabel plateIndexStatusLabel; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripButton btnPreviousPlate; + private System.Windows.Forms.ToolStripButton btnNextPlate; + private System.Windows.Forms.ToolStripMenuItem mnuWindow; + private System.Windows.Forms.ToolStripMenuItem mnuWindowCascade; + private System.Windows.Forms.ToolStripMenuItem mnuWindowTileVertical; + private System.Windows.Forms.ToolStripMenuItem mnuWindowTileHorizontal; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem10; + private System.Windows.Forms.ToolStripMenuItem mnuCloseAll; + private System.Windows.Forms.ToolStripStatusLabel plateQtyStatusLabel; + private System.Windows.Forms.ToolStripMenuItem mnuFileExportAll; + private System.Windows.Forms.ToolStripMenuItem openNestToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pEPToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomTo; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomToArea; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomToFit; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomToPlate; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomToSelected; + private System.Windows.Forms.ToolStripMenuItem mnuResizeToFitParts; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlign; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignLeft; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignRight; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignTop; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignBottom; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem11; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignHorizontal; + private System.Windows.Forms.ToolStripMenuItem mnuToolsAlignVertically; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem8; + private System.Windows.Forms.ToolStripMenuItem mnuToolsEvenlySpaceHorizontal; + private System.Windows.Forms.ToolStripMenuItem mnuToolsEvenlySpaceVertical; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12; + private System.Windows.Forms.ToolStripMenuItem mnuNestPost; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem13; + private System.Windows.Forms.ToolStripMenuItem mnuPlateViewInCad; + private System.Windows.Forms.ToolStripButton btnNew; + private System.Windows.Forms.ToolStripMenuItem mnuPlateAdd; + private System.Windows.Forms.ToolStripButton btnFirstPlate; + private System.Windows.Forms.ToolStripButton btnLastPlate; + private System.Windows.Forms.ToolStripButton btnZoomToFit; + private System.Windows.Forms.ToolStripMenuItem mnuNestRemoveEmptyPlates; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomIn; + private System.Windows.Forms.ToolStripMenuItem mnuViewZoomOut; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem14; + private System.Windows.Forms.ToolStripMenuItem mnuSetOffsetIncrement; + private System.Windows.Forms.ToolStripMenuItem mnuSetRotationIncrement; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem15; + private System.Windows.Forms.ToolStripMenuItem mnuPlateSetAsDefault; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem16; + private System.Windows.Forms.ToolStripButton btnZoomOut; + private System.Windows.Forms.ToolStripButton btnZoomIn; + private System.Windows.Forms.ToolStripStatusLabel statusLabel1; + private System.Windows.Forms.ToolStripStatusLabel spacerLabel; + private System.Windows.Forms.ToolStripMenuItem mnuPlateFill; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem18; + private System.Windows.Forms.ToolStripMenuItem mnuPlateRemove; + private System.Windows.Forms.ToolStripButton btnSave; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem19; + private System.Windows.Forms.ToolStripMenuItem calculateCutTimeToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem20; + private System.Windows.Forms.ToolStripMenuItem calculateCutTimeToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem mnuSequenceParts; + private System.Windows.Forms.ToolStripMenuItem closeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem runAutoNestToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem9; + private System.Windows.Forms.ToolStripMenuItem autoSequenceCurrentPlateToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem manualSequenceToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem autoSequenceAllPlatesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem mnuToolsMeasureArea; + private System.Windows.Forms.ToolStripButton btnSaveAs; + private System.Windows.Forms.ToolStripMenuItem centerPartsToolStripMenuItem; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/MainForm.cs b/Source/OpenNest/Forms/MainForm.cs new file mode 100644 index 0000000..de99a04 --- /dev/null +++ b/Source/OpenNest/Forms/MainForm.cs @@ -0,0 +1,891 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows.Forms; +using OpenNest.Actions; +using OpenNest.IO; +using OpenNest.Properties; + +namespace OpenNest.Forms +{ + public partial class MainForm : Form + { + private EditNestForm activeForm; + private bool clickUpdateLocation; + + private const float ZoomInFactor = 1.5f; + private const float ZoomOutFactor = 1.0f / ZoomInFactor; + + public MainForm() + { + InitializeComponent(); + LoadSettings(); + + var renderer = new ToolStripRenderer(ToolbarTheme.Toolbar); + menuStrip1.Renderer = renderer; + toolStrip1.Renderer = renderer; + + mnuViewZoomIn.ShortcutKeyDisplayString = "Ctrl+Plus"; + mnuViewZoomOut.ShortcutKeyDisplayString = "Ctrl+Minus"; + + clickUpdateLocation = true; + + this.SetBevel(false); + + LoadPosts(); + EnableCheck(); + UpdateStatus(); + } + + private string GetNestName(DateTime date, int id) + { + var month = date.Month.ToString().PadLeft(2, '0'); + var day = date.Day.ToString().PadLeft(2, '0'); + + return string.Format("N{0}{1}-{2}", month, day, id.ToString().PadLeft(3, '0')); + } + + private void LoadNest(Nest nest, FormWindowState windowState = FormWindowState.Maximized) + { + var editForm = new EditNestForm(nest); + editForm.MdiParent = this; + editForm.PlateChanged += (sender, e) => + { + NavigationEnableCheck(); + UpdatePlateStatus(); + }; + editForm.WindowState = windowState; + editForm.Show(); + editForm.PlateView.ZoomToFit(); + } + + private void LoadSettings() + { + var screen = Screen.PrimaryScreen.Bounds; + + if (screen.Contains(Settings.Default.MainWindowLocation)) + Location = Settings.Default.MainWindowLocation; + + if (Settings.Default.MainWindowSize.Width <= screen.Width && + Settings.Default.MainWindowSize.Height <= screen.Height) + { + Size = Settings.Default.MainWindowSize; + } + + WindowState = Settings.Default.MainWindowState; + } + + private void SaveSettings() + { + Settings.Default.MainWindowLocation = Location; + Settings.Default.MainWindowSize = Size; + Settings.Default.MainWindowState = WindowState; + Settings.Default.Save(); + } + + private void EnableCheck() + { + NavigationEnableCheck(); + + var hasValue = activeForm != null; + + btnZoomToFit.Enabled = hasValue; + mnuFileSave.Enabled = hasValue; + btnSave.Enabled = hasValue; + btnSaveAs.Enabled = hasValue; + mnuFileSaveAs.Enabled = hasValue; + mnuFileExport.Enabled = hasValue; + mnuFileExportAll.Enabled = hasValue; + btnZoomOut.Enabled = hasValue; + btnZoomIn.Enabled = hasValue; + mnuEdit.Visible = hasValue; + mnuView.Visible = hasValue; + mnuNest.Visible = hasValue; + mnuPlate.Visible = hasValue; + mnuWindow.Visible = hasValue; + mnuToolsAlign.Visible = hasValue; + mnuToolsMeasureArea.Visible = hasValue; + + toolStripMenuItem14.Visible = hasValue; + mnuSetOffsetIncrement.Visible = hasValue; + mnuSetRotationIncrement.Visible = hasValue; + toolStripMenuItem15.Visible = hasValue; + } + + private void NavigationEnableCheck() + { + if (activeForm == null) + { + mnuNestPreviousPlate.Enabled = false; + btnPreviousPlate.Enabled = false; + + mnuNestNextPlate.Enabled = false; + btnNextPlate.Enabled = false; + + mnuNestFirstPlate.Enabled = false; + btnFirstPlate.Enabled = false; + + mnuNestLastPlate.Enabled = false; + btnLastPlate.Enabled = false; + } + else + { + mnuNestPreviousPlate.Enabled = !activeForm.IsFirstPlate(); + btnPreviousPlate.Enabled = !activeForm.IsFirstPlate(); + + mnuNestNextPlate.Enabled = !activeForm.IsLastPlate(); + btnNextPlate.Enabled = !activeForm.IsLastPlate(); + + mnuNestFirstPlate.Enabled = activeForm.PlateCount > 0 && !activeForm.IsFirstPlate(); + btnFirstPlate.Enabled = activeForm.PlateCount > 0 && !activeForm.IsFirstPlate(); + + mnuNestLastPlate.Enabled = activeForm.PlateCount > 0 && !activeForm.IsLastPlate(); + btnLastPlate.Enabled = activeForm.PlateCount > 0 && !activeForm.IsLastPlate(); + } + } + + private void UpdateLocationStatus() + { + if (activeForm == null) + { + locationStatusLabel.Text = string.Empty; + return; + } + + locationStatusLabel.Text = string.Format("Location: [{0}, {1}]", + activeForm.PlateView.CurrentPoint.X.ToString("n4"), + activeForm.PlateView.CurrentPoint.Y.ToString("n4")); + } + + private void UpdatePlateStatus() + { + if (activeForm == null) + { + plateIndexStatusLabel.Text = string.Empty; + plateSizeStatusLabel.Text = string.Empty; + plateQtyStatusLabel.Text = string.Empty; + return; + } + + plateIndexStatusLabel.Text = string.Format( + "Plate: {0} of {1}", + activeForm.CurrentPlateIndex + 1, + activeForm.PlateCount); + + plateSizeStatusLabel.Text = string.Format( + "Size: {0}", + activeForm.PlateView.Plate.Size); + + plateQtyStatusLabel.Text = string.Format( + "Qty: {0}", + activeForm.PlateView.Plate.Quantity); + } + + private void UpdateStatus() + { + UpdateLocationStatus(); + UpdatePlateStatus(); + } + + private void UpdateLocationMode() + { + if (activeForm == null) + return; + + if (clickUpdateLocation) + { + clickUpdateLocation = true; + locationStatusLabel.ForeColor = Color.Gray; + activeForm.PlateView.MouseMove -= PlateView_MouseMove; + activeForm.PlateView.MouseClick += PlateView_MouseClick; + } + else + { + clickUpdateLocation = false; + locationStatusLabel.ForeColor = Color.Black; + activeForm.PlateView.MouseMove += PlateView_MouseMove; + activeForm.PlateView.MouseClick -= PlateView_MouseClick; + } + } + + private void LoadPosts() + { + var exepath = Assembly.GetEntryAssembly().Location; + var postprocessordir = Path.Combine(Path.GetDirectoryName(exepath), "Posts"); + + if (Directory.Exists(postprocessordir)) + { + foreach (var file in Directory.GetFiles(postprocessordir, "*.dll")) + { + var types = Assembly.LoadFile(file).GetTypes(); + + foreach (var type in types) + { + if (type.GetInterfaces().Contains(typeof(IPostProcessor)) == false) + continue; + + var postProcessor = Activator.CreateInstance(type) as IPostProcessor; + var postProcessorMenuItem = new ToolStripMenuItem(postProcessor.Name); + postProcessorMenuItem.Tag = postProcessor; + postProcessorMenuItem.Click += PostProcessor_Click; + mnuNestPost.DropDownItems.Add(postProcessorMenuItem); + } + } + } + + mnuNestPost.Visible = mnuNestPost.DropDownItems.Count > 0; + } + + #region Overrides + + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + this.Refresh(); + } + + protected override void OnMdiChildActivate(EventArgs e) + { + base.OnMdiChildActivate(e); + + if (activeForm != null) + { + activeForm.PlateView.MouseMove -= PlateView_MouseMove; + activeForm.PlateView.MouseClick -= PlateView_MouseClick; + activeForm.PlateView.StatusChanged -= PlateView_StatusChanged; + } + + activeForm = ActiveMdiChild as EditNestForm; + + EnableCheck(); + UpdatePlateStatus(); + UpdateLocationStatus(); + + if (activeForm == null) + { + statusLabel1.Text = ""; + return; + } + + UpdateLocationMode(); + activeForm.PlateView.StatusChanged += PlateView_StatusChanged; + mnuViewDrawRapids.Checked = activeForm.PlateView.DrawRapid; + mnuViewDrawBounds.Checked = activeForm.PlateView.DrawBounds; + statusLabel1.Text = activeForm.PlateView.Status; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + if (Settings.Default.CreateNewNestOnOpen) + New_Click(this, new EventArgs()); + } + + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + base.OnClosing(e); + SaveSettings(); + } + + #endregion + + #region File Menu Events + + private void New_Click(object sender, EventArgs e) + { + var windowState = ActiveMdiChild != null + ? ActiveMdiChild.WindowState + : FormWindowState.Maximized; + + Nest nest; + + if (File.Exists(Properties.Settings.Default.NestTemplatePath)) + { + var reader = new NestReader(Properties.Settings.Default.NestTemplatePath); + nest = reader.Read(); + } + else + { + nest = new Nest(); + nest.Units = Properties.Settings.Default.DefaultUnit; + nest.PlateDefaults.EdgeSpacing = new Spacing(0, 0, 0, 0); + nest.PlateDefaults.PartSpacing = 0; + nest.PlateDefaults.Size = new OpenNest.Size(100, 100); + nest.PlateDefaults.Quadrant = 1; + } + + nest.DateCreated = DateTime.Now; + nest.DateLastModified = DateTime.Now; + + if (DateTime.Now.Date != Settings.Default.LastNestCreatedDate.Date) + { + Settings.Default.NestNumber = 1; + Settings.Default.LastNestCreatedDate = DateTime.Now.Date; + } + + nest.Name = GetNestName(DateTime.Now, Settings.Default.NestNumber++); + LoadNest(nest, windowState); + } + + private void Open_Click(object sender, EventArgs e) + { + var dlg = new OpenFileDialog(); + dlg.Filter = "Nest Files (*.zip) | *.zip"; + dlg.Multiselect = true; + + if (dlg.ShowDialog() == DialogResult.OK) + { + var reader = new NestReader(dlg.FileName); + var nest = reader.Read(); + LoadNest(nest); + } + } + + private void Save_Click(object sender, EventArgs e) + { + if (activeForm != null) + activeForm.Save(); + } + + private void SaveAs_Click(object sender, EventArgs e) + { + if (activeForm != null) + activeForm.SaveAs(); + } + + private void Export_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.Export(); + } + + private void ExportAll_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.ExportAll(); + } + + private void Exit_Click(object sender, EventArgs e) + { + Close(); + } + + #endregion File Menu Events + + #region Edit Menu Events + + private void EditCut_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + + var selectedParts = activeForm.PlateView.SelectedParts; + + if (selectedParts.Count > 0) + { + var partsToClone = selectedParts.Select(p => p.BasePart).ToList(); + activeForm.PlateView.SetAction(typeof(ActionClone), partsToClone); + + foreach (var part in partsToClone) + activeForm.PlateView.Plate.Parts.Remove(part); + } + } + + private void EditPaste_Click(object sender, EventArgs e) + { + } + + private void EditCopy_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + + var selectedParts = activeForm.PlateView.SelectedParts; + + if (selectedParts.Count > 0) + { + var partsToClone = selectedParts.Select(p => p.BasePart).ToList(); + activeForm.PlateView.SetAction(typeof(ActionClone), partsToClone); + } + } + + private void EditSelectAll_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.SelectAllParts(); + } + + #endregion Edit Menu Events + + #region View Menu Events + + private void ToggleDrawRapids_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.ToggleRapid(); + mnuViewDrawRapids.Checked = activeForm.PlateView.DrawRapid; + } + + private void ToggleDrawBounds_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.ToggleDrawBounds(); + mnuViewDrawBounds.Checked = activeForm.PlateView.DrawBounds; + } + + private void ZoomToArea_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.SetAction(typeof(ActionZoomWindow)); + } + + private void ZoomToFit_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.ZoomToFit(); + } + + private void ZoomToPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.ZoomToPlate(); + } + + private void ZoomToSelected_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.ZoomToSelected(); + } + + private void ZoomIn_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + + var pt = new Point( + activeForm.PlateView.Width / 2, + activeForm.PlateView.Height / 2); + + activeForm.PlateView.ZoomToControlPoint(pt, ZoomInFactor); + } + + private void ZoomOut_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + + var pt = new Point( + activeForm.PlateView.Width / 2, + activeForm.PlateView.Height / 2); + + activeForm.PlateView.ZoomToControlPoint(pt, ZoomOutFactor); + } + + #endregion View Menu Events + + #region Tools Menu Events + + private void MeasureArea_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + activeForm.PlateView.SetAction(typeof(ActionSelectArea)); + } + + private void SetOffsetIncrement_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + var form = new SetValueForm(); + form.Text = "Set Offset Increment"; + form.Value = activeForm.PlateView.OffsetIncrementDistance; + + if (form.ShowDialog() == DialogResult.OK) + activeForm.PlateView.OffsetIncrementDistance = form.Value; + } + + private void SetRotationIncrement_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + var form = new SetValueForm(); + form.Text = "Set Rotation Increment"; + form.Value = activeForm.PlateView.RotateIncrementAngle; + form.Maximum = 360; + + if (form.ShowDialog() == DialogResult.OK) + activeForm.PlateView.RotateIncrementAngle = form.Value; + } + + private void Options_Click(object sender, EventArgs e) + { + var form = new OptionsForm(); + form.ShowDialog(); + } + + private void AlignLeft_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Left); + } + + private void AlignRight_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Right); + } + + private void AlignTop_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Top); + } + + private void AlignBottom_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Bottom); + } + + private void AlignVertical_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Vertically); + } + + private void AlignHorizontal_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.Horizontally); + } + + private void EvenlySpaceHorizontally_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.EvenlySpaceHorizontally); + } + + private void EvenlySpaceVertically_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.PlateView.AlignSelected(AlignType.EvenlySpaceVertically); + } + + #endregion Tools Menu Events + + #region Nest Menu Events + + private void Import_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.Import(); + } + + private void EditNest_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.ShowNestInfoEditor(); + } + + private void RemoveEmptyPlates_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.Nest.Plates.RemoveEmptyPlates(); + } + + private void LoadFirstPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.LoadFirstPlate(); + } + + private void LoadLastPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.LoadLastPlate(); + } + + private void LoadPreviousPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.LoadPreviousPlate(); + } + + private void LoadNextPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.LoadNextPlate(); + } + + private void RunAutoNest_Click(object sender, EventArgs e) + { + var form = new AutoNestForm(activeForm.Nest); + form.AllowPlateCreation = true; + + if (form.ShowDialog() != System.Windows.Forms.DialogResult.OK) + return; + + var items = form.GetNestItems(); + var qty = new int[items.Count]; + + while (true) + { + for (int i = 0; i < items.Count; i++) + qty[i] = items[i].Drawing.Quantity.Remaining; + + var plate = activeForm.PlateView.Plate.Parts.Count > 0 + ? activeForm.Nest.CreatePlate() + : activeForm.PlateView.Plate; + + var engine = new NestEngine(plate); + + if (!engine.Pack(items)) + break; + + activeForm.Nest.UpdateDrawingQuantities(); + + for (int i = 0; i < items.Count; i++) + items[i].Quantity -= qty[i] - items[i].Drawing.Quantity.Remaining; + } + } + + private void SequenceAllPlates_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + activeForm.AutoSequenceAllPlates(); + } + + private void PostProcessor_Click(object sender, EventArgs e) + { + var menuItem = (ToolStripMenuItem)sender; + var postProcessor = menuItem.Tag as IPostProcessor; + + if (postProcessor == null) + return; + + var dialog = new SaveFileDialog(); + dialog.Filter = "CNC File (*.cnc) | *.cnc"; + dialog.FileName = activeForm.Nest.Name; + + if (dialog.ShowDialog() == DialogResult.OK) + { + var path = dialog.FileName; + postProcessor.Post(activeForm.Nest, path); + } + } + + private void CalculateNestCutTime_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + activeForm.CalculateNestCutTime(); + } + + #endregion Nest Menu Events + + #region Plate Menu Events + + private void SetAsNestDefault_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.SetCurrentPlateAsNestDefault(); + } + + private void FillPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + if (activeForm.Nest.Drawings.Count == 0) + return; + + var form = new FillPlateForm(activeForm.Nest.Drawings); + form.ShowDialog(); + + var drawing = form.SelectedDrawing; + + if (drawing == null) + return; + + var engine = new NestEngine(activeForm.PlateView.Plate); + engine.Fill(new NestItem + { + Drawing = drawing + }); + + activeForm.PlateView.Invalidate(); + } + + private void FillArea_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + if (activeForm.Nest.Drawings.Count == 0) + return; + + var form = new FillPlateForm(activeForm.Nest.Drawings); + form.ShowDialog(); + + var drawing = form.SelectedDrawing; + + if (drawing == null) + return; + + activeForm.PlateView.SetAction(typeof(ActionFillArea), drawing); + } + + private void AddPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.Nest.CreatePlate(); + NavigationEnableCheck(); + } + + private void EditPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.EditPlate(); + } + + private void RemovePlate_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.RemoveCurrentPlate(); + } + + private void ResizeToFitParts_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.ResizePlateToFitParts(); + UpdatePlateStatus(); + } + + private void RotateCw_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.RotateCw(); + } + + private void RotateCcw_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.RotateCcw(); + } + + private void Rotate180_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.Rotate180(); + } + + private void OpenInExternalCad_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + activeForm.OpenCurrentPlate(); + } + + private void CalculatePlateCutTime_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + activeForm.CalculateCurrentPlateCutTime(); + } + + private void AutoSequenceCurrentPlate_Click(object sender, EventArgs e) + { + if (activeForm == null) + return; + + activeForm.AutoSequenceCurrentPlate(); + } + + private void ManualSequenceParts_Click(object sender, EventArgs e) + { + if (activeForm == null || activeForm.PlateView.Plate.Parts.Count < 2) + return; + + activeForm.PlateView.SetAction(typeof(ActionSetSequence)); + } + + #endregion Plate Menu Events + + #region Window Menu Events + + private void TileVertical_Click(object sender, EventArgs e) + { + LayoutMdi(MdiLayout.TileVertical); + } + + private void TileHorizontal_Click(object sender, EventArgs e) + { + LayoutMdi(MdiLayout.TileHorizontal); + } + + private void CascadeWindows_Click(object sender, EventArgs e) + { + LayoutMdi(MdiLayout.Cascade); + } + + private void Close_Click(object sender, EventArgs e) + { + if (ActiveMdiChild != null) + ActiveMdiChild.Close(); + } + + private void CloseAll_Click(object sender, EventArgs e) + { + foreach (var mdiChild in MdiChildren) + mdiChild.Close(); + } + + #endregion Window Menu Events + + #region Statusbar Events + + private void LocationStatusLabel_Click(object sender, EventArgs e) + { + clickUpdateLocation = !clickUpdateLocation; + UpdateLocationMode(); + } + + #endregion Statusbar Events + + #region PlateView Events + + private void PlateView_MouseMove(object sender, MouseEventArgs e) + { + UpdateLocationStatus(); + } + + private void PlateView_MouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + UpdateLocationStatus(); + } + + private void PlateView_StatusChanged(object sender, EventArgs e) + { + statusLabel1.Text = activeForm.PlateView.Status; + } + + #endregion PlateView Events + + private void centerPartsToolStripMenuItem_Click(object sender, EventArgs e) + { + var plateCenter = this.activeForm.PlateView.Plate.BoundingBox(false).Center; + var partsCenter = this.activeForm.PlateView.Parts.GetBoundingBox().Center; + + var offset = plateCenter - partsCenter; + + foreach (var part in this.activeForm.PlateView.Parts) + { + part.Offset(offset); + } + + activeForm.PlateView.Invalidate(); + } + } +} diff --git a/Source/OpenNest/Forms/MainForm.resx b/Source/OpenNest/Forms/MainForm.resx new file mode 100644 index 0000000..24c4237 --- /dev/null +++ b/Source/OpenNest/Forms/MainForm.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + + 126, 17 + + + 236, 17 + + + 59 + + \ No newline at end of file diff --git a/Source/OpenNest/Forms/OptionsForm.Designer.cs b/Source/OpenNest/Forms/OptionsForm.Designer.cs new file mode 100644 index 0000000..c1d4776 --- /dev/null +++ b/Source/OpenNest/Forms/OptionsForm.Designer.cs @@ -0,0 +1,256 @@ +namespace OpenNest.Forms +{ + partial class OptionsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.checkBox1 = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.numericUpDown1 = new OpenNest.Controls.NumericUpDown(); + this.label2 = new System.Windows.Forms.Label(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.numericUpDown2 = new OpenNest.Controls.NumericUpDown(); + this.button1 = new System.Windows.Forms.Button(); + this.saveButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // checkBox1 + // + this.checkBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.checkBox1.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.checkBox1, 2); + this.checkBox1.Location = new System.Drawing.Point(3, 130); + this.checkBox1.Name = "checkBox1"; + this.checkBox1.Size = new System.Drawing.Size(281, 20); + this.checkBox1.TabIndex = 7; + this.checkBox1.Text = "Create a new nest on startup"; + this.checkBox1.UseVisualStyleBackColor = true; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 52); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(145, 16); + this.label1.TabIndex = 3; + this.label1.Text = "Auto-size plate factor:"; + // + // numericUpDown1 + // + this.numericUpDown1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown1.DecimalPlaces = 4; + this.numericUpDown1.Increment = new decimal(new int[] { + 125, + 0, + 0, + 196608}); + this.numericUpDown1.Location = new System.Drawing.Point(154, 49); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(130, 22); + this.numericUpDown1.Suffix = ""; + this.numericUpDown1.TabIndex = 4; + this.toolTip1.SetToolTip(this.numericUpDown1, "The amount to round the plate size up."); + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 92); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(145, 16); + this.label2.TabIndex = 5; + this.label2.Text = "Import spline precision:\r\n"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 4; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 297F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.textBox1, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.label2, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.label3, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.checkBox1, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown2, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown1, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.button1, 3, 0); + this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 12); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(684, 160); + this.tableLayoutPanel1.TabIndex = 0; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.SetColumnSpan(this.textBox1, 2); + this.textBox1.Location = new System.Drawing.Point(154, 9); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(427, 22); + this.textBox1.TabIndex = 1; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 12); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(145, 16); + this.label3.TabIndex = 0; + this.label3.Text = "Nest Template Path:"; + // + // numericUpDown2 + // + this.numericUpDown2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown2.Location = new System.Drawing.Point(154, 89); + this.numericUpDown2.Maximum = new decimal(new int[] { + 360, + 0, + 0, + 0}); + this.numericUpDown2.Minimum = new decimal(new int[] { + 3, + 0, + 0, + 0}); + this.numericUpDown2.Name = "numericUpDown2"; + this.numericUpDown2.Size = new System.Drawing.Size(130, 22); + this.numericUpDown2.Suffix = ""; + this.numericUpDown2.TabIndex = 6; + this.numericUpDown2.Value = new decimal(new int[] { + 200, + 0, + 0, + 0}); + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(588, 6); + this.button1.Margin = new System.Windows.Forms.Padding(4); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(92, 28); + this.button1.TabIndex = 2; + this.button1.Text = "Browse..."; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.BrowseNestTemplatePath_Click); + // + // saveButton + // + this.saveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.saveButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.saveButton.Location = new System.Drawing.Point(507, 11); + this.saveButton.Margin = new System.Windows.Forms.Padding(4); + this.saveButton.Name = "saveButton"; + this.saveButton.Size = new System.Drawing.Size(90, 28); + this.saveButton.TabIndex = 0; + this.saveButton.Text = "Save"; + this.saveButton.UseVisualStyleBackColor = true; + this.saveButton.Click += new System.EventHandler(this.SaveSettings_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(605, 11); + this.cancelButton.Margin = new System.Windows.Forms.Padding(4); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(90, 28); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.cancelButton); + this.bottomPanel1.Controls.Add(this.saveButton); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 368); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(708, 50); + this.bottomPanel1.TabIndex = 1; + // + // OptionsForm + // + this.AcceptButton = this.saveButton; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(708, 418); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "OptionsForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Options"; + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button saveButton; + private System.Windows.Forms.Label label1; + private Controls.NumericUpDown numericUpDown1; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.Label label2; + private Controls.NumericUpDown numericUpDown2; + private Controls.BottomPanel bottomPanel1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button button1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/OptionsForm.cs b/Source/OpenNest/Forms/OptionsForm.cs new file mode 100644 index 0000000..48896f7 --- /dev/null +++ b/Source/OpenNest/Forms/OptionsForm.cs @@ -0,0 +1,50 @@ +using System.Windows.Forms; +using OpenNest.Properties; + +namespace OpenNest.Forms +{ + public partial class OptionsForm : Form + { + public OptionsForm() + { + InitializeComponent(); + } + + protected override void OnLoad(System.EventArgs e) + { + base.OnLoad(e); + LoadSettings(); + } + + private void LoadSettings() + { + textBox1.Text = Settings.Default.NestTemplatePath; + checkBox1.Checked = Settings.Default.CreateNewNestOnOpen; + numericUpDown1.Value = (decimal)Settings.Default.AutoSizePlateFactor; + numericUpDown2.Value = (decimal)Settings.Default.ImportSplinePrecision; + } + + private void SaveSettings() + { + Settings.Default.NestTemplatePath = textBox1.Text; + Settings.Default.CreateNewNestOnOpen = checkBox1.Checked; + Settings.Default.AutoSizePlateFactor = (double)numericUpDown1.Value; + Settings.Default.ImportSplinePrecision = (int)numericUpDown2.Value; + Settings.Default.Save(); + } + + private void SaveSettings_Click(object sender, System.EventArgs e) + { + SaveSettings(); + } + + private void BrowseNestTemplatePath_Click(object sender, System.EventArgs e) + { + var dlg = new OpenFileDialog(); + dlg.Filter = "Template File|*.nstdot"; + + if (dlg.ShowDialog() == DialogResult.OK) + textBox1.Text = dlg.FileName; + } + } +} diff --git a/Source/OpenNest/Forms/OptionsForm.resx b/Source/OpenNest/Forms/OptionsForm.resx new file mode 100644 index 0000000..df8339b --- /dev/null +++ b/Source/OpenNest/Forms/OptionsForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + \ No newline at end of file diff --git a/Source/OpenNest/Forms/SequenceForm.Designer.cs b/Source/OpenNest/Forms/SequenceForm.Designer.cs new file mode 100644 index 0000000..e3d2d39 --- /dev/null +++ b/Source/OpenNest/Forms/SequenceForm.Designer.cs @@ -0,0 +1,111 @@ +namespace OpenNest.Forms +{ + partial class SequenceForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.label1 = new System.Windows.Forms.Label(); + this.numericUpDown1 = new OpenNest.Controls.NumericUpDown(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.numericUpDown1, 1, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(220, 36); + this.tableLayoutPanel1.TabIndex = 0; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 11); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(62, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Sequence :"; + // + // numericUpDown1 + // + this.numericUpDown1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDown1.Location = new System.Drawing.Point(71, 8); + this.numericUpDown1.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(146, 20); + this.numericUpDown1.TabIndex = 1; + this.numericUpDown1.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numericUpDown1.Leave += new System.EventHandler(this.numericUpDown1_Leave); + // + // SequenceForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(220, 36); + this.ControlBox = false; + this.Controls.Add(this.tableLayoutPanel1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Location = new System.Drawing.Point(100, 100); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SequenceForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "Set Sequence"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label1; + public Controls.NumericUpDown numericUpDown1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/SequenceForm.cs b/Source/OpenNest/Forms/SequenceForm.cs new file mode 100644 index 0000000..d800475 --- /dev/null +++ b/Source/OpenNest/Forms/SequenceForm.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class SequenceForm : Form + { + public SequenceForm() + { + InitializeComponent(); + } + + private void numericUpDown1_Leave(object sender, EventArgs e) + { + numericUpDown1.Validate(); + } + } +} diff --git a/Source/OpenNest/Forms/SequenceForm.resx b/Source/OpenNest/Forms/SequenceForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/SequenceForm.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/Source/OpenNest/Forms/SetValueForm.Designer.cs b/Source/OpenNest/Forms/SetValueForm.Designer.cs new file mode 100644 index 0000000..82d49ac --- /dev/null +++ b/Source/OpenNest/Forms/SetValueForm.Designer.cs @@ -0,0 +1,122 @@ +namespace OpenNest.Forms +{ + partial class SetValueForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.buttonSet = new System.Windows.Forms.Button(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.labelIncrement = new System.Windows.Forms.Label(); + this.numericUpDownValue = new OpenNest.Controls.NumericUpDown(); + this.tableLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownValue)).BeginInit(); + this.SuspendLayout(); + // + // buttonSet + // + this.buttonSet.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.buttonSet.DialogResult = System.Windows.Forms.DialogResult.OK; + this.buttonSet.Location = new System.Drawing.Point(256, 6); + this.buttonSet.Name = "buttonSet"; + this.buttonSet.Size = new System.Drawing.Size(85, 28); + this.buttonSet.TabIndex = 2; + this.buttonSet.Text = "Set"; + this.buttonSet.UseVisualStyleBackColor = true; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 3; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.buttonSet, 2, 0); + this.tableLayoutPanel1.Controls.Add(this.labelIncrement, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.numericUpDownValue, 1, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 5); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(344, 41); + this.tableLayoutPanel1.TabIndex = 0; + // + // labelIncrement + // + this.labelIncrement.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.labelIncrement.AutoSize = true; + this.labelIncrement.Location = new System.Drawing.Point(3, 12); + this.labelIncrement.Name = "labelIncrement"; + this.labelIncrement.Size = new System.Drawing.Size(72, 16); + this.labelIncrement.TabIndex = 0; + this.labelIncrement.Text = "Increment :"; + this.labelIncrement.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // numericUpDownValue + // + this.numericUpDownValue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.numericUpDownValue.DecimalPlaces = 4; + this.numericUpDownValue.Location = new System.Drawing.Point(80, 9); + this.numericUpDownValue.Margin = new System.Windows.Forms.Padding(2); + this.numericUpDownValue.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.numericUpDownValue.Name = "numericUpDownValue"; + this.numericUpDownValue.Size = new System.Drawing.Size(171, 22); + this.numericUpDownValue.TabIndex = 1; + // + // SetValueForm + // + this.AcceptButton = this.buttonSet; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(356, 51); + this.Controls.Add(this.tableLayoutPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SetValueForm"; + this.Padding = new System.Windows.Forms.Padding(6, 5, 6, 5); + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownValue)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button buttonSet; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label labelIncrement; + private Controls.NumericUpDown numericUpDownValue; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/SetValueForm.cs b/Source/OpenNest/Forms/SetValueForm.cs new file mode 100644 index 0000000..8a9be2c --- /dev/null +++ b/Source/OpenNest/Forms/SetValueForm.cs @@ -0,0 +1,48 @@ +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class SetValueForm : Form + { + public SetValueForm() + { + InitializeComponent(); + } + + protected override bool ProcessDialogKey(Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + break; + } + + return base.ProcessDialogKey(keyData); + } + + public double Minimum + { + get { return (double)numericUpDownValue.Minimum; } + set { numericUpDownValue.Minimum = (decimal)value; } + } + + public double Maximum + { + get { return (double)numericUpDownValue.Maximum; } + set { numericUpDownValue.Maximum = (decimal)value; } + } + + public double Increment + { + get { return (double)numericUpDownValue.Increment; } + set { numericUpDownValue.Increment = (decimal)value; } + } + + public double Value + { + get { return (double)numericUpDownValue.Value; } + set { numericUpDownValue.Value = (decimal)value; } + } + } +} diff --git a/Source/OpenNest/Forms/SetValueForm.resx b/Source/OpenNest/Forms/SetValueForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/SetValueForm.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/Source/OpenNest/Forms/TimingForm.Designer.cs b/Source/OpenNest/Forms/TimingForm.Designer.cs new file mode 100644 index 0000000..a48c44c --- /dev/null +++ b/Source/OpenNest/Forms/TimingForm.Designer.cs @@ -0,0 +1,360 @@ +namespace OpenNest.Forms +{ + partial class TimingForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.horizontalLine2 = new OpenNest.Controls.HorizontalLine(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.cutTimeLabel = new System.Windows.Forms.Label(); + this.cutDistanceLabel = new System.Windows.Forms.Label(); + this.rapidDistanceLabel = new System.Windows.Forms.Label(); + this.interectionsLabel = new System.Windows.Forms.Label(); + this.piercesLabel = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.feedrateLabel = new System.Windows.Forms.Label(); + this.rapidLabel = new System.Windows.Forms.Label(); + this.pierceTimeLabel = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.bottomPanel1 = new OpenNest.Controls.BottomPanel(); + this.tableLayoutPanel1.SuspendLayout(); + this.bottomPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.horizontalLine2, 0, 5); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.label4, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.label3, 0, 4); + this.tableLayoutPanel1.Controls.Add(this.label5, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.cutTimeLabel, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.cutDistanceLabel, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.rapidDistanceLabel, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.interectionsLabel, 1, 3); + this.tableLayoutPanel1.Controls.Add(this.piercesLabel, 1, 4); + this.tableLayoutPanel1.Controls.Add(this.label6, 0, 6); + this.tableLayoutPanel1.Controls.Add(this.label7, 0, 7); + this.tableLayoutPanel1.Controls.Add(this.label8, 0, 8); + this.tableLayoutPanel1.Controls.Add(this.feedrateLabel, 1, 6); + this.tableLayoutPanel1.Controls.Add(this.rapidLabel, 1, 7); + this.tableLayoutPanel1.Controls.Add(this.pierceTimeLabel, 1, 8); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(5); + this.tableLayoutPanel1.RowCount = 10; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(340, 319); + this.tableLayoutPanel1.TabIndex = 1; + // + // horizontalLine2 + // + this.horizontalLine2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.SetColumnSpan(this.horizontalLine2, 2); + this.horizontalLine2.Location = new System.Drawing.Point(9, 176); + this.horizontalLine2.Margin = new System.Windows.Forms.Padding(4); + this.horizontalLine2.Name = "horizontalLine2"; + this.horizontalLine2.Size = new System.Drawing.Size(322, 10); + this.horizontalLine2.TabIndex = 3; + this.horizontalLine2.Text = "horizontalLine1"; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 13); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(113, 16); + this.label1.TabIndex = 0; + this.label1.Text = "Cut Time :"; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(9, 45); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(113, 16); + this.label2.TabIndex = 0; + this.label2.Text = "Cut Distance :\r\n"; + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 77); + this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(113, 16); + this.label4.TabIndex = 0; + this.label4.Text = "Rapid Distance :"; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(9, 141); + this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(113, 16); + this.label3.TabIndex = 0; + this.label3.Text = "# of Pierces :"; + // + // label5 + // + this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(9, 109); + this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(113, 16); + this.label5.TabIndex = 0; + this.label5.Text = "# of Intersections :"; + // + // cutTimeLabel + // + this.cutTimeLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.cutTimeLabel.AutoSize = true; + this.cutTimeLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cutTimeLabel.Location = new System.Drawing.Point(130, 13); + this.cutTimeLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.cutTimeLabel.Name = "cutTimeLabel"; + this.cutTimeLabel.Size = new System.Drawing.Size(201, 16); + this.cutTimeLabel.TabIndex = 0; + this.cutTimeLabel.Text = "-----"; + // + // cutDistanceLabel + // + this.cutDistanceLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.cutDistanceLabel.AutoSize = true; + this.cutDistanceLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cutDistanceLabel.Location = new System.Drawing.Point(130, 45); + this.cutDistanceLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.cutDistanceLabel.Name = "cutDistanceLabel"; + this.cutDistanceLabel.Size = new System.Drawing.Size(201, 16); + this.cutDistanceLabel.TabIndex = 0; + this.cutDistanceLabel.Text = "-----"; + // + // rapidDistanceLabel + // + this.rapidDistanceLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.rapidDistanceLabel.AutoSize = true; + this.rapidDistanceLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.rapidDistanceLabel.Location = new System.Drawing.Point(130, 77); + this.rapidDistanceLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.rapidDistanceLabel.Name = "rapidDistanceLabel"; + this.rapidDistanceLabel.Size = new System.Drawing.Size(201, 16); + this.rapidDistanceLabel.TabIndex = 0; + this.rapidDistanceLabel.Text = "-----"; + // + // interectionsLabel + // + this.interectionsLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.interectionsLabel.AutoSize = true; + this.interectionsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.interectionsLabel.Location = new System.Drawing.Point(130, 109); + this.interectionsLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.interectionsLabel.Name = "interectionsLabel"; + this.interectionsLabel.Size = new System.Drawing.Size(201, 16); + this.interectionsLabel.TabIndex = 0; + this.interectionsLabel.Text = "-----"; + // + // piercesLabel + // + this.piercesLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.piercesLabel.AutoSize = true; + this.piercesLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.piercesLabel.Location = new System.Drawing.Point(130, 141); + this.piercesLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.piercesLabel.Name = "piercesLabel"; + this.piercesLabel.Size = new System.Drawing.Size(201, 16); + this.piercesLabel.TabIndex = 0; + this.piercesLabel.Text = "-----"; + // + // label6 + // + this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(9, 205); + this.label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(113, 16); + this.label6.TabIndex = 0; + this.label6.Text = "Feedrate :"; + // + // label7 + // + this.label7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(9, 237); + this.label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(113, 16); + this.label7.TabIndex = 0; + this.label7.Text = "Rapid Feedrate :"; + // + // label8 + // + this.label8.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(9, 269); + this.label8.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(113, 16); + this.label8.TabIndex = 0; + this.label8.Text = "Pierce Time :"; + // + // feedrateLabel + // + this.feedrateLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.feedrateLabel.AutoSize = true; + this.feedrateLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.feedrateLabel.Location = new System.Drawing.Point(130, 205); + this.feedrateLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.feedrateLabel.Name = "feedrateLabel"; + this.feedrateLabel.Size = new System.Drawing.Size(201, 16); + this.feedrateLabel.TabIndex = 0; + this.feedrateLabel.Text = "-----"; + // + // rapidLabel + // + this.rapidLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.rapidLabel.AutoSize = true; + this.rapidLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.rapidLabel.Location = new System.Drawing.Point(130, 237); + this.rapidLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.rapidLabel.Name = "rapidLabel"; + this.rapidLabel.Size = new System.Drawing.Size(201, 16); + this.rapidLabel.TabIndex = 0; + this.rapidLabel.Text = "-----"; + // + // pierceTimeLabel + // + this.pierceTimeLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.pierceTimeLabel.AutoSize = true; + this.pierceTimeLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.pierceTimeLabel.Location = new System.Drawing.Point(130, 269); + this.pierceTimeLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.pierceTimeLabel.Name = "pierceTimeLabel"; + this.pierceTimeLabel.Size = new System.Drawing.Size(201, 16); + this.pierceTimeLabel.TabIndex = 0; + this.pierceTimeLabel.Text = "-----"; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Location = new System.Drawing.Point(237, 11); + this.button1.Margin = new System.Windows.Forms.Padding(4); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(90, 28); + this.button1.TabIndex = 4; + this.button1.Text = "Close"; + this.button1.UseVisualStyleBackColor = true; + // + // bottomPanel1 + // + this.bottomPanel1.Controls.Add(this.button1); + this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.bottomPanel1.Location = new System.Drawing.Point(0, 319); + this.bottomPanel1.Name = "bottomPanel1"; + this.bottomPanel1.Size = new System.Drawing.Size(340, 50); + this.bottomPanel1.TabIndex = 5; + // + // TimingForm + // + this.AcceptButton = this.button1; + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button1; + this.ClientSize = new System.Drawing.Size(340, 369); + this.Controls.Add(this.tableLayoutPanel1); + this.Controls.Add(this.bottomPanel1); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Margin = new System.Windows.Forms.Padding(4); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "TimingForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Timing Information"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.bottomPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label cutTimeLabel; + private System.Windows.Forms.Label cutDistanceLabel; + private System.Windows.Forms.Label rapidDistanceLabel; + private System.Windows.Forms.Label interectionsLabel; + private System.Windows.Forms.Label piercesLabel; + private Controls.HorizontalLine horizontalLine2; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label feedrateLabel; + private System.Windows.Forms.Label rapidLabel; + private System.Windows.Forms.Label pierceTimeLabel; + private Controls.BottomPanel bottomPanel1; + } +} \ No newline at end of file diff --git a/Source/OpenNest/Forms/TimingForm.cs b/Source/OpenNest/Forms/TimingForm.cs new file mode 100644 index 0000000..67579e2 --- /dev/null +++ b/Source/OpenNest/Forms/TimingForm.cs @@ -0,0 +1,72 @@ +using System; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class TimingForm : Form + { + public TimingForm() + { + InitializeComponent(); + } + + public Units Units { get; set; } + + public void SetCutTime(TimeSpan timeSpan) + { + cutTimeLabel.Text = GetTimeMsg(timeSpan); + } + + public void SetCutDistance(double dist) + { + cutDistanceLabel.Text = string.Format("{0} {1}", Math.Round(dist, 4), UnitsHelper.GetShortString(Units)); + } + + public void SetRapidDistance(double dist) + { + rapidDistanceLabel.Text = string.Format("{0} {1}", Math.Round(dist, 4), UnitsHelper.GetShortString(Units)); + } + + public void SetIntersectionCount(int count) + { + interectionsLabel.Text = count.ToString(); + } + + public void SetPierceCount(int count) + { + piercesLabel.Text = count.ToString(); + } + + public void SetCutParameters(CutParameters cutparams) + { + feedrateLabel.Text = string.Format("{0} {1}/{2}", cutparams.Feedrate, UnitsHelper.GetShortString(Units), UnitsHelper.GetShortTimeUnit(Units)); + rapidLabel.Text = string.Format("{0} {1}/{2}", cutparams.RapidTravelRate, UnitsHelper.GetShortString(Units), UnitsHelper.GetShortTimeUnit(Units)); + pierceTimeLabel.Text = GetTimeMsg(cutparams.PierceTime); + } + + public static string GetTimeMsg(TimeSpan time) + { + int hrs = time.Days * 24 + time.Hours; + int min = time.Minutes; + int sec = time.Seconds; + + string msg = string.Empty; + + if (hrs > 0) + { + msg += hrs.ToString() + "h "; + msg += min.ToString().PadLeft(2, '0') + "m "; + msg += sec.ToString().PadLeft(2, '0') + "s"; + } + else if (min > 0) + { + msg += min.ToString().PadLeft(2, '0') + "m "; + msg += sec.ToString().PadLeft(2, '0') + "s"; + } + else + msg += time.TotalSeconds.ToString("n0").PadLeft(2, '0') + "s"; + + return msg; + } + } +} diff --git a/Source/OpenNest/Forms/TimingForm.resx b/Source/OpenNest/Forms/TimingForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/OpenNest/Forms/TimingForm.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/Source/OpenNest/GraphicsHelper.cs b/Source/OpenNest/GraphicsHelper.cs new file mode 100644 index 0000000..ebb954f --- /dev/null +++ b/Source/OpenNest/GraphicsHelper.cs @@ -0,0 +1,307 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest +{ + internal static class GraphicsHelper + { + public static GraphicsPath GetGraphicsPath(this Program pgm) + { + var path = new GraphicsPath(); + var curpos = Vector.Zero; + + AddProgram(path, pgm, pgm.Mode, ref curpos); + + return path; + } + + public static GraphicsPath GetGraphicsPath(this Program pgm, Vector origin) + { + var path = new GraphicsPath(); + var curpos = origin; + + AddProgram(path, pgm, pgm.Mode, ref curpos); + + return path; + } + + public static GraphicsPath GetGraphicsPath(this Shape shape) + { + var path = new GraphicsPath(); + + AddShape(path, shape); + + return path; + } + + public static Image GetImage(this Program pgm, System.Drawing.Size size) + { + return pgm.GetImage(size, Pens.Black, null); + } + + public static Image GetImage(this Program pgm, System.Drawing.Size size, Pen pen) + { + return pgm.GetImage(size, pen, null); + } + + public static Image GetImage(this Program pgm, System.Drawing.Size size, Pen pen, Brush brush) + { + var img = new Bitmap(size.Width, size.Height); + var path = pgm.GetGraphicsPath(); + var bounds = path.GetBounds(); + + var scalex = (size.Height - 10) / bounds.Height; + var scaley = (size.Width - 10) / bounds.Width; + var scale = scalex < scaley ? scalex : scaley; + + var matrix = new Matrix(); + matrix.Scale(scale, -scale); + + path.Transform(matrix); + + bounds = path.GetBounds(); + + var offset = new PointF( + (size.Width - bounds.Width) * 0.5f - bounds.X, + (size.Height - bounds.Height) * 0.5f - bounds.Y); + + var graphics = Graphics.FromImage(img); + graphics.TranslateTransform(offset.X, offset.Y); + + if (brush != null) + graphics.FillPath(brush, path); + + if (pen == null) + pen = Pens.Black; + + graphics.DrawPath(pen, path); + + matrix.Dispose(); + graphics.Dispose(); + + return img; + } + + private static void AddArc(GraphicsPath path, CircularMove arc, Mode mode, ref Vector curpos) + { + var endpt = arc.EndPoint; + var center = arc.CenterPoint; + + if (mode == Mode.Incremental) + { + endpt += curpos; + center += curpos; + } + + // start angle in degrees + var startAngle = Angle.ToDegrees(Math.Atan2( + curpos.Y - center.Y, + curpos.X - center.X)); + + // end angle in degrees + var endAngle = Angle.ToDegrees(Math.Atan2( + endpt.Y - center.Y, + endpt.X - center.X)); + + endAngle = Angle.NormalizeDeg(endAngle); + startAngle = Angle.NormalizeDeg(startAngle); + + if (arc.Rotation == RotationType.CCW && endAngle < startAngle) + endAngle += 360.0; + else if (arc.Rotation == RotationType.CW && startAngle < endAngle) + startAngle += 360.0; + + var dx = endpt.X - center.X; + var dy = endpt.Y - center.Y; + + var radius = Math.Sqrt(dx * dx + dy * dy); + + var pt = new PointF((float)(center.X - radius), (float)(center.Y - radius)); + var size = (float)(radius * 2.0); + + if (startAngle.IsEqualTo(endAngle)) + { + path.AddEllipse(pt.X, pt.Y, size, size); + } + else + { + var sweepAngle = (endAngle - startAngle); + + path.AddArc( + pt.X, pt.Y, + size, size, + (float)startAngle, + (float)sweepAngle); + + if (arc.Layer == LayerType.Leadin || arc.Layer == LayerType.Leadout) + { + path.AddArc(pt.X, pt.Y, size, size, (float)(-startAngle + sweepAngle), (float)-sweepAngle); + path.CloseFigure(); + } + } + + curpos = endpt; + } + + private static void AddLine(GraphicsPath path, LinearMove line, Mode mode, ref Vector curpos) + { + var pt = line.EndPoint; + + if (mode == Mode.Incremental) + pt += curpos; + + var pt1 = new PointF((float)curpos.X, (float)curpos.Y); + var pt2 = new PointF((float)pt.X, (float)pt.Y); + + path.AddLine(pt1, pt2); + + if (line.Layer == LayerType.Leadin || line.Layer == LayerType.Leadout) + path.CloseFigure(); + + curpos = pt; + } + + private static void AddLine(GraphicsPath path, RapidMove line, Mode mode, ref Vector curpos) + { + var pt = line.EndPoint; + + if (mode == Mode.Incremental) + pt += curpos; + + path.CloseFigure(); + + curpos = pt; + } + + private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector curpos) + { + mode = pgm.Mode; + + for (int i = 0; i < pgm.Length; ++i) + { + var code = pgm[i]; + + switch (code.Type) + { + case CodeType.CircularMove: + AddArc(path, (CircularMove)code, mode, ref curpos); + break; + + case CodeType.LinearMove: + AddLine(path, (LinearMove)code, mode, ref curpos); + break; + + case CodeType.RapidMove: + AddLine(path, (RapidMove)code, mode, ref curpos); + break; + + case CodeType.SubProgramCall: + { + var tmpmode = mode; + var subpgm = (SubProgramCall)code; + + if (subpgm.Program != null) + { + path.StartFigure(); + AddProgram(path, subpgm.Program, mode, ref curpos); + } + + mode = tmpmode; + break; + } + } + } + } + + private static void AddArc(GraphicsPath path, Arc arc) + { + var diameter = arc.Diameter; + + var endAngle = Angle.NormalizeDeg(Angle.ToDegrees(arc.EndAngle)); + var startAngle = Angle.NormalizeDeg(Angle.ToDegrees(arc.StartAngle)); + + if (arc.Rotation == RotationType.CCW && endAngle < startAngle) + endAngle += 360.0; + else if (arc.Rotation == RotationType.CW && startAngle < endAngle) + startAngle += 360.0; + + var sweepAngle = (endAngle - startAngle); + + path.AddArc( + (float)(arc.Center.X - arc.Radius), + (float)(arc.Center.Y - arc.Radius), + (float)diameter, + (float)diameter, + (float)(startAngle), + (float)sweepAngle); + } + + private static void AddCircle(GraphicsPath path, Circle circle) + { + var diameter = circle.Diameter; + + path.AddEllipse( + (float)(circle.Center.X - circle.Radius), + (float)(circle.Center.Y - circle.Radius), + (float)diameter, + (float)diameter); + } + + private static void AddLine(GraphicsPath path, Line line) + { + path.AddLine( + (float)line.StartPoint.X, + (float)line.StartPoint.Y, + (float)line.EndPoint.X, + (float)line.EndPoint.Y); + } + + private static void AddShape(GraphicsPath path, Shape shape) + { + foreach (var entity in shape.Entities) + { + switch (entity.Type) + { + case EntityType.Arc: + AddArc(path, (Arc)entity); + break; + + case EntityType.Circle: + AddCircle(path, (Circle)entity); + break; + + case EntityType.Line: + AddLine(path, (Line)entity); + break; + + case EntityType.Polygon: + AddPolygon(path, (Polygon)entity); + break; + + case EntityType.Shape: + var subpath = new GraphicsPath(); + AddShape(subpath, (Shape)entity); + path.AddPath(subpath, false); + subpath.Dispose(); + break; + } + } + } + + private static void AddPolygon(GraphicsPath path, Polygon polygon) + { + var pts = new PointF[polygon.Vertices.Count]; + + for (int i = 0; i < pts.Length; i++) + { + var pt = polygon.Vertices[i]; + pts[i] = new PointF((float)pt.X, (float)pt.Y); + } + + path.AddPolygon(pts); + } + } +} diff --git a/Source/OpenNest/IO/DxfExporter.cs b/Source/OpenNest/IO/DxfExporter.cs new file mode 100644 index 0000000..2e29c51 --- /dev/null +++ b/Source/OpenNest/IO/DxfExporter.cs @@ -0,0 +1,287 @@ +using System; +using System.Diagnostics; +using System.IO; +using netDxf; +using netDxf.Entities; +using netDxf.Tables; +using OpenNest.CNC; + +namespace OpenNest.IO +{ + using Layer = netDxf.Tables.Layer; + using Line = netDxf.Entities.Line; + + public class DxfExporter + { + private const double RadToDeg = 180.0 / Math.PI; + + private DxfDocument doc; + private Vector2 curpos; + private Mode mode; + private readonly Layer cutLayer; + private readonly Layer rapidLayer; + private readonly Layer plateLayer; + + public DxfExporter() + { + doc = new DxfDocument(); + + cutLayer = new Layer("Cut"); + cutLayer.Color = AciColor.Red; + + rapidLayer = new Layer("Rapid"); + rapidLayer.Color = AciColor.Blue; + rapidLayer.LineType = LineType.Dashed; + + plateLayer = new Layer("Plate"); + plateLayer.Color = AciColor.Cyan; + } + + public void ExportProgram(Program program, Stream stream) + { + doc = new DxfDocument(); + AddProgram(program); + doc.Save(stream); + } + + public bool ExportProgram(Program program, string path) + { + Stream stream = null; + bool success = false; + + try + { + stream = File.Create(path); + ExportProgram(program, stream); + success = true; + } + catch + { + Debug.Fail("DxfExporter.ExportProgram failed to write program to file: " + path); + } + finally + { + if (stream != null) + stream.Close(); + } + + return success; + } + + public void ExportPlate(Plate plate, Stream stream) + { + doc = new DxfDocument(); + AddPlateOutline(plate); + + foreach (var part in plate.Parts) + { + var endpt = part.Location.ToNetDxf(); + var line = new netDxf.Entities.Line(curpos, endpt); + line.Layer = rapidLayer; + doc.AddEntity(line); + curpos = part.Location.ToNetDxf(); + AddProgram(part.Program); + } + + doc.Save(stream); + } + + public bool ExportPlate(Plate plate, string path) + { + Stream stream = null; + bool success = false; + + try + { + stream = File.Create(path); + ExportPlate(plate, stream); + success = true; + } + catch + { + Debug.Fail("DxfExporter.ExportPlate failed to write plate to file: " + path); + } + finally + { + if (stream != null) + stream.Close(); + } + + return success; + } + + private void AddPlateOutline(Plate plate) + { + Vector2 pt1; + Vector2 pt2; + Vector2 pt3; + Vector2 pt4; + + switch (plate.Quadrant) + { + case 1: + pt1 = new Vector2(0, 0); + pt2 = new Vector2(0, plate.Size.Height); + pt3 = new Vector2(plate.Size.Width, plate.Size.Height); + pt4 = new Vector2(plate.Size.Width, 0); + break; + + case 2: + pt1 = new Vector2(0, 0); + pt2 = new Vector2(0, plate.Size.Height); + pt3 = new Vector2(-plate.Size.Width, plate.Size.Height); + pt4 = new Vector2(-plate.Size.Width, 0); + break; + + case 3: + pt1 = new Vector2(0, 0); + pt2 = new Vector2(0, -plate.Size.Height); + pt3 = new Vector2(-plate.Size.Width, -plate.Size.Height); + pt4 = new Vector2(-plate.Size.Width, 0); + break; + + case 4: + pt1 = new Vector2(0, 0); + pt2 = new Vector2(0, -plate.Size.Height); + pt3 = new Vector2(plate.Size.Width, -plate.Size.Height); + pt4 = new Vector2(plate.Size.Width, 0); + break; + + default: + return; + } + + doc.AddEntity(new Line(pt1, pt2) { Layer = plateLayer }); + doc.AddEntity(new Line(pt2, pt3) { Layer = plateLayer }); + doc.AddEntity(new Line(pt3, pt4) { Layer = plateLayer }); + doc.AddEntity(new Line(pt4, pt1) { Layer = plateLayer }); + + pt1.X += plate.EdgeSpacing.Left; + pt1.Y += plate.EdgeSpacing.Bottom; + + pt2.X = pt1.X; + pt2.Y -= plate.EdgeSpacing.Top; + + pt3.X -= plate.EdgeSpacing.Right; + pt3.Y = pt2.Y; + + pt4.X = pt3.X; + pt4.Y = pt1.Y; + + doc.AddEntity(new Line(pt1, pt2) { Layer = plateLayer, LineType = LineType.Dashed }); + doc.AddEntity(new Line(pt2, pt3) { Layer = plateLayer, LineType = LineType.Dashed }); + doc.AddEntity(new Line(pt3, pt4) { Layer = plateLayer, LineType = LineType.Dashed }); + doc.AddEntity(new Line(pt4, pt1) { Layer = plateLayer, LineType = LineType.Dashed }); + } + + private void AddProgram(Program program) + { + mode = program.Mode; + + for (int i = 0; i < program.Length; ++i) + { + var code = program[i]; + + switch (code.Type) + { + case CodeType.CircularMove: + var arc = (CircularMove)code; + AddCircularMove(arc); + break; + + case CodeType.LinearMove: + var line = (LinearMove)code; + AddLinearMove(line); + break; + + case CodeType.RapidMove: + var rapid = (RapidMove)code; + AddRapidMove(rapid); + break; + + case CodeType.SubProgramCall: + var tmpmode = mode; + var subpgm = (CNC.SubProgramCall)code; + AddProgram(subpgm.Program); + mode = tmpmode; + break; + } + } + } + + private void AddLinearMove(LinearMove line) + { + var pt = line.EndPoint.ToNetDxf(); + + if (mode == Mode.Incremental) + pt += curpos; + + var ln = new Line(curpos, pt); + ln.Layer = cutLayer; + doc.AddEntity(ln); + curpos = pt; + } + + private void AddRapidMove(RapidMove rapid) + { + var pt = rapid.EndPoint.ToNetDxf(); + + if (mode == Mode.Incremental) + pt += curpos; + + var ln = new Line(curpos, pt); + ln.Layer = rapidLayer; + doc.AddEntity(ln); + curpos = pt; + } + + private void AddCircularMove(CircularMove arc) + { + var center = arc.CenterPoint.ToNetDxf(); + var endpt = arc.EndPoint.ToNetDxf(); + + if (mode == Mode.Incremental) + { + endpt += curpos; + center += curpos; + } + + // start angle in radians + var startAngle = Math.Atan2( + curpos.Y - center.Y, + curpos.X - center.X); + + // end angle in radians + var endAngle = Math.Atan2( + endpt.Y - center.Y, + endpt.X - center.X); + + // convert the angles to degrees + startAngle = Angle.ToDegrees(startAngle); + endAngle = Angle.ToDegrees(endAngle); + + if (arc.Rotation == OpenNest.RotationType.CW) + Generic.Swap(ref startAngle, ref endAngle); + + var dx = endpt.X - center.X; + var dy = endpt.Y - center.Y; + + var radius = Math.Sqrt(dx * dx + dy * dy); + + if (startAngle.IsEqualTo(endAngle)) + { + var circle = new Circle(center, radius); + circle.Layer = cutLayer; + doc.AddEntity(circle); + } + else + { + var arc2 = new Arc(center, radius, startAngle, endAngle); + arc2.Layer = cutLayer; + doc.AddEntity(arc2); + } + + curpos = endpt; + } + } +} diff --git a/Source/OpenNest/IO/DxfImporter.cs b/Source/OpenNest/IO/DxfImporter.cs new file mode 100644 index 0000000..fba8051 --- /dev/null +++ b/Source/OpenNest/IO/DxfImporter.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using netDxf; +using OpenNest.Geometry; + +namespace OpenNest.IO +{ + public class DxfImporter + { + public int SplinePrecision { get; set; } + + public DxfImporter() + { + } + + private List GetGeometry(DxfDocument doc) + { + var entities = new List(); + var lines = new List(doc.Lines.Count); + var arcs = new List(doc.Arcs.Count); + + foreach (var spline in doc.Splines) + lines.AddRange(spline.ToOpenNest(SplinePrecision)); + + foreach (var polyline in doc.LwPolylines) + lines.AddRange(polyline.ToOpenNest()); + + foreach (var ellipse in doc.Ellipses) + lines.AddRange(ellipse.ToOpenNest(SplinePrecision)); + + foreach (var line in doc.Lines) + lines.Add(line.ToOpenNest()); + + foreach (var arc in doc.Arcs) + arcs.Add(arc.ToOpenNest()); + + foreach (var circle in doc.Circles) + entities.Add(circle.ToOpenNest()); + + foreach (var polyline in doc.Polylines) + lines.AddRange(polyline.ToOpenNest()); + + Helper.Optimize(lines); + Helper.Optimize(arcs); + + entities.AddRange(lines); + entities.AddRange(arcs); + + return entities; + } + + public bool GetGeometry(Stream stream, out List geometry) + { + bool success = false; + + try + { + var doc = DxfDocument.Load(stream); + geometry = GetGeometry(doc); + success = true; + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + geometry = new List(); + } + finally + { + if (stream != null) + stream.Close(); + } + + return success; + } + + public bool GetGeometry(string path, out List geometry) + { + Stream stream = null; + bool success = false; + + try + { + var doc = DxfDocument.Load(path); + geometry = GetGeometry(doc); + success = true; + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + geometry = new List(); + } + finally + { + if (stream != null) + stream.Close(); + } + + return success; + } + } +} diff --git a/Source/OpenNest/IO/Extensions.cs b/Source/OpenNest/IO/Extensions.cs new file mode 100644 index 0000000..4738d3d --- /dev/null +++ b/Source/OpenNest/IO/Extensions.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.Linq; +using OpenNest.Geometry; + +namespace OpenNest.IO +{ + internal static class Extensions + { + public static Vector ToOpenNest(this netDxf.Vector2 v) + { + return new Vector(v.X, v.Y); + } + + public static Vector ToOpenNest(this netDxf.Vector3 v) + { + return new Vector(v.X, v.Y); + } + + public static Vector ToOpenNest(this netDxf.Entities.PolylineVertex v) + { + return new Vector(v.Location.X, v.Location.Y); + } + + public static Vector ToOpenNest(this netDxf.Entities.LwPolylineVertex v) + { + return new Vector(v.Location.X, v.Location.Y); + } + + public static Arc ToOpenNest(this netDxf.Entities.Arc arc) + { + return new Arc( + arc.Center.X, arc.Center.Y, arc.Radius, + Angle.ToRadians(arc.StartAngle), + Angle.ToRadians(arc.EndAngle)) + { + Layer = arc.Layer.ToOpenNest() + }; + } + + public static Circle ToOpenNest(this netDxf.Entities.Circle circle) + { + return new Circle( + circle.Center.X, circle.Center.Y, + circle.Radius) + { + Layer = circle.Layer.ToOpenNest() + }; + } + + public static Line ToOpenNest(this netDxf.Entities.Line line) + { + return new Line( + line.StartPoint.X, line.StartPoint.Y, + line.EndPoint.X, line.EndPoint.Y) + { + Layer = line.Layer.ToOpenNest() + }; + } + + public static List ToOpenNest(this netDxf.Entities.Spline spline, int precision = 200) + { + var lines = new List(); + var pts = spline.PolygonalVertexes(precision); + + if (pts.Count == 0) + return lines; + + var lastPoint = pts[0].ToOpenNest(); + + for (int i = 1; i < pts.Count; i++) + { + var nextPoint = pts[i].ToOpenNest(); + + lines.Add(new Line( + lastPoint, + nextPoint) { Layer = spline.Layer.ToOpenNest() }); + + lastPoint = nextPoint; + } + + if (spline.IsClosed) + lines.Add(new Line(lastPoint, pts[0].ToOpenNest()) { Layer = spline.Layer.ToOpenNest() }); + + return lines; + } + + public static List ToOpenNest(this netDxf.Entities.Polyline polyline) + { + var lines = new List(); + + if (polyline.Vertexes.Count == 0) + return lines; + + var lastPoint = polyline.Vertexes[0].ToOpenNest(); + + for (int i = 1; i < polyline.Vertexes.Count; i++) + { + var nextPoint = polyline.Vertexes[i].ToOpenNest(); + + lines.Add(new Line( + lastPoint, + nextPoint) { Layer = polyline.Layer.ToOpenNest() }); + + lastPoint = nextPoint; + } + + if (polyline.IsClosed) + lines.Add(new Line(lastPoint, polyline.Vertexes[0].ToOpenNest()) { Layer = polyline.Layer.ToOpenNest() }); + + return lines; + } + + public static List ToOpenNest(this netDxf.Entities.LwPolyline polyline) + { + var lines = new List(); + + if (polyline.Vertexes.Count == 0) + return lines; + + var lastPoint = polyline.Vertexes[0].ToOpenNest(); + + for (int i = 1; i < polyline.Vertexes.Count; i++) + { + var nextPoint = polyline.Vertexes[i].ToOpenNest(); + + lines.Add(new Line( + lastPoint, + nextPoint) { Layer = polyline.Layer.ToOpenNest() }); + + lastPoint = nextPoint; + } + + if (polyline.IsClosed) + lines.Add(new Line(lastPoint, polyline.Vertexes[0].ToOpenNest()) { Layer = polyline.Layer.ToOpenNest() }); + + return lines; + } + + public static List ToOpenNest(this netDxf.Entities.Ellipse ellipse, int precision = 200) + { + var lines = ellipse.ToPolyline(precision).ToOpenNest(); + + if (lines.Count < 2) + return lines; + + var first = lines.First(); + var last = lines.Last(); + + // workaround for ellipse.ToPolyline not connecting the last and first point. + lines.Add(new Line(last.EndPoint, first.StartPoint) { Layer = ellipse.Layer.ToOpenNest() }); + + return lines; + } + + public static Layer ToOpenNest(this netDxf.Tables.Layer layer) + { + return new Layer(layer.Name) + { + Color = layer.Color.ToColor(), + IsVisible = layer.IsVisible + }; + } + + public static netDxf.Vector2 ToNetDxf(this Vector v) + { + return new netDxf.Vector2(v.X, v.Y); + } + } +} diff --git a/Source/OpenNest/IO/NestReader.cs b/Source/OpenNest/IO/NestReader.cs new file mode 100644 index 0000000..c3355a9 --- /dev/null +++ b/Source/OpenNest/IO/NestReader.cs @@ -0,0 +1,466 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; +using Ionic.Zip; +using OpenNest.CNC; + +namespace OpenNest.IO +{ + public sealed class NestReader + { + private ZipFile zipFile; + private Dictionary plateDict; + private Dictionary drawingDict; + private Dictionary programDict; + private Dictionary plateProgramDict; + private Stream stream; + private Nest nest; + + private NestReader() + { + plateDict = new Dictionary(); + drawingDict = new Dictionary(); + programDict = new Dictionary(); + plateProgramDict = new Dictionary(); + nest = new Nest(); + } + + public NestReader(string file) + : this() + { + stream = new FileStream(file, FileMode.Open, FileAccess.ReadWrite); + zipFile = ZipFile.Read(stream); + } + + public NestReader(Stream stream) + : this() + { + zipFile = ZipFile.Read(stream); + } + + public Nest Read() + { + const string plateExtensionPattern = "plate-\\d\\d\\d"; + const string programExtensionPattern = "program-\\d\\d\\d"; + + foreach (var entry in zipFile.Entries) + { + var memstream = new MemoryStream(); + entry.Extract(memstream); + + memstream.Position = 0; + + switch (entry.FileName) + { + case "info": + ReadNestInfo(memstream); + continue; + + case "drawing-info": + ReadDrawingInfo(memstream); + continue; + + case "plate-info": + ReadPlateInfo(memstream); + continue; + } + + if (Regex.IsMatch(entry.FileName, programExtensionPattern)) + { + ReadProgram(memstream, entry.FileName); + continue; + } + + if (Regex.IsMatch(entry.FileName, plateExtensionPattern)) + { + ReadPlate(memstream, entry.FileName); + continue; + } + } + + LinkProgramsToDrawings(); + LinkPartsToPlates(); + + AddPlatesToNest(); + AddDrawingsToNest(); + + stream.Close(); + + return nest; + } + + private void ReadNestInfo(Stream stream) + { + var reader = XmlReader.Create(stream); + var spacing = new Spacing(); + + while (reader.Read()) + { + if (!reader.IsStartElement()) + continue; + + switch (reader.Name) + { + case "Nest": + nest.Name = reader["name"]; + break; + + case "Units": + Units units; + TryParseEnum(reader.ReadString(), out units); + nest.Units = units; + break; + + case "Customer": + nest.Customer = reader.ReadString(); + break; + + case "DateCreated": + nest.DateCreated = DateTime.Parse(reader.ReadString()); + break; + + case "DateLastModified": + nest.DateLastModified = DateTime.Parse(reader.ReadString()); + break; + + case "Notes": + nest.Notes = Uri.UnescapeDataString(reader.ReadString()); + break; + + case "Size": + nest.PlateDefaults.Size = Size.Parse(reader.ReadString()); + break; + + case "Thickness": + nest.PlateDefaults.Thickness = double.Parse(reader.ReadString()); + break; + + case "Quadrant": + nest.PlateDefaults.Quadrant = int.Parse(reader.ReadString()); + break; + + case "PartSpacing": + nest.PlateDefaults.PartSpacing = double.Parse(reader.ReadString()); + break; + + case "Name": + nest.PlateDefaults.Material.Name = reader.ReadString(); + break; + + case "Grade": + nest.PlateDefaults.Material.Grade = reader.ReadString(); + break; + + case "Density": + nest.PlateDefaults.Material.Density = double.Parse(reader.ReadString()); + break; + + case "Left": + spacing.Left = double.Parse(reader.ReadString()); + break; + + case "Right": + spacing.Right = double.Parse(reader.ReadString()); + break; + + case "Top": + spacing.Top = double.Parse(reader.ReadString()); + break; + + case "Bottom": + spacing.Bottom = double.Parse(reader.ReadString()); + break; + } + } + + reader.Close(); + nest.PlateDefaults.EdgeSpacing = spacing; + } + + private void ReadDrawingInfo(Stream stream) + { + var reader = XmlReader.Create(stream); + Drawing drawing = null; + + while (reader.Read()) + { + if (!reader.IsStartElement()) + continue; + + switch (reader.Name) + { + case "Drawing": + var id = int.Parse(reader["id"]); + var name = reader["name"]; + + drawingDict.Add(id, (drawing = new Drawing(name))); + break; + + case "Customer": + drawing.Customer = reader.ReadString(); + break; + + case "Color": + { + var parts = reader.ReadString().Split(','); + + if (parts.Length == 3) + { + byte r = byte.Parse(parts[0]); + byte g = byte.Parse(parts[1]); + byte b = byte.Parse(parts[2]); + + drawing.Color = Color.FromArgb(r, g, b); + } + else if (parts.Length == 4) + { + byte a = byte.Parse(parts[0]); + byte r = byte.Parse(parts[1]); + byte g = byte.Parse(parts[2]); + byte b = byte.Parse(parts[3]); + + drawing.Color = Color.FromArgb(a, r, g, b); + } + } + break; + + case "Required": + drawing.Quantity.Required = int.Parse(reader.ReadString()); + break; + + case "Name": + drawing.Material.Name = reader.ReadString(); + break; + + case "Grade": + drawing.Material.Grade = reader.ReadString(); + break; + + case "Density": + drawing.Material.Density = double.Parse(reader.ReadString()); + break; + + case "Path": + drawing.Source.Path = reader.ReadString(); + break; + + case "Offset": + { + var parts = reader.ReadString().Split(','); + + if (parts.Length != 2) + continue; + + drawing.Source.Offset = new Vector(double.Parse(parts[0]), double.Parse(parts[1])); + } + break; + } + } + + reader.Close(); + } + + private void ReadPlateInfo(Stream stream) + { + var reader = XmlReader.Create(stream); + var spacing = new Spacing(); + Plate plate = null; + + while (reader.Read()) + { + if (!reader.IsStartElement()) + continue; + + switch (reader.Name) + { + case "Plate": + var id = int.Parse(reader["id"]); + + if (plate != null) + plate.EdgeSpacing = spacing; + + plateDict.Add(id, (plate = new Plate())); + break; + + case "Size": + plate.Size = Size.Parse(reader.ReadString()); + break; + + case "Qty": + plate.Quantity = int.Parse(reader.ReadString()); + break; + + case "Thickness": + plate.Thickness = double.Parse(reader.ReadString()); + break; + + case "Quadrant": + plate.Quadrant = int.Parse(reader.ReadString()); + break; + + case "PartSpacing": + plate.PartSpacing = double.Parse(reader.ReadString()); + break; + + case "Name": + plate.Material.Name = reader.ReadString(); + break; + + case "Grade": + plate.Material.Grade = reader.ReadString(); + break; + + case "Density": + plate.Material.Density = double.Parse(reader.ReadString()); + break; + + case "Left": + spacing.Left = double.Parse(reader.ReadString()); + break; + + case "Right": + spacing.Right = double.Parse(reader.ReadString()); + break; + + case "Top": + spacing.Top = double.Parse(reader.ReadString()); + break; + + case "Bottom": + spacing.Bottom = double.Parse(reader.ReadString()); + break; + } + } + + if (plate != null) + plate.EdgeSpacing = spacing; + } + + private void ReadProgram(Stream stream, string name) + { + var id = GetProgramId(name); + var reader = new ProgramReader(stream); + var pgm = reader.Read(); + programDict.Add(id, pgm); + } + + private void ReadPlate(Stream stream, string name) + { + var id = GetPlateId(name); + var reader = new ProgramReader(stream); + var pgm = reader.Read(); + plateProgramDict.Add(id, pgm); + } + + private void LinkProgramsToDrawings() + { + foreach (var drawingItem in drawingDict) + { + Program pgm; + + if (programDict.TryGetValue(drawingItem.Key, out pgm)) + drawingItem.Value.Program = pgm; + } + } + + private void LinkPartsToPlates() + { + foreach (var plateProgram in plateProgramDict) + { + var parts = CreateParts(plateProgram.Value); + + Plate plate; + + if (!plateDict.TryGetValue(plateProgram.Key, out plate)) + plate = new Plate(); + + plate.Parts.AddRange(parts); + plateDict[plateProgram.Key] = plate; + } + } + + private void AddPlatesToNest() + { + var plates = plateDict.OrderBy(i => i.Key).Select(i => i.Value).ToList(); + nest.Plates.AddRange(plates); + } + + private void AddDrawingsToNest() + { + var drawings = drawingDict.OrderBy(i => i.Key).Select(i => i.Value).ToList(); + drawings.ForEach(d => nest.Drawings.Add(d)); + } + + private List CreateParts(Program pgm) + { + var parts = new List(); + var pos = Vector.Zero; + + for (int i = 0; i < pgm.Codes.Count; i++) + { + var code = pgm.Codes[i]; + + switch (code.Type) + { + case CodeType.RapidMove: + pos = ((RapidMove)code).EndPoint; + break; + + case CodeType.SubProgramCall: + var subpgm = (SubProgramCall)code; + var dwg = drawingDict[subpgm.Id]; + var part = new Part(dwg); + part.Rotate(Angle.ToRadians(subpgm.Rotation)); + part.Offset(pos); + parts.Add(part); + break; + } + } + + return parts; + } + + private int GetPlateId(string name) + { + return int.Parse(name.Replace("plate-", "")); + } + + private int GetProgramId(string name) + { + return int.Parse(name.Replace("program-", "")); + } + + public static T ParseEnum(string value) + { + return (T)Enum.Parse(typeof(T), value, true); + } + + public static bool TryParseEnum(string value, out T e) + { + try + { + e = ParseEnum(value); + return true; + } + catch + { + e = ParseEnum(typeof(T).GetEnumValues().GetValue(0).ToString()); + } + + return false; + } + + private enum NestInfoSection + { + None, + DefaultPlate, + Material, + EdgeSpacing, + Source + } + } +} diff --git a/Source/OpenNest/IO/NestWriter.cs b/Source/OpenNest/IO/NestWriter.cs new file mode 100644 index 0000000..b80b783 --- /dev/null +++ b/Source/OpenNest/IO/NestWriter.cs @@ -0,0 +1,398 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using Ionic.Zip; +using OpenNest.CNC; + +namespace OpenNest.IO +{ + public sealed class NestWriter + { + /// + /// Number of decimal places the output is round to. + /// This number must have more decimal places than Tolerance.Epsilon + /// + private const int OutputPrecision = 10; + + private readonly Nest nest; + private ZipFile zipFile; + private Dictionary drawingDict; + + public NestWriter(Nest nest) + { + this.drawingDict = new Dictionary(); + this.nest = nest; + } + + public bool Write(string file) + { + this.nest.DateLastModified = DateTime.Now; + + SetDrawingIds(); + zipFile = new ZipFile(); + AddNestInfo(); + AddPlates(); + AddPlateInfo(); + AddDrawings(); + AddDrawingInfo(); + zipFile.Save(file); + + return true; + } + + private void SetDrawingIds() + { + int id = 1; + + foreach (var drawing in nest.Drawings) + { + drawingDict.Add(id, drawing); + id++; + } + } + + private void AddNestInfo() + { + var stream = new MemoryStream(); + var writer = XmlWriter.Create(stream, new XmlWriterSettings() + { + Indent = true + }); + + writer.WriteStartDocument(); + writer.WriteStartElement("Nest"); + writer.WriteAttributeString("name", nest.Name); + + writer.WriteElementString("Units", nest.Units.ToString()); + writer.WriteElementString("Customer", nest.Customer); + writer.WriteElementString("DateCreated", nest.DateCreated.ToString()); + writer.WriteElementString("DateLastModified", nest.DateLastModified.ToString()); + + writer.WriteStartElement("DefaultPlate"); + writer.WriteElementString("Size", nest.PlateDefaults.Size.ToString()); + writer.WriteElementString("Thickness", nest.PlateDefaults.Thickness.ToString()); + writer.WriteElementString("Quadrant", nest.PlateDefaults.Quadrant.ToString()); + writer.WriteElementString("PartSpacing", nest.PlateDefaults.PartSpacing.ToString()); + + writer.WriteStartElement("Material"); + writer.WriteElementString("Name", nest.PlateDefaults.Material.Name); + writer.WriteElementString("Grade", nest.PlateDefaults.Material.Grade); + writer.WriteElementString("Density", nest.PlateDefaults.Material.Density.ToString()); + writer.WriteEndElement(); + + writer.WriteStartElement("EdgeSpacing"); + writer.WriteElementString("Left", nest.PlateDefaults.EdgeSpacing.Left.ToString()); + writer.WriteElementString("Top", nest.PlateDefaults.EdgeSpacing.Top.ToString()); + writer.WriteElementString("Right", nest.PlateDefaults.EdgeSpacing.Right.ToString()); + writer.WriteElementString("Bottom", nest.PlateDefaults.EdgeSpacing.Bottom.ToString()); + writer.WriteEndElement(); + + writer.WriteElementString("Notes", Uri.EscapeDataString(nest.Notes)); + + writer.WriteEndElement(); // DefaultPlate + writer.WriteEndElement(); // Nest + + writer.WriteEndDocument(); + + writer.Flush(); + writer.Close(); + + stream.Position = 0; + + zipFile.AddEntry("info", stream); + } + + private void AddPlates() + { + int num = 1; + + foreach (var plate in nest.Plates) + { + var stream = new MemoryStream(); + var name = string.Format("plate-{0}", num.ToString().PadLeft(3, '0')); + + WritePlate(stream, plate); + zipFile.AddEntry(name, stream); + + num++; + } + } + + private void AddPlateInfo() + { + var stream = new MemoryStream(); + var writer = XmlWriter.Create(stream, new XmlWriterSettings() + { + Indent = true + }); + + writer.WriteStartDocument(); + writer.WriteStartElement("Plates"); + writer.WriteAttributeString("count", nest.Plates.Count.ToString()); + + for (int i = 0; i < nest.Plates.Count; ++i) + { + var plate = nest.Plates[i]; + + writer.WriteStartElement("Plate"); + writer.WriteAttributeString("id", (i + 1).ToString()); + + writer.WriteElementString("Quadrant", plate.Quadrant.ToString()); + writer.WriteElementString("Thickness", plate.Thickness.ToString()); + writer.WriteElementString("Size", plate.Size.ToString()); + writer.WriteElementString("Qty", plate.Quantity.ToString()); + writer.WriteElementString("PartSpacing", plate.PartSpacing.ToString()); + + writer.WriteStartElement("Material"); + writer.WriteElementString("Name", plate.Material.Name); + writer.WriteElementString("Grade", plate.Material.Grade); + writer.WriteElementString("Density", plate.Material.Density.ToString()); + writer.WriteEndElement(); + + writer.WriteStartElement("EdgeSpacing"); + writer.WriteElementString("Left", plate.EdgeSpacing.Left.ToString()); + writer.WriteElementString("Top", plate.EdgeSpacing.Top.ToString()); + writer.WriteElementString("Right", plate.EdgeSpacing.Right.ToString()); + writer.WriteElementString("Bottom", plate.EdgeSpacing.Bottom.ToString()); + writer.WriteEndElement(); + + writer.WriteEndElement(); // Plate + writer.Flush(); + } + + writer.WriteEndElement(); // Plates + writer.WriteEndDocument(); + + writer.Flush(); + writer.Close(); + + stream.Position = 0; + + zipFile.AddEntry("plate-info", stream); + } + + private void AddDrawings() + { + int num = 1; + + foreach (var dwg in nest.Drawings) + { + var stream = new MemoryStream(); + var name = string.Format("program-{0}", num.ToString().PadLeft(3, '0')); + + WriteDrawing(stream, dwg); + zipFile.AddEntry(name, stream); + + num++; + } + } + + private void AddDrawingInfo() + { + var stream = new MemoryStream(); + var writer = XmlWriter.Create(stream, new XmlWriterSettings() + { + Indent = true + }); + + writer.WriteStartDocument(); + writer.WriteStartElement("Drawings"); + writer.WriteAttributeString("count", nest.Drawings.Count.ToString()); + + int id = 1; + + foreach (var drawing in nest.Drawings) + { + writer.WriteStartElement("Drawing"); + writer.WriteAttributeString("id", id.ToString()); + writer.WriteAttributeString("name", drawing.Name); + + writer.WriteElementString("Customer", drawing.Customer); + writer.WriteElementString("Color", string.Format("{0}, {1}, {2}, {3}", drawing.Color.A, drawing.Color.R, drawing.Color.G, drawing.Color.B)); + + writer.WriteStartElement("Quantity"); + writer.WriteElementString("Required", drawing.Quantity.Required.ToString()); + writer.WriteElementString("Nested", drawing.Quantity.Nested.ToString()); + writer.WriteEndElement(); + + writer.WriteStartElement("Material"); + writer.WriteElementString("Name", drawing.Material.Name); + writer.WriteElementString("Grade", drawing.Material.Grade); + writer.WriteElementString("Density", drawing.Material.Density.ToString()); + writer.WriteEndElement(); + + writer.WriteStartElement("Source"); + writer.WriteElementString("Path", drawing.Source.Path); + writer.WriteElementString("Offset", string.Format("{0}, {1}", + drawing.Source.Offset.X, + drawing.Source.Offset.Y)); + writer.WriteEndElement(); // Source + + writer.WriteEndElement(); // Drawing + + id++; + } + + writer.WriteEndElement(); // Drawings + writer.WriteEndDocument(); + + writer.Flush(); + writer.Close(); + + stream.Position = 0; + + zipFile.AddEntry("drawing-info", stream); + } + + private void WritePlate(Stream stream, Plate plate) + { + var writer = new StreamWriter(stream); + writer.AutoFlush = true; + writer.WriteLine("G90"); + + foreach (var part in plate.Parts) + { + var match = drawingDict.Where(dwg => dwg.Value == part.BaseDrawing).FirstOrDefault(); + var id = match.Key; + + writer.WriteLine("G00X{0}Y{1}", part.Location.X, part.Location.Y); + writer.WriteLine("G65P{0}R{1}", id, Angle.ToDegrees(part.Rotation)); + } + + stream.Position = 0; + } + + private void WriteDrawing(Stream stream, Drawing drawing) + { + var program = drawing.Program; + var writer = new StreamWriter(stream); + writer.AutoFlush = true; + + writer.WriteLine(program.Mode == Mode.Absolute ? "G90" : "G91"); + + for (int i = 0; i < drawing.Program.Length; ++i) + { + var code = drawing.Program[i]; + writer.WriteLine(GetCodeString(code)); + } + + stream.Position = 0; + } + + private string GetCodeString(ICode code) + { + switch (code.Type) + { + case CodeType.CircularMove: + { + var sb = new StringBuilder(); + var circularMove = (CircularMove)code; + + if (circularMove.Rotation == RotationType.CW) + { + sb.Append(string.Format("G02X{0}Y{1}I{2}J{3}", + Math.Round(circularMove.EndPoint.X, OutputPrecision), + Math.Round(circularMove.EndPoint.Y, OutputPrecision), + Math.Round(circularMove.CenterPoint.X, OutputPrecision), + Math.Round(circularMove.CenterPoint.Y, OutputPrecision))); + } + else + { + sb.Append(string.Format("G03X{0}Y{1}I{2}J{3}", + Math.Round(circularMove.EndPoint.X, OutputPrecision), + Math.Round(circularMove.EndPoint.Y, OutputPrecision), + Math.Round(circularMove.CenterPoint.X, OutputPrecision), + Math.Round(circularMove.CenterPoint.Y, OutputPrecision))); + } + + if (circularMove.Layer != LayerType.Cut) + sb.Append(GetLayerString(circularMove.Layer)); + + return sb.ToString(); + } + + case CodeType.Comment: + { + var comment = (Comment)code; + return ":" + comment.Value; + } + + case CodeType.LinearMove: + { + var sb = new StringBuilder(); + var linearMove = (LinearMove)code; + + sb.Append(string.Format("G01X{0}Y{1}", + Math.Round(linearMove.EndPoint.X, OutputPrecision), + Math.Round(linearMove.EndPoint.Y, OutputPrecision))); + + if (linearMove.Layer != LayerType.Cut) + sb.Append(GetLayerString(linearMove.Layer)); + + return sb.ToString(); + } + + case CodeType.RapidMove: + { + var rapidMove = (RapidMove)code; + + return string.Format("G00X{0}Y{1}", + Math.Round(rapidMove.EndPoint.X, OutputPrecision), + Math.Round(rapidMove.EndPoint.Y, OutputPrecision)); + } + + case CodeType.SetFeedrate: + { + var setFeedrate = (Feedrate)code; + return "F" + setFeedrate.Value; + } + + case CodeType.SetKerf: + { + var setKerf = (Kerf)code; + + switch (setKerf.Value) + { + case KerfType.None: return "G40"; + case KerfType.Left: return "G41"; + case KerfType.Right: return "G42"; + } + + break; + } + + case CodeType.SubProgramCall: + { + var subProgramCall = (SubProgramCall)code; + break; + } + } + + return string.Empty; + } + + private string GetLayerString(LayerType layer) + { + switch (layer) + { + case LayerType.Display: + return ":DISPLAY"; + + case LayerType.Leadin: + return ":LEADIN"; + + case LayerType.Leadout: + return ":LEADOUT"; + + case LayerType.Scribe: + return ":SCRIBE"; + + default: + return string.Empty; + } + } + } +} diff --git a/Source/OpenNest/IO/ProgramReader.cs b/Source/OpenNest/IO/ProgramReader.cs new file mode 100644 index 0000000..3f7b182 --- /dev/null +++ b/Source/OpenNest/IO/ProgramReader.cs @@ -0,0 +1,389 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using OpenNest.CNC; + +namespace OpenNest.IO +{ + internal sealed class ProgramReader + { + private const int BufferSize = 200; + + private int codeIndex; + private CodeBlock block; + private CodeSection section; + private Program program; + private StreamReader reader; + + public ProgramReader(Stream stream) + { + reader = new StreamReader(stream); + program = new Program(); + } + + public Program Read() + { + string line; + + while ((line = reader.ReadLine()) != null) + { + block = ParseBlock(line); + ProcessCurrentBlock(); + } + + return program; + } + + private CodeBlock ParseBlock(string line) + { + var block = new CodeBlock(); + Code code = null; + for (int i = 0; i < line.Length; ++i) + { + var c = line[i]; + if (char.IsLetter(c)) + block.Add((code = new Code(c))); + else if (c == ':') + { + block.Add((new Code(c, line.Remove(0, i + 1).Trim()))); + break; + } + else if (code != null) + code.Value += c; + } + return block; + } + + private void ProcessCurrentBlock() + { + var code = GetFirstCode(); + + while (code != null) + { + switch (code.Id) + { + case ':': + program.Codes.Add(new Comment(code.Value)); + code = GetNextCode(); + break; + + case 'G': + int value = int.Parse(code.Value); + + switch (value) + { + case 0: + case 1: + section = CodeSection.Line; + ReadLine(value == 0); + code = GetCurrentCode(); + break; + + case 2: + case 3: + section = CodeSection.Arc; + ReadArc(value == 2 ? RotationType.CW : RotationType.CCW); + code = GetCurrentCode(); + break; + + case 65: + section = CodeSection.SubProgram; + ReadSubProgram(); + code = GetCurrentCode(); + break; + + case 40: + program.Codes.Add(new Kerf() { Value = KerfType.None }); + code = GetNextCode(); + break; + + case 41: + program.Codes.Add(new Kerf() { Value = KerfType.Left }); + code = GetNextCode(); + break; + + case 42: + program.Codes.Add(new Kerf() { Value = KerfType.Right }); + code = GetNextCode(); + break; + + case 90: + program.Mode = Mode.Absolute; + code = GetNextCode(); + break; + + case 91: + program.Mode = Mode.Incremental; + code = GetNextCode(); + break; + + default: + code = GetNextCode(); + break; + } + break; + + case 'F': + program.Codes.Add(new Feedrate() { Value = double.Parse(code.Value) }); + code = GetNextCode(); + break; + + default: + code = GetNextCode(); + break; + } + } + } + + private void ReadLine(bool isRapid) + { + var line = new LinearMove(); + double x = 0; + double y = 0; + var layer = LayerType.Cut; + + while (section == CodeSection.Line) + { + var code = GetNextCode(); + + if (code == null) + { + section = CodeSection.Unknown; + break; + } + switch (code.Id) + { + case 'X': + x = double.Parse(code.Value); + break; + + case 'Y': + y = double.Parse(code.Value); + break; + + case ':': + { + var value = code.Value.Trim().ToUpper(); + + switch (value) + { + case "DISPLAY": + layer = LayerType.Display; + break; + + case "LEADIN": + layer = LayerType.Leadin; + break; + + case "LEADOUT": + layer = LayerType.Leadout; + break; + + case "SCRIBE": + layer = LayerType.Scribe; + break; + } + break; + } + + default: + section = CodeSection.Unknown; + break; + } + } + if (isRapid) + program.Codes.Add(new RapidMove(x, y)); + else + program.Codes.Add(new LinearMove(x, y) { Layer = layer }); + } + + private void ReadArc(RotationType rotation) + { + double x = 0; + double y = 0; + double i = 0; + double j = 0; + var layer = LayerType.Cut; + + while (section == CodeSection.Arc) + { + var code = GetNextCode(); + + if (code == null) + { + section = CodeSection.Unknown; + break; + } + + switch (code.Id) + { + case 'X': + x = double.Parse(code.Value); + break; + + case 'Y': + y = double.Parse(code.Value); + break; + + case 'I': + i = double.Parse(code.Value); + break; + + case 'J': + j = double.Parse(code.Value); + break; + + case ':': + { + var value = code.Value.Trim().ToUpper(); + + switch (value) + { + case "DISPLAY": + layer = LayerType.Display; + break; + + case "LEADIN": + layer = LayerType.Leadin; + break; + + case "LEADOUT": + layer = LayerType.Leadout; + break; + + case "SCRIBE": + layer = LayerType.Scribe; + break; + } + break; + } + + default: + section = CodeSection.Unknown; + break; + } + } + program.Codes.Add(new CircularMove() + { + EndPoint = new Vector(x, y), + CenterPoint = new Vector(i, j), + Rotation = rotation, + Layer = layer + }); + } + + private void ReadSubProgram() + { + var p = 0; + var r = 0.0; + + while (section == CodeSection.SubProgram) + { + var code = GetNextCode(); + + if (code == null) + { + section = CodeSection.Unknown; + break; + } + + switch (code.Id) + { + case 'P': + p = int.Parse(code.Value); + break; + + case 'R': + r = double.Parse(code.Value); + break; + + default: + section = CodeSection.Unknown; + break; + } + } + + program.Codes.Add(new SubProgramCall() { Id = p, Rotation = r }); + } + + private Code GetNextCode() + { + codeIndex++; + + if (codeIndex >= block.Count) + return null; + + return block[codeIndex]; + } + + private Code GetCurrentCode() + { + if (codeIndex >= block.Count) + return null; + + return block[codeIndex]; + } + + private Code GetFirstCode() + { + if (block.Count == 0) + return null; + + codeIndex = 0; + return block[codeIndex]; + } + + public void Close() + { + reader.Close(); + } + + private class Code + { + public Code(char id) + { + Id = id; + Value = string.Empty; + } + + public Code(char id, string value) + { + Id = id; + Value = value; + } + + public char Id { get; private set; } + + public string Value { get; set; } + + public override string ToString() + { + return Id + Value; + } + } + + private class CodeBlock : List + { + public void Add(char id, string value) + { + Add(new Code(id, value)); + } + + public override string ToString() + { + var builder = new StringBuilder(); + foreach (var code in this) + builder.Append(code.ToString() + " "); + return builder.ToString(); + } + } + + private enum CodeSection + { + Unknown, + Arc, + Line, + SubProgram + } + } +} diff --git a/Source/OpenNest/LayoutPart.cs b/Source/OpenNest/LayoutPart.cs new file mode 100644 index 0000000..e34b92d --- /dev/null +++ b/Source/OpenNest/LayoutPart.cs @@ -0,0 +1,202 @@ +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; +using OpenNest.Controls; + +namespace OpenNest +{ + public class LayoutPart : IPart + { + private static Font programIdFont; + private static Color selectedColor; + private static Pen selectedPen; + private static Brush selectedBrush; + + private Color color; + private Brush brush; + private Pen pen; + + public readonly Part BasePart; + + static LayoutPart() + { + programIdFont = new Font(SystemFonts.DefaultFont, FontStyle.Bold | FontStyle.Underline); + SelectedColor = Color.FromArgb(90, 150, 200, 255); + } + + private LayoutPart(Part part) + { + this.BasePart = part; + + if (part.BaseDrawing.Color.IsEmpty) + part.BaseDrawing.Color = Color.FromArgb(130, 204, 130); + + Color = part.BaseDrawing.Color; + } + + internal bool IsDirty { get; set; } + + public bool IsSelected { get; set; } + + public GraphicsPath Path { get; private set; } + + public Color Color + { + get { return color; } + set + { + color = value; + + if (brush != null) + brush.Dispose(); + + brush = new SolidBrush(value); + + if (pen != null) + pen.Dispose(); + + pen = new Pen(ControlPaint.Dark(value)); + } + } + + public void Draw(Graphics g) + { + if (IsSelected) + { + g.FillPath(selectedBrush, Path); + g.DrawPath(selectedPen, Path); + } + else + { + g.FillPath(brush, Path); + g.DrawPath(pen, Path); + } + } + + public void Draw(Graphics g, string id) + { + if (IsSelected) + { + g.FillPath(selectedBrush, Path); + g.DrawPath(selectedPen, Path); + } + else + { + g.FillPath(brush, Path); + g.DrawPath(pen, Path); + } + + var pt = Path.PointCount > 0 ? Path.PathPoints[0] : PointF.Empty; + + g.DrawString(id, programIdFont, Brushes.Black, pt.X, pt.Y); + } + + public void Update(DrawControl plateView) + { + Path = GraphicsHelper.GetGraphicsPath(BasePart.Program, BasePart.Location); + Path.Transform(plateView.Matrix); + IsDirty = false; + } + + public static LayoutPart Create(Part part, PlateView plateView) + { + var layoutPart = new LayoutPart(part); + layoutPart.Update(plateView); + + return layoutPart; + } + + public static Color SelectedColor + { + get { return selectedColor; } + set + { + selectedColor = value; + + if (selectedBrush != null) + selectedBrush.Dispose(); + + selectedBrush = new SolidBrush(value); + + if (selectedPen != null) + selectedPen.Dispose(); + + selectedPen = new Pen(ControlPaint.Dark(value)); + } + } + + public Vector Location + { + get { return BasePart.Location; } + set + { + BasePart.Location = value; + IsDirty = true; + } + } + + public double Rotation + { + get { return BasePart.Rotation; } + } + + public void Rotate(double angle) + { + BasePart.Rotate(angle); + IsDirty = true; + } + + public void Rotate(double angle, Vector origin) + { + BasePart.Rotate(angle, origin); + IsDirty = true; + } + + public void Offset(double x, double y) + { + BasePart.Offset(x, y); + IsDirty = true; + } + + public void Offset(Vector voffset) + { + BasePart.Offset(voffset); + IsDirty = true; + } + + public Box BoundingBox + { + get { return BasePart.BoundingBox; } + } + + public double Left + { + get { return BasePart.Left; } + } + + public double Right + { + get { return BasePart.Right; } + } + + public double Top + { + get { return BasePart.Top; } + } + + public double Bottom + { + get { return BasePart.Bottom; } + } + + public void UpdateBounds() + { + BasePart.UpdateBounds(); + } + + public void Update() + { + Color = BasePart.BaseDrawing.Color; + } + } +} diff --git a/Source/OpenNest/MainApp.cs b/Source/OpenNest/MainApp.cs new file mode 100644 index 0000000..dacba67 --- /dev/null +++ b/Source/OpenNest/MainApp.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using OpenNest.Forms; + +namespace OpenNest +{ + internal static class MainApp + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + if (Environment.OSVersion.Version.Major >= 6) + SetProcessDPIAware(); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + + [DllImport("user32.dll")] + private static extern bool SetProcessDPIAware(); + } +} diff --git a/Source/OpenNest/MdiExtensions.cs b/Source/OpenNest/MdiExtensions.cs new file mode 100644 index 0000000..740fd0a --- /dev/null +++ b/Source/OpenNest/MdiExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Windows.Forms; + +namespace OpenNest +{ + public static class MdiExtensions + { + /// + /// Sets the display state of the 3D bevel on MDI client area. + /// Source: http://stackoverflow.com/questions/7752696/how-to-remove-3d-border-sunken-from-mdiclient-component-in-mdi-parent-form + /// + /// + /// + /// + public static bool SetBevel(this Form form, bool show) + { + foreach (Control c in form.Controls) + { + if (c is MdiClient == false) + continue; + + var client = c as MdiClient; + + int windowLong = Win32.GetWindowLong(c.Handle, Win32.GWL_EXSTYLE); + + if (show) + windowLong |= Win32.WS_EX_CLIENTEDGE; + else + windowLong &= ~Win32.WS_EX_CLIENTEDGE; + + Win32.SetWindowLong(c.Handle, Win32.GWL_EXSTYLE, windowLong); + + // Update the non-client area. + Win32.SetWindowPos(client.Handle, IntPtr.Zero, 0, 0, 0, 0, + Win32.SWP_NOACTIVATE | Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_NOZORDER | + Win32.SWP_NOOWNERZORDER | Win32.SWP_FRAMECHANGED); + + return true; + } + return false; + } + } +} diff --git a/Source/OpenNest/OpenNest.csproj b/Source/OpenNest/OpenNest.csproj new file mode 100644 index 0000000..a1dbe59 --- /dev/null +++ b/Source/OpenNest/OpenNest.csproj @@ -0,0 +1,324 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {1F1E40E0-5C53-474F-A258-69C9C3FAC15A} + WinExe + Properties + OpenNest + OpenNest + v4.0 + + + 512 + + + true + bin\Debug\ + TRACE;DEBUG + false + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + false + + + + False + ..\..\External\Ionic.Zip.dll + + + False + ..\..\External\netDxf.dll + + + + + + + + + + + + + + + + + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + + Form + + + AutoNestForm.cs + + + Form + + + CadConverterForm.cs + + + Form + + + EditDrawingForm.cs + + + Form + + + EditNestForm.cs + + + Form + + + FillPlateForm.cs + + + Form + + + CutParametersForm.cs + + + Form + + + MainForm.cs + + + Form + + + EditNestInfoForm.cs + + + Form + + + OptionsForm.cs + + + Form + + + EditPlateForm.cs + + + Form + + + SequenceForm.cs + + + Form + + + SetValueForm.cs + + + Form + + + TimingForm.cs + + + + + + + + + + + + + + + + + AutoNestForm.cs + + + CadConverterForm.cs + + + EditDrawingForm.cs + + + EditNestForm.cs + + + FillPlateForm.cs + + + CutParametersForm.cs + + + MainForm.cs + + + EditNestInfoForm.cs + + + OptionsForm.cs + + + EditPlateForm.cs + + + SequenceForm.cs + + + SetValueForm.cs + + + TimingForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {5a5fde8d-f8db-440e-866c-c4807e1686cf} + OpenNest.Core + + + {0083b9cc-54ad-4085-a30d-56bc6834b71a} + OpenNest.Engine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if $(ConfigurationName) == Release ( + xcopy "$(TargetDir)*.*" "$(SolutionDir)\..\Installer\" /Y +) + + + \ No newline at end of file diff --git a/Source/OpenNest/Properties/AssemblyInfo.cs b/Source/OpenNest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1025566 --- /dev/null +++ b/Source/OpenNest/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenNest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OpenNest")] +[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f99a0ac7-0b95-4c24-b9bc-f18b64d93242")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2")] diff --git a/Source/OpenNest/Properties/Resources.Designer.cs b/Source/OpenNest/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0a2befe --- /dev/null +++ b/Source/OpenNest/Properties/Resources.Designer.cs @@ -0,0 +1,196 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1022 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenNest.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenNest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static System.Drawing.Bitmap add { + get { + object obj = ResourceManager.GetObject("add", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap clear { + get { + object obj = ResourceManager.GetObject("clear", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap clock { + get { + object obj = ResourceManager.GetObject("clock", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap doc_new { + get { + object obj = ResourceManager.GetObject("doc_new", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap doc_open { + get { + object obj = ResourceManager.GetObject("doc_open", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap import { + get { + object obj = ResourceManager.GetObject("import", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap move_first { + get { + object obj = ResourceManager.GetObject("move_first", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap move_last { + get { + object obj = ResourceManager.GetObject("move_last", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap move_next { + get { + object obj = ResourceManager.GetObject("move_next", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap move_previous { + get { + object obj = ResourceManager.GetObject("move_previous", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap remove { + get { + object obj = ResourceManager.GetObject("remove", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap rotate_ccw { + get { + object obj = ResourceManager.GetObject("rotate_ccw", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap rotate_cw { + get { + object obj = ResourceManager.GetObject("rotate_cw", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap save { + get { + object obj = ResourceManager.GetObject("save", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap save_as { + get { + object obj = ResourceManager.GetObject("save_as", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap watermark { + get { + object obj = ResourceManager.GetObject("watermark", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap zoom_all { + get { + object obj = ResourceManager.GetObject("zoom_all", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap zoom_in { + get { + object obj = ResourceManager.GetObject("zoom_in", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + internal static System.Drawing.Bitmap zoom_out { + get { + object obj = ResourceManager.GetObject("zoom_out", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/Source/OpenNest/Properties/Resources.resx b/Source/OpenNest/Properties/Resources.resx new file mode 100644 index 0000000..cbba870 --- /dev/null +++ b/Source/OpenNest/Properties/Resources.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + ..\Resources\add.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\clear.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\clock.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\doc_new.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\doc_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\import.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\move_first.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\move_last.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\move_next.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\move_previous.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\remove.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\rotate_ccw.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\rotate_cw.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\save.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\save_as.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\watermark.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\zoom_all.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\zoom_in.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\zoom_out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Source/OpenNest/Properties/Settings.Designer.cs b/Source/OpenNest/Properties/Settings.Designer.cs new file mode 100644 index 0000000..4fc1085 --- /dev/null +++ b/Source/OpenNest/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34209 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenNest.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] + public global::System.Drawing.Point MainWindowLocation { + get { + return ((global::System.Drawing.Point)(this["MainWindowLocation"])); + } + set { + this["MainWindowLocation"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] + public global::System.Drawing.Size MainWindowSize { + get { + return ((global::System.Drawing.Size)(this["MainWindowSize"])); + } + set { + this["MainWindowSize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Normal")] + public global::System.Windows.Forms.FormWindowState MainWindowState { + get { + return ((global::System.Windows.Forms.FormWindowState)(this["MainWindowState"])); + } + set { + this["MainWindowState"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool PlateViewDrawRapid { + get { + return ((bool)(this["PlateViewDrawRapid"])); + } + set { + this["PlateViewDrawRapid"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool PlateViewDrawBounds { + get { + return ((bool)(this["PlateViewDrawBounds"])); + } + set { + this["PlateViewDrawBounds"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool CreateNewNestOnOpen { + get { + return ((bool)(this["CreateNewNestOnOpen"])); + } + set { + this["CreateNewNestOnOpen"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.5")] + public double AutoSizePlateFactor { + get { + return ((double)(this["AutoSizePlateFactor"])); + } + set { + this["AutoSizePlateFactor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public int NestNumber { + get { + return ((int)(this["NestNumber"])); + } + set { + this["NestNumber"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("200")] + public int ImportSplinePrecision { + get { + return ((int)(this["ImportSplinePrecision"])); + } + set { + this["ImportSplinePrecision"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Inches")] + public global::OpenNest.Units DefaultUnit { + get { + return ((global::OpenNest.Units)(this["DefaultUnit"])); + } + set { + this["DefaultUnit"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("275")] + public int SplitterDistance { + get { + return ((int)(this["SplitterDistance"])); + } + set { + this["SplitterDistance"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("04/08/2015 19:30:00")] + public global::System.DateTime LastNestCreatedDate { + get { + return ((global::System.DateTime)(this["LastNestCreatedDate"])); + } + set { + this["LastNestCreatedDate"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string NestTemplatePath { + get { + return ((string)(this["NestTemplatePath"])); + } + set { + this["NestTemplatePath"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public decimal LastFeedrate { + get { + return ((decimal)(this["LastFeedrate"])); + } + set { + this["LastFeedrate"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public decimal LastRapidFeedrate { + get { + return ((decimal)(this["LastRapidFeedrate"])); + } + set { + this["LastRapidFeedrate"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public decimal LastPierceTime { + get { + return ((decimal)(this["LastPierceTime"])); + } + set { + this["LastPierceTime"] = value; + } + } + } +} diff --git a/Source/OpenNest/Properties/Settings.settings b/Source/OpenNest/Properties/Settings.settings new file mode 100644 index 0000000..a699740 --- /dev/null +++ b/Source/OpenNest/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + 0, 0 + + + 0, 0 + + + Normal + + + False + + + True + + + True + + + 0.5 + + + 1 + + + 200 + + + Inches + + + 275 + + + 04/08/2015 19:30:00 + + + + + + 1 + + + 1 + + + 0 + + + \ No newline at end of file diff --git a/Source/OpenNest/PushDirection.cs b/Source/OpenNest/PushDirection.cs new file mode 100644 index 0000000..eb01c5c --- /dev/null +++ b/Source/OpenNest/PushDirection.cs @@ -0,0 +1,11 @@ + +namespace OpenNest +{ + public enum PushDirection + { + Up, + Down, + Left, + Right + } +} diff --git a/Source/OpenNest/Resources/add.png b/Source/OpenNest/Resources/add.png new file mode 100644 index 0000000000000000000000000000000000000000..9cb4036d9d8e23cae68afd57e08aeeae4bff9b84 GIT binary patch literal 3294 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 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/clear.png b/Source/OpenNest/Resources/clear.png new file mode 100644 index 0000000000000000000000000000000000000000..406bad480c6745b131349bcadf5466ed670de6aa GIT binary patch literal 1289 zcmV+k1@`)hP)e9*|oO+%ai zoY|&pJ{g~ZK;eG@=*8l`=Eiz|D4;0Kjge1PRrRD&i4(maM$LP*uKP9r@!+~J7ycLQ z{0yjL(7JHIJ2X6cvo;()2BffRfM(j;o5G%mo|y)Bn7EZEmStIQ?L9oH|CfGQfOGwO zs3g)MnOOGdVC+Ryr%17sgUMywn05NQHw1oItae!dDiu6!>yzg_zBa`dtdE$}F=Ea^ zjLy2E?Hz0Sp4lJes{#yM4DMl0xV5v#t#)jz1Apidkx5KI8ydet4B4qvD=lRny|6oQ z?T^k2vb5DVJm@1=2eSa5BY~iSU~QmH*Ath%I+Yz;tiEEBCw|}6<&v|;4fo)rQNoEvbMj;1Dt-M!P8naLv?*P`gJ+~NgE zPK@=+hCX?uuKB4=lqxU{2b>vTb^*4jfpY_dYiMrfkmdBYrT*|P!0hl2QpgVX)wk?? zg-``0!xr30gY%Lw4JN4M2xjSEmM#DWLRrI7fO3l_2DcF-KmJBd%X9C6$k21MMA#{~ zy+m;CCCp)<)P`*ogyWb*CN5Dj#genEEXZd5dNtJe>|sj1N@D6P;FyqH3_<`F^Fy#H zFuMrH7NC*}5Cz48om=(`hR4;@9`E^ID2khF8+KMZ_N zDrJRjxp3-iadKmq;+6<;CTpU$lH4E;AHDSPl|R{K0M6~r?l_m{(}Gda z)9ID+8k3W0+f4{u+!0lNm4w!{N;Q5Tk%9r&21%m9pMEYL&lZpa1QQ&N!^_5Z{X@lB zw0)~e7z0s#l&C%z+-`{&B?nr8iJayLi$M%sEr?1Ue*<`@1Dvh&P3`~y03~!qSaf7z zbY(hYa%Ew3WdJfTF)%GKGA%JMR53C-GB`RhFfA}PIxsK*8-pqU001R)MObuXVRU6W zZEs|0W_bWIFfchSFf}bQHB>S&Ix;XiF*YqQH99abgt`rG00000NkvXXu0mjfqtZ?5 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/clock.png b/Source/OpenNest/Resources/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..4d0524288fe01744da5502ed5d52c43d22a2f915 GIT binary patch literal 1419 zcmV;61$6p}P))M75>f}&zqSy1~c-H+SCPVlf=ej+#-SyX{bQ5Dn+UW5E4=nRu!d61%E&nNZoYV zMXOYZMd_mIA^`!FP-<|?0;f_0O|XM;Gc`8NFlLPL{PfuK=G}X~F2)Qgq-ndzk&fQE z@9BQu`OdrN9^h*>`Kph{jvbTX;o-q#GCA7b-kx+_x7`?Hip65){{8#6rlzK@XEK@m z*w`5VZ{~C7&K*pr(>JwYR8?B5wARvEOJfYn<#PG*<;%Z0apFYp{|tQk^y$dN#KgP$ z1zTCG^Yf(@{VZG3^g@XX`KtQ$fI$#wyMu)nP z@LIy}yz|p{mzI_?0LEWXaQ5ukgQ-;N*MP{qY+3F;EWs+ps7l=^EFpv4aak<+$QFIE zN*JY7YF1_+SJBt$i5GFhTefU@HJ{J_VS0MH1^^BK96EGJ4jee}a{#hbsqt>6L}L|3 z$%wg^IwAg~If3S$YQgs~Rp`FBcmw1$x>tl`0vRgjABz5e>!M~)ovngl~bL!$tt z>IYaTtVwY-kg9KClwvF;P=&E7#u#akP>d>!Qmj(4MITlv(+ex2qznuU>lsY&3vZaWF7`Uc``T*Sj;vFW@l$_k(AC>7a>Q4mLLGDlxGAG1yNWt_~YGW`BqyPyT2KgmAZx7;$XFI zWose=sv`I3(UaQR+I+J_larIzS68bQ5n!+lZ`dUdW zR($N+wgFyP;QPLvxOwyZrAwEdb@If-1g~Db`ip!%|2XW3{BXEaA}tP-knXmKtOH3j z91;Mqq=Z`p9dTDIDZ@P;cK61Gq>PRI>4Q?KbnS(^=+2!xWzX~O@7c5Ga6HoDZ10Lm zuIw{Ys6h!)l0VtM6;(fwN2NLgtKu8w{=SX)e&0qR$(eiiX5WARy&qq=aN*&XUe~c$ zES<~c9*&HR{J@KZ!$aM1bZ>A$QhclUG+Rcl;!8)o1v`5@d1GJ`wk0AUDKj%Ov%fq4 z+aE12FaK?BZmx;>!pIyQ9d%wA7&v(R`0-!$_4N&s0N0P{QwIs65&M4KT)%$pgFlat z|Ey3bOplL`|J(Tdk2Cq`(W8l>q2ycp_U(K7s#*~U9A2wsi^;8aEJT?4ng|>VH1g)0@q6)>utVm zd)~RqZ1d!MD(up2>(;GYb^YtERV@D#*Z$k_t}@P7Q&fF%@@KW*{+rrw-n^OZC@@Dz zP-AIT)ufqs8*-R$I80jO`Z&2*o=^VQg@AdQ27$Q~3tU4gS4LD_V`Z@C4$u&h^Y!x! z^9XVYDtM-sbUt?7dVP=8r7_p*uiy9cne#LC%boH~UuSkaH~$w?yKn!OEtlB^SR6%; z8}=Fs2riSblw^3hQdCBx{^5%go5Wp@>#;nvJ@R%oZ?c#j(=vx^vl;GvVZ6bR#N5^4 z;F59G%tB+)(a&{899=@L<`4MiEB2;hWh7UzMH)5+UNJPd%NF@$Y}FUtMghp z=bXbzUWWLGKSMiu@_(%i47~TV_R02UhGQI@zZg8ez1UFgALe!OUBDyZM~7!fS$|}X zil3CCUe}@W^BR-Du%xKk zw)gp>j-AKvO>5MR-ObOa(Y)a8)T{fr^O-kzS{QG5tZbWZZFB!;Uc*|y((i{AVy#xk zoSu4cox@9oGfjL2oK zYhVD(4yq-t5hW>!C8<`)MX5lF!N|bSK-a)X*U%uu(8$Wj!V1JSvjQrXZq`E4kei>9 tnN|tZU|^|hV5)0m8e(K!y+@O1TaS?83{1OQaXss{i7 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/doc_open.png b/Source/OpenNest/Resources/doc_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e449664388f27d6b0789eb1b83b72d989897fb GIT binary patch literal 1078 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 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/move_last.png b/Source/OpenNest/Resources/move_last.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf850c3145a8c0e69ee3747e08027a970bd33b7 GIT binary patch literal 1143 zcmV--1c>{IP)#zuCB-Z=jR$9yA*6u*D;*yz(65#mJ* zW0@6lHkSbr?zj;(72DdkcWwLf;g`=HJ97Hdb$k`7fJov405$*|Ir&MUJXaYq#xw&8 z5Ggc00zo}5ct)XcgBXs}+`4DC{lSfiQV)Rl)`kaE6{-Sc0Td_#bCVX+YcU#-mH!qLAR!3qAQH5yYzd}*60L(1ZX|~d!*@ZgF zi!RMoT`E^y%rAM=@MEGhZx*FSq!<|7bKG?spFj5cN8L%0bp?b)Rso18-Ap7o&A;ba zT}0uAN_~C3&vF!gbMW|Ruc$y(A)*l0Rf?F?MN-Ppx!ydhE1*n1&HykoMDMN!#ta&@Ahp4J{TIPFZAZ%dgFu&VA3@T|0Xo&shduZLu=>%h#uBlRsXxot7=RLL28= zy12NbUpVwvI*2&~005o+Pg2xawCAo~vN?3^bk71{sfuF=R9%>zo*4W1gggJ|IG`1*fdv2qU@|s0wLif5)*at2 zOkInNoo_gW78m&MKNDc002ov JPDHLkV1f}Q_4xn* literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/move_next.png b/Source/OpenNest/Resources/move_next.png new file mode 100644 index 0000000000000000000000000000000000000000..f997a0b2b0855cb4cee248373f8a95454b0e8a35 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*FB&(dWK49uLKE{-7{$KQtfW`u-`{5!YzeXsG%Gbuun z|HZBbT?zGG(Z#*=?XnB+b_Fky65q(1=asozOSk0Vp;iH|wqMLzH=J}hbaNMKi~qZz zm9$~0YNX0m4JqTZd(0mJ;{R(d7j_`!{~^RCyd{aidd zPf#STCh>2(R(OOm!`H77M#i=Omu)isoj1p>X1#X9-FE-lZP^94#m{yqGIemU2;F87 zG>HDHc!^E-+l?8u7S-otIllSF$owp|WYB)g!LVKMQp{RS7niqZR5xDU*Wq!uKO^Dp zF5PLS{~v3!`|*i(wa#BxU%F!P;dv_=kH7TP$+Ui}-&ZH#x$lbohJ|$CZ{GUoE!x7=tu{d}#_$FG5PYzHLT9-Dpe*L|^Vr_jZ& z1*$?pDZ+tH!OuBoB+fW^CSpmf%LFaQN6VjYRVsMF9(w)nmH>^ud;1EWR5CC<^P4yQ zmUnhScI$!og4y%TO-g?*ejmQBG4tRhm!opgE)JhcjI+|$JYWqBV)ho6^832J?Q=%P zv**d-(-*H}R#*|5=jU&=`0cjD21zD|OzD8IeIfTM1gvJWGDHi{InMw7+Viw;wmZzs ze(t%YQ$0Jc;%9ExtWr@{ZmkvJCNFs+Z|?i#oxi8t`+3!AuZU;==It`7etv0p!x>>g zW9e_&m-js4^qO71YTEl3r>=x*bJ=nql4@XEP`YjXfkR7tyXF722{P}iQ^I;fw!`uxDb7G=58a;Xw z^fvL5yx!#OSB5BOiSWUH{7bv_qO_K7<1%f%kkchCeyKOI%Q-PK9Akz__Q@{g?XM*dwN`(w+TY-KhUuZN$Oea=jcJDAhk^_pnlIJ21hG7BPl#N<;2OJq zKkt7jmzt!+ynhF`ZIKoaJaA^~`(KrN=g&+Qj>tAwV`yME_eQ zb&tsw)b!r5Cusk5;ofOEcRjTxhPa&C_;A(ZZEvf0KmTzudza?qi+zXQm*>x~|8n?* zCBvkgw+W3A+jsVcDy43n{_JRH;Z3i`$NJ|pIbK)z{%(EwzOLI#zEsxw=gn{5&iOq& z!F++~z^~R!?gMPClFSn>`mR;mnmk|gy#E!hhbAiu848o?ua@kvvE6O+`?;*wd#hLC z;+vk{jDL6ispr)jKZK@!cJktGnXeEbE+c(B?MznPK7+51uirZDaxkcj$$>+m>+G7| zq}RK1SJ&$^Dip=IF5x((c%^*Zh7G1neLLPc{I`{3RzG~)|K+0QwZL4XTH+c}l9E`G zYL#4+3Zxi}3=9o)4UBXR4MGf!tc)zIKwL8`0|N%>W-SyAx%nxXX_Y_?29~-8rn*L^ gAx4H)Mg~@fW!Pgg&ebxsLQ0EgPQz5oCK literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/remove.png b/Source/OpenNest/Resources/remove.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc5f7683e3c6b1a3591c184f6cca68560ca29ea GIT binary patch literal 2945 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 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/rotate_ccw.png b/Source/OpenNest/Resources/rotate_ccw.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc99825f915d384b8668fb33caf776d361db60c GIT binary patch literal 1316 zcmV+<1>5?GP)f>07t9sPTfNE+>h}5*L?Y;fK3sQQmT1|$8jcPa^-&o>{O62PeweK8>(D3 zZAo5B2Ti@_y!=DYf33e@3-{D>;I*V4W1lRR&M8Iag?SUkgHs=<0d|lz=N(5za?GX`3!W(n1WlH0IA%@gp#HDc zYMc@6)OVhutpyMeGHM7K5{EU0MN?MhNuljE0w-p@C%Dva4{T0XW^PtWNuyWbcG@vz zo%`HYN)J`GpKkI(WHjv9hq3b;8jqhhTkkf7@QMa5HoVX5!kol{;c;t{iqQK7c+xYM zj?PS=roIJ>03lTfXgAr`j!14b-=})d7o>&^zp~r> zR?Wb89FUmj@}#DePRLE+p<+j7(mP*K~PiUQ1T-V@-8PDm~o;hEd)3*xX!bSbcD z0;Rz3_4-1tlR6GoyJAj z_~TA1X^BqWoi}E7(b(*Z6U(a>15`XG`v9bjiySd5t)w73iI8ccX)^2z}}^>zAPomLD`HPlH88%pYFXd2okVd2T#w9}lP9O@f4Y0-l+PdgN2Kkozfo9RQNDN|u>Q=SE an)wf$OR1&dv8@aM0000pi!_blA=gaz_2DlpimP61~fzs zBn6eASi%=#P($#81j8a!kf34FrUD^a_7sUg5uCQr={8GeI`8^n#?n&C5^r*H@45Nk z^Ut|wgD0%wa#Hg@A|7D9k7r)WlK>qCSB4~T(pjRVZixM#1Pc!J1T_2bEGt}+Jt?cB zBB@x3-65ZiKm&O9yoD_}e*UZ??#-=9(}gCtO2UZ}KP~zv&+q%w;Yco5FbnUP{8;PC@dJ2zjaLJuxwYNg0A}T8D~HU8k-z62hI~8o5_{~ zr_XWcpLva16N6rkr0O<414 z-tgqJ_3QA8dg?qK9IG-~c6>*}w%vr|;=$vE{T1F5m76FrM3dcawb0-dWMw#KnXR_; zUW1%C5Ld=v+uS=HN04T?UJD2JvG3?M7M?0&qRm8oQ#<^0jKe23Q3^B(Md?t5pvK+I z$n+#q)6$prN)T$LkICjyls! zqU1)o_(7eE1;AZp-8llse1VUZ(=%oM&x~5=K1(*MBVR2E8J@b_n ziyl?U5-_?*w_4qNJE9hTbktt->4~)YTMjvlaM&;;=$cB5uF~uel9Fs!Mh|rrrW7o* zJasR6@3a(aU4khaD)NiR3_P_oKjUV|WIyNkwgsdFvnJ3LfyJcI(=SHH9%G8wm4Jxsx~8D13Yr#?4#OZf)4|Zeu30$8#&siD z_x6s&*Mp4km^Be?8r?BDnGVzN4A*Nd zQ&#QP-0r%8D;A6|pP!f335+$Vy-z4b2%#vVuT7(mMdwii&ZNN$x#=@6{?R{8la04b z2hRT0^v$W7pdlYW4^^RveinH>$=rT>LD>O!N7zt0gVMFmg@QFk4J)m;6#>|QzFkx6 w39BW#TLJYkxOSjPMCrvd%yIc}SRRM{7e!#VHbgkBDgXcg07*qoM6N<$f)M+AFaQ7m literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/save.png b/Source/OpenNest/Resources/save.png new file mode 100644 index 0000000000000000000000000000000000000000..988b9a8bb8bb4b6249535136bab9f2d81b07ef9e GIT binary patch literal 1204 zcmV;l1WWsgP)=VF#S@7~S^oo89~I8r!RnXw=iR+Z%X*Jpd${voq@5+CY*y=e19Q zCNdc7lXf#RlL6Z!*_;PC$27pMI0vI*K(|*OshDO^lm#Hn!1NdZ=bV9~p3Sm!Z0^Vu z)xvF@8}1r44IqRhIVYTR7DCW&WG3wwd8|*`%`melsk%$KAJ2gdgd`+~_x?x}m8C$H z4=X`BkO2Zo=DipWyR2^(N24g36-9w}t}^NnMeM>l8jX2-;`oxr+)A3DsM06xW@a%) z)sh=HS6Vdf_19FJ3fXRVVP;bWobH%PKVY{p%@I4C!R`sUkn;7#>o(g6!oV;CwCAew zT1~PcV~mBawz?!q-HNJV4pU?p^=5-sza(}>l*JAf4YTpb#J^#P=#o9V^9{U&RiPfH zsG3dTy{EDEGcTS#PVgS@6TJ6$=ZP^=#*(ru*?iPt`ECj`Pa}z9G$PHiD%=>S0W3zM z*&eNDPSk8}&hh=l-|3XIwpbhT*+=gZt)$=Y+i+*db7xN3AMF+5>;A2z zcjwnjzg%8^xY$gT1V`{*i^rDq=DB0Mlw8+q&(su>pd=(@SzT!@BhPaQ&Vr^Gu2 z@4ygP+JCf`c!1`@Nx!{*vwvgd>S~yK_ASL&FW0XsmWIU=M^r2d*4Emz+w0u_`@UMO z7OShP`t$BK4|{>ljV*0H++t&6i<>Lgt~^+|^i6$cVN(FdPrss0yET^&x(h&^$Gj6q zkOCMaX>)7o^vj*8=X`eYR4o?6WYFsdsaoNO_uPTq0GRE&Nt^RUs5M62^;Z5Lg8cXL z4|CZJcDCPe^Z)<=C3HntbYx+4WjbSWWnpw>05UK!FfA}LEio`uF)}(bI65&fEig7Z zFfafcgDL<303~!qSaf7zbY(hiZ)9m^c>ppnFgYzSH7znVR5CF-GB7$ZI4v+WIxsNQ Sg?2Ll0000{s+XV69>kB z!Nf2!#%PQrG=d2cFb;%JB$O6t3q^VKwxzwN_jO*&!EI@8TO$s1P0rps`>fyIzy0mK z;6DyoZM=2-L{}iSk9u;|-0R*2+T57HM))RMH*0gKLM1VTiNnRpUM@#NEJt!N^E*y` z^VxgqtWKPmdGx`D@4WVDsd!HgmQJXXghB>nD9sY9{q^IDgG>j5X!}!Idsw`iM(H8o zk+W_k`Ea?)okh{&o8Qyb9_WUoY|+=X|5fjeTx0p>5UD&0$tnMp2(sss!{&^Ebg5VjuvJaNGIgfLgExe+^-XPs)L=54V9K|~NSSZn{$n$qY=;)(C@Y#AdN zX%sVY-(|ACBOY^i)i)e6h=^J%)M_DfkCMN&NYhyB*M7uIo+9vSSl5u&Q*O_?j8tFN zX>VWTSk(;{0U`p1AZXXV?k-v7Lu8GjEKRj^>04pu47ru$S*p~uhq3$%Oh(V4mC|Fr znlKeaSLi}jn;2hhhoe7Gx%xGJu7WigX+5Dv4=o4Z)rE<6%u>B^H&+y#4QPmQJihwr zx)uUQP*79>(KMl__zO?h&(Xgv!&*ZcrOeEEj0Hz@;bwSl`?cT=#| z63m`sc(5DCS~};iFx+;9-cG@~LK?;VIWJ7fFt?H(NK-_FB#!ZXzp>mbYmoWao-8BN zMgZ*W$!U8*b@9e|dY3QJ-Jw_q(t1R3$zi_tCEa=K4QRwqciUk(L*kTMl^8eT;PbVLaCr5m|Ww5XW_h(gxB= zt=ue7UMxDgj&|1?`f?Tew-4hM2B_XT%k2DR=6a53)OSEyJRj>gh=3SD1T2O`6;(B( zr@zbta}GfNocqc@1gN@Rez9xtnNm|ZFLVy%vZUr#%b_FEc)~ZAf-Q=IuBGwv?TM2M g;&5&z;{S-h0KN7$)e?-=y8r+H07*qoM6N<$f?V}ntpET3 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/watermark.png b/Source/OpenNest/Resources/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..9f0f2b75c11f9b3607d761c594a733b3a95b68ea GIT binary patch literal 8960 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 literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/zoom_all.png b/Source/OpenNest/Resources/zoom_all.png new file mode 100644 index 0000000000000000000000000000000000000000..56c7e31b37e880455044cd33bdb1a32eb2472887 GIT binary patch literal 1728 zcmV;x20!_UP)CLX}D-NVI7|NS1KoT6Tz&=Astt8arkk+x2Ddd)B*W=ggUz zzBm$1HYohk|1cWOd>Z{TzXriQ#E0ibEG5AP1hu1&J?7ranp=VHHu1s8Xu=@kS=%z6 z2v}B<5K4WY`_-Czqw2C@m*X=}4<26l%D@LBV;TYPrLOkauPn=I$>(#B&1MkuJZ0zx zifrAgT3TA5;yA^n)$9p@tUr8b@DRTz;Qf!r0!^km*^}rxJ~KNjuly&wo@31`47Zeo z(tJ>i7_!v7Jy2=i^Y!Sqct;1GPcOdf`q~Rm4<4#E0_Yb9CgHCq;@!_oT)N_2TCk_V z+~H80fQ>Oh2!L^nn5R4QwXn1L&*J()XYOnF_xB#3xiOyufS-Qp3Em$b{$_t~Vq|LO zhB$kbFFv}bKZp?^gb<8$#pKT97z z{_Us6ZU-;`!0_m(7PPgYn(N|g3or+Ivp29aqF3EIaW-n$GBn&2FsNl{*x7J#oiVbz zFRWEX(7iavX9*>^$)-bRhtF#b0pI66(MV+f%>0ro%(lFtYgkBymJ7b4^ev11-P;}8 z)JShvtXz@7`T4A0pp+s_Bd8?8na>y3-B>hwz^&K28v@ej1G=sonUZi!J0K~=X#C$R zfG}1~K~n~71DU3V1GbJrK}!N4ICpIsD5VJNmcN|$%1xn=A$@P4VUZO;7XX0kYc*mM zNOiSkjDTL;(MkxV2qA=!Q~($&sgS^(?Xk>ynJpE#ERSBuID@Im*1<>g7$BTz3`hkE z6kv+sA5%`osY}0es~K!-we|28lUfEQLV_ySe7=w90VdnA%u`p zN;L$0pDzkuD5)g(lfzTFS~_`lgAd-FJ8*NUVaoEYPJpY zFK7SyuBVLe|N56Ni7y3CzxTHQ-zdDfd*GpGxm0R0%@;3cTR&UlEd{_-fb>b5UTWPJ zEAKqi7m6#v(Vx$}Q}q}Fs?oK4F8^9268Yn+ul!au1ONcucr4HncSo-+ zrG%=>geQQT8qu}{ZGtg{>FKH3*!ahFGR=Y7bw|wR5)$KYlaoZ(b=|bg5CSlj0_nOn z*Yh|40NG4)dq?LpM~*x`q*0=!QYp2iIaI%Raq>mev`&)S0go}Xva*WP=`?X`E*3%n zt24h|XN)n%g^;&yAKnD?4aZ4mvV~$Y*|WD;ERd#9NJ}KTzBTpf^!OdX>gpQg^Lg-n zUje|a*Dp6yY$gW)Z!>)W5S4OyYHdB6OZ6uAbB_@~h+V&S{nQ-*0DzQI2_d9X>h?{) zxG@8OI>Ok+2)RlrUU8h6we@Tk5RQ#}bnZl{SUP{#U&U7m!!VF(Sr{Rp!WR@{OfeyT zXJ05UK!FfA}LEio`uF)}(bI65&f zEig7ZFfafcgDL<303~!qSaf7zbY(hiZ)9m^c>ppnFgYzSH7znVR5CF-GB7$ZI4v+W WIxsNQg?2Ll0000rd+{1_pFV|%aOW%uly zx%CCA6q}0t(*H0To%!_ZpYv-F+(Z2H)Uazb1OP$((Ek12y?k>!(A_rP8$Oe=iFzjB z+5EO)(7x?Gsp0YL zv^qQEUI{eCJw`D`7^CUMl{RC*D6LIoV**3_A|PWffh$HoxK}6N$&XMx%Sf9%w0cGRExHvRZKhL2WZir{-4^7C{&x z1P`Udh!6}2Av1e@wOt6Mh0?ks8NaTZ(pBycT7%L76VExz?48bDx=@)Z|y40KDlltk^j!2zrHiOd+%4H z58T)N=!1#s)`c=EmQ|=iEC$dN&vPf5m#jH&v<5H=-QTj-`O5)vpe4YZzn?t$RzsNI zoOyp}U6cQ3*d71N$)Kv`f4ghv!_Nq9%;>C`znE*CSQe2YV5&e%vWb;j_axW0eZ4o_ zWt2dFJn^TR&pA+wE*J8JS7Wi*?_PfC6|*4#0Py-7zjyq4@x>jznP2qW*AviML(SvL zS0EgVSk0jT!I;9;tK;<#&yBqN@3E_)haT)5?CR`7#wc%e^y2rAy>a}_y8-|JKOKAt z`NH2^E3Z7?yKUQnbUNL|Y&*a?uTM`+End2O`Skg7BPSLY7FSziu}8o8vy8-mS&Ix;XiF*q$SH99ab T)P;6400000NkvXXu0mjfWY;2= literal 0 HcmV?d00001 diff --git a/Source/OpenNest/Resources/zoom_out.png b/Source/OpenNest/Resources/zoom_out.png new file mode 100644 index 0000000000000000000000000000000000000000..d73eaff96c3cd6991e03bf79b309eca67442a59a GIT binary patch literal 1669 zcmV;027394P)fTV!Drk>`4> zxuu0tsT8V}N@?~^{)j^C_2>KcsD}ca9Q+`W@a(Z{X7i!3@o{_cT5cg9ZkP*ss%*&7 zpip?WykTRax@p&^TiZI@+xg_pnLpIE`_l7$dunR|!stGIG~dhelUzw-XQ01f~H zXU;guxYJ(`0)BZK6N&Y0E9(-jOeQ?}pV5=agfEf_PsU@O@O_7Ck*W+&sBs|$Ul$ks ze;GLKtl1*1)t2_QwkJj>ZUx3~FEqqm5%Z<=9Kktbuml)ORvBVKS}?9jCDJaKuT3v@0T{mzV71!mx~{iaHVb|{VLeyMiJ8S#M=;J90|BrEGL}divPv1N zjJ56QmVa5q^lYKH)YH|Fwt9Bwng9Ye13;jix`+!{aN4~!)w1`ob_URYnavUr5u?9d zoUwnt7>xe&Myax|yV|nvi*6c>XVz>HSptE;CCA@KD~pv7(c#ZK+sey|a}LHBV|SBl zLq;3e+)`xE->4Oh0+W2Iw{FXY6w*kE!XSXg%lFRI2mH3 z$&$8)j8@DDMbeE}(ly@Wy-jWTLLnsXU0BnmjLYfUb8|zznRIejN08MJRzVmIX*72X zNzZcEBkB2YJQvb+Ash!nN_?r?WVdytlQ*Vr4Owfa*93g+K%XjBqoYxz@b%qtM`puH z6Sx555?nYWT@Q}u!}UBkjteOra4C>&(oNsl+qk1xEYf_T@{5-aA5?1s0N`8SIDGma zA6@%ZM{COa=~MOnpJ}SL3&FsJfN&%^zDL55#D#zm43D$fAZbGzO`Jd z5B=ZA?)V?yPMBK$*SmH;_M);xBU5VTLay=pyh;_prGeGr5x3mW-ng4bo$)c;S;mBXBQiro4@eYg9rZPd#;!5&Z4QQ z(Vjhf?%U1H&BtFm@=Ll8@WUT{pOs2wF*Y_X&X0@;*L7V#<~K0FCxQ)vdJshl0K}^N zjqM#T?BD-Pzaxdy-Q7*=H#Dpa50AX;`>|uw`D8N|w;pGqCybk~X)><;gSR%SN>Bkd?09F|1 z0cR{AB2}$a#^x7tIWV3ceDBnea;bd!fxn7R5}xNVKNjPRK^ko&=Uj3jbVtXg@ZW*D z=jL7<3~vAc03~!qSaf7zbY(hYa%Ew3WdJfTF)%GKGA%JMR53C-GB`RhFfA}PIxsK* z8-pqU001R)MObuXVRU6WZEs|0W_bWIFfchSFf}bQHB>S&Ix;XiF*q$SH99ab)P;64 P00000NkvXXu0mjfR!Iwo literal 0 HcmV?d00001 diff --git a/Source/OpenNest/SelectionType.cs b/Source/OpenNest/SelectionType.cs new file mode 100644 index 0000000..fc172f2 --- /dev/null +++ b/Source/OpenNest/SelectionType.cs @@ -0,0 +1,8 @@ +namespace OpenNest +{ + public enum SelectionType + { + Intersect, + Contains + } +} diff --git a/Source/OpenNest/ToolStripRenderer.cs b/Source/OpenNest/ToolStripRenderer.cs new file mode 100644 index 0000000..3158aee --- /dev/null +++ b/Source/OpenNest/ToolStripRenderer.cs @@ -0,0 +1,534 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace OpenNest +{ + public enum ToolbarTheme + { + Toolbar, + MediaToolbar, + CommunicationsToolbar, + BrowserTabBar, + HelpBar + } + + /// Renders a toolstrip using the UxTheme API via VisualStyleRenderer and a specific style. + /// Perhaps surprisingly, this does not need to be disposable. + public class ToolStripRenderer : ToolStripSystemRenderer + { + VisualStyleRenderer renderer; + + public ToolStripRenderer(ToolbarTheme theme) + { + Theme = theme; + } + + /// + /// It shouldn't be necessary to P/Invoke like this, however VisualStyleRenderer.GetMargins + /// misses out a parameter in its own P/Invoke. + /// + static internal class NativeMethods + { + [StructLayout(LayoutKind.Sequential)] + public struct MARGINS + { + public int cxLeftWidth; + public int cxRightWidth; + public int cyTopHeight; + public int cyBottomHeight; + } + + [DllImport("uxtheme.dll")] + public extern static int GetThemeMargins(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, int iPropId, IntPtr rect, out MARGINS pMargins); + } + + // See http://msdn2.microsoft.com/en-us/library/bb773210.aspx - "Parts and States" + // Only menu-related parts/states are needed here, VisualStyleRenderer handles most of the rest. + enum MenuParts : int + { + ItemTMSchema = 1, + DropDownTMSchema = 2, + BarItemTMSchema = 3, + BarDropDownTMSchema = 4, + ChevronTMSchema = 5, + SeparatorTMSchema = 6, + BarBackground = 7, + BarItem = 8, + PopupBackground = 9, + PopupBorders = 10, + PopupCheck = 11, + PopupCheckBackground = 12, + PopupGutter = 13, + PopupItem = 14, + PopupSeparator = 15, + PopupSubmenu = 16, + SystemClose = 17, + SystemMaximize = 18, + SystemMinimize = 19, + SystemRestore = 20 + } + + enum MenuBarStates : int + { + Active = 1, + Inactive = 2 + } + + enum MenuBarItemStates : int + { + Normal = 1, + Hover = 2, + Pushed = 3, + Disabled = 4, + DisabledHover = 5, + DisabledPushed = 6 + } + + enum MenuPopupItemStates : int + { + Normal = 1, + Hover = 2, + Disabled = 3, + DisabledHover = 4 + } + + enum MenuPopupCheckStates : int + { + CheckmarkNormal = 1, + CheckmarkDisabled = 2, + BulletNormal = 3, + BulletDisabled = 4 + } + + enum MenuPopupCheckBackgroundStates : int + { + Disabled = 1, + Normal = 2, + Bitmap = 3 + } + + enum MenuPopupSubMenuStates : int + { + Normal = 1, + Disabled = 2 + } + + enum MarginTypes : int + { + Sizing = 3601, + Content = 3602, + Caption = 3603 + } + + static readonly int RebarBackground = 6; + + Padding GetThemeMargins(IDeviceContext dc, MarginTypes marginType) + { + NativeMethods.MARGINS margins; + try + { + IntPtr hDC = dc.GetHdc(); + if (0 == NativeMethods.GetThemeMargins(renderer.Handle, hDC, renderer.Part, renderer.State, (int)marginType, IntPtr.Zero, out margins)) + return new Padding(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight); + return new Padding(0); + } + finally + { + dc.ReleaseHdc(); + } + } + + private static int GetItemState(ToolStripItem item) + { + bool hot = item.Selected; + + if (item.IsOnDropDown) + { + if (item.Enabled) + return hot ? (int)MenuPopupItemStates.Hover : (int)MenuPopupItemStates.Normal; + return hot ? (int)MenuPopupItemStates.DisabledHover : (int)MenuPopupItemStates.Disabled; + } + else { + if (item.Pressed) + return item.Enabled ? (int)MenuBarItemStates.Pushed : (int)MenuBarItemStates.DisabledPushed; + if (item.Enabled) + return hot ? (int)MenuBarItemStates.Hover : (int)MenuBarItemStates.Normal; + return hot ? (int)MenuBarItemStates.DisabledHover : (int)MenuBarItemStates.Disabled; + } + } + + public ToolbarTheme Theme + { + get; + set; + } + + private string RebarClass + { + get + { + return SubclassPrefix + "Rebar"; + } + } + + private string ToolbarClass + { + get + { + return SubclassPrefix + "ToolBar"; + } + } + + private string MenuClass + { + get + { + return SubclassPrefix + "Menu"; + } + } + + private string SubclassPrefix + { + get + { + switch (Theme) + { + case ToolbarTheme.MediaToolbar: return "Media::"; + case ToolbarTheme.CommunicationsToolbar: return "Communications::"; + case ToolbarTheme.BrowserTabBar: return "BrowserTabBar::"; + case ToolbarTheme.HelpBar: return "Help::"; + default: return string.Empty; + } + } + } + + private VisualStyleElement Subclass(VisualStyleElement element) + { + return VisualStyleElement.CreateElement(SubclassPrefix + element.ClassName, + element.Part, element.State); + } + + private bool EnsureRenderer() + { + if (!IsSupported) + return false; + + if (renderer == null) + renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal); + + return true; + } + + // Gives parented ToolStrips a transparent background. + protected override void Initialize(ToolStrip toolStrip) + { + if (toolStrip.Parent is ToolStripPanel) + toolStrip.BackColor = Color.Transparent; + + base.Initialize(toolStrip); + } + + // Using just ToolStripManager.Renderer without setting the Renderer individually per ToolStrip means + // that the ToolStrip is not passed to the Initialize method. ToolStripPanels, however, are. So we can + // simply initialize it here too, and this should guarantee that the ToolStrip is initialized at least + // once. Hopefully it isn't any more complicated than this. + protected override void InitializePanel(ToolStripPanel toolStripPanel) + { + foreach (Control control in toolStripPanel.Controls) + if (control is ToolStrip) + Initialize((ToolStrip)control); + + base.InitializePanel(toolStripPanel); + } + + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) + { + if (EnsureRenderer()) + { + renderer.SetParameters(MenuClass, (int)MenuParts.PopupBorders, 0); + if (e.ToolStrip.IsDropDown) + { + Region oldClip = e.Graphics.Clip; + + // Tool strip borders are rendered *after* the content, for some reason. + // So we have to exclude the inside of the popup otherwise we'll draw over it. + Rectangle insideRect = e.ToolStrip.ClientRectangle; + insideRect.Inflate(-1, -1); + e.Graphics.ExcludeClip(insideRect); + + renderer.DrawBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.AffectedBounds); + + // Restore the old clip in case the Graphics is used again (does that ever happen?) + e.Graphics.Clip = oldClip; + } + } + else { + base.OnRenderToolStripBorder(e); + } + } + + Rectangle GetBackgroundRectangle(ToolStripItem item) + { + if (!item.IsOnDropDown) + return new Rectangle(new Point(), item.Bounds.Size); + + // For a drop-down menu item, the background rectangles of the items should be touching vertically. + // This ensures that's the case. + Rectangle rect = item.Bounds; + + // The background rectangle should be inset two pixels horizontally (on both sides), but we have + // to take into account the border. + rect.X = item.ContentRectangle.X + 1; + rect.Width = item.ContentRectangle.Width - 1; + + // Make sure we're using all of the vertical space, so that the edges touch. + rect.Y = 0; + return rect; + } + + protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) + { + if (EnsureRenderer()) + { + int partID = e.Item.IsOnDropDown ? (int)MenuParts.PopupItem : (int)MenuParts.BarItem; + renderer.SetParameters(MenuClass, partID, GetItemState(e.Item)); + + Rectangle bgRect = GetBackgroundRectangle(e.Item); + renderer.DrawBackground(e.Graphics, bgRect, bgRect); + } + else { + base.OnRenderMenuItemBackground(e); + } + } + + protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e) + { + if (EnsureRenderer()) + { + // Draw the background using Rebar & RP_BACKGROUND (or, if that is not available, fall back to + // Rebar.Band.Normal) + if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement(RebarClass, RebarBackground, 0))) + { + renderer.SetParameters(RebarClass, RebarBackground, 0); + } + else { + renderer.SetParameters(RebarClass, 0, 0); + } + + if (renderer.IsBackgroundPartiallyTransparent()) + renderer.DrawParentBackground(e.Graphics, e.ToolStripPanel.ClientRectangle, e.ToolStripPanel); + + renderer.DrawBackground(e.Graphics, e.ToolStripPanel.ClientRectangle); + + e.Handled = true; + } + else { + base.OnRenderToolStripPanelBackground(e); + } + } + + // Render the background of an actual menu bar, dropdown menu or toolbar. + protected override void OnRenderToolStripBackground(System.Windows.Forms.ToolStripRenderEventArgs e) + { + if (EnsureRenderer()) + { + if (e.ToolStrip.IsDropDown) + { + renderer.SetParameters(MenuClass, (int)MenuParts.PopupBackground, 0); + } + else { + // It's a MenuStrip or a ToolStrip. If it's contained inside a larger panel, it should have a + // transparent background, showing the panel's background. + + if (e.ToolStrip.Parent is ToolStripPanel) + { + // The background should be transparent, because the ToolStripPanel's background will be visible. + // (Of course, we assume the ToolStripPanel is drawn using the same theme, but it's not my fault + // if someone does that.) + return; + } + else { + // A lone toolbar/menubar should act like it's inside a toolbox, I guess. + // Maybe I should use the MenuClass in the case of a MenuStrip, although that would break + // the other themes... + if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement(RebarClass, RebarBackground, 0))) + renderer.SetParameters(RebarClass, RebarBackground, 0); + else + renderer.SetParameters(RebarClass, 0, 0); + } + } + + if (renderer.IsBackgroundPartiallyTransparent()) + renderer.DrawParentBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.ToolStrip); + + renderer.DrawBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.AffectedBounds); + } + else { + base.OnRenderToolStripBackground(e); + } + } + + // The only purpose of this override is to change the arrow colour. + // It's OK to just draw over the default arrow since we also pass down arrow drawing to the system renderer. + protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) + { + if (EnsureRenderer()) + { + ToolStripSplitButton sb = (ToolStripSplitButton)e.Item; + base.OnRenderSplitButtonBackground(e); + + // It doesn't matter what colour of arrow we tell it to draw. OnRenderArrow will compute it from the item anyway. + OnRenderArrow(new ToolStripArrowRenderEventArgs(e.Graphics, sb, sb.DropDownButtonBounds, Color.Red, ArrowDirection.Down)); + } + else { + base.OnRenderSplitButtonBackground(e); + } + } + + Color GetItemTextColor(ToolStripItem item) + { + int partId = item.IsOnDropDown ? (int)MenuParts.PopupItem : (int)MenuParts.BarItem; + renderer.SetParameters(MenuClass, partId, GetItemState(item)); + return renderer.GetColor(ColorProperty.TextColor); + } + + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) + { + if (EnsureRenderer()) + e.TextColor = GetItemTextColor(e.Item); + + base.OnRenderItemText(e); + } + + protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) + { + if (EnsureRenderer()) + { + if (e.ToolStrip.IsDropDown) + { + renderer.SetParameters(MenuClass, (int)MenuParts.PopupGutter, 0); + // The AffectedBounds is usually too small, way too small to look right. Instead of using that, + // use the AffectedBounds but with the right width. Then narrow the rectangle to the correct edge + // based on whether or not it's RTL. (It doesn't need to be narrowed to an edge in LTR mode, but let's + // do that anyway.) + // Using the DisplayRectangle gets roughly the right size so that the separator is closer to the text. + Padding margins = GetThemeMargins(e.Graphics, MarginTypes.Sizing); + int extraWidth = (e.ToolStrip.Width - e.ToolStrip.DisplayRectangle.Width - margins.Left - margins.Right - 1) - e.AffectedBounds.Width; + Rectangle rect = e.AffectedBounds; + rect.Y += 2; + rect.Height -= 4; + int sepWidth = renderer.GetPartSize(e.Graphics, ThemeSizeType.True).Width; + if (e.ToolStrip.RightToLeft == RightToLeft.Yes) + { + rect = new Rectangle(rect.X - extraWidth, rect.Y, sepWidth, rect.Height); + rect.X += sepWidth; + } + else { + rect = new Rectangle(rect.Width + extraWidth - sepWidth, rect.Y, sepWidth, rect.Height); + } + renderer.DrawBackground(e.Graphics, rect); + } + } + else { + base.OnRenderImageMargin(e); + } + } + + protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) + { + if (e.ToolStrip.IsDropDown && EnsureRenderer()) + { + renderer.SetParameters(MenuClass, (int)MenuParts.PopupSeparator, 0); + Rectangle rect = new Rectangle(e.ToolStrip.DisplayRectangle.Left, 0, e.ToolStrip.DisplayRectangle.Width, e.Item.Height); + renderer.DrawBackground(e.Graphics, rect, rect); + } + else + { + e.Graphics.DrawLine(Pens.LightGray, + e.Item.ContentRectangle.X, + e.Item.ContentRectangle.Y, + e.Item.ContentRectangle.X, + e.Item.ContentRectangle.Y + e.Item.Height - 6); + } + } + + protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) + { + if (EnsureRenderer()) + { + Rectangle bgRect = GetBackgroundRectangle(e.Item); + bgRect.Width = bgRect.Height; + + // Now, mirror its position if the menu item is RTL. + if (e.Item.RightToLeft == RightToLeft.Yes) + bgRect = new Rectangle(e.ToolStrip.ClientSize.Width - bgRect.X - bgRect.Width, bgRect.Y, bgRect.Width, bgRect.Height); + + renderer.SetParameters(MenuClass, (int)MenuParts.PopupCheckBackground, e.Item.Enabled ? (int)MenuPopupCheckBackgroundStates.Normal : (int)MenuPopupCheckBackgroundStates.Disabled); + renderer.DrawBackground(e.Graphics, bgRect); + + Rectangle checkRect = e.ImageRectangle; + checkRect.X = bgRect.X + bgRect.Width / 2 - checkRect.Width / 2; + checkRect.Y = bgRect.Y + bgRect.Height / 2 - checkRect.Height / 2; + + // I don't think ToolStrip even supports radio box items, so no need to render them. + renderer.SetParameters(MenuClass, (int)MenuParts.PopupCheck, e.Item.Enabled ? (int)MenuPopupCheckStates.CheckmarkNormal : (int)MenuPopupCheckStates.CheckmarkDisabled); + + renderer.DrawBackground(e.Graphics, checkRect); + } + else { + base.OnRenderItemCheck(e); + } + } + + protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) + { + // The default renderer will draw an arrow for us (the UXTheme API seems not to have one for all directions), + // but it will get the colour wrong in many cases. The text colour is probably the best colour to use. + if (EnsureRenderer()) + e.ArrowColor = GetItemTextColor(e.Item); + base.OnRenderArrow(e); + } + + protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e) + { + if (EnsureRenderer()) + { + // BrowserTabBar::Rebar draws the chevron using the default background. Odd. + string rebarClass = RebarClass; + if (Theme == ToolbarTheme.BrowserTabBar) + rebarClass = "Rebar"; + + int state = VisualStyleElement.Rebar.Chevron.Normal.State; + if (e.Item.Pressed) + state = VisualStyleElement.Rebar.Chevron.Pressed.State; + else if (e.Item.Selected) + state = VisualStyleElement.Rebar.Chevron.Hot.State; + + renderer.SetParameters(rebarClass, VisualStyleElement.Rebar.Chevron.Normal.Part, state); + renderer.DrawBackground(e.Graphics, new Rectangle(Point.Empty, e.Item.Size)); + } + else { + base.OnRenderOverflowButtonBackground(e); + } + } + + public bool IsSupported + { + get + { + if (!VisualStyleRenderer.IsSupported) + return false; + + // Needs a more robust check. It seems mono supports very different style sets. + return + VisualStyleRenderer.IsElementDefined( + VisualStyleElement.CreateElement("Menu", + (int)MenuParts.BarBackground, + (int)MenuBarStates.Active)); + } + } + } +} diff --git a/Source/OpenNest/Win32.cs b/Source/OpenNest/Win32.cs new file mode 100644 index 0000000..990e639 --- /dev/null +++ b/Source/OpenNest/Win32.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenNest +{ + public static class Win32 + { + [DllImport("kernel32")] + public static extern long WritePrivateProfileString( + string section, + string key, + string val, + string filePath); + + [DllImport("kernel32")] + public static extern int GetPrivateProfileString( + string section, + string key, + string def, + StringBuilder retVal, + int size, + string filePath); + + [DllImport("user32.dll")] + public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll", ExactSpelling = true)] + public static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + #region Constants + + public const int GWL_EXSTYLE = -20; + public const int WS_EX_CLIENTEDGE = 0x200; + public const uint SWP_NOSIZE = 0x0001; + public const uint SWP_NOMOVE = 0x0002; + public const uint SWP_NOZORDER = 0x0004; + public const uint SWP_NOREDRAW = 0x0008; + public const uint SWP_NOACTIVATE = 0x0010; + public const uint SWP_FRAMECHANGED = 0x0020; + public const uint SWP_SHOWWINDOW = 0x0040; + public const uint SWP_HIDEWINDOW = 0x0080; + public const uint SWP_NOCOPYBITS = 0x0100; + public const uint SWP_NOOWNERZORDER = 0x0200; + public const uint SWP_NOSENDCHANGING = 0x0400; + + #endregion Constants + } +} diff --git a/Source/OpenNest/app.config b/Source/OpenNest/app.config new file mode 100644 index 0000000..24d263e --- /dev/null +++ b/Source/OpenNest/app.config @@ -0,0 +1,69 @@ + + + + +
+
+ + + + + + 0, 0 + + + 0, 0 + + + Normal + + + False + + + True + + + True + + + 0.5 + + + 1 + + + 200 + + + Inches + + + 275 + + + 04/08/2015 19:30:00 + + + + + + 1 + + + 1 + + + 0 + + + + + 0, 0 + + + 0, 0 + + + +

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≪OFA_=Lh(uEIwUJ{X_I8 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 z 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�Q(aO^ye3LfWFXZx;2^>PA@j#qt+I5~O5Jz(-L! zlpr-O)_lQbaI|~F7_qs67@1dy9J&cqinfIuFCrlG<@I>d4s;Oyp*S^pct|amSu`0M zf|U`Jei2r3DFo}HDB2MYU`9!s@|%#1xgyC`AymSb-o6~(&u(=8FbA6W1|{xXpNNy! z3>&SS_sofufVaNEXyCekvb;GY^#~@b!{v6m_Ir6$j~pG+p)a~#9z~x}mu}cLe}PY< z;4>x|P%_MD6$vHfo z;^*UJzKoM<52K~jTukeau-B);(~{wMoE$GdU`XYXq=f4zpmhdM`7_}L52&uYC^XGc z64a$A4m+5d)z^|mm)7!qLTL{Mi)^k^U0=&v!u~7+NQFR4rbUSJGV6ZHE!%i_2V8_X zWhGy->V^zMNJGe#mqCKvVfl;DVq0$_Qr85^HV`zlNnnL&S&4HU7LCFi>6Rg-W#lxt zqya<*JmtGXCo<$=B@;2semWQ9vY&6QUaBr~a)y zwLKid(F;b|(y&IrRReeSn2thak2m7eJ&_!@5&48PP!J`2{M-%u0F8C5(MGL7SZL#H#%Y%9hPJ2lHMzdT33_KKNddV ze1pj^qyY)el<*JTxhx*>UM%ASk@!A`Xqw1NsquIpMeYuJffe#7luP&q7rw=XE?y`P zg}oIlO|+9i1F-d3#B*=x`uh@w24^shQ29X*)@4djtOD60pcG;ze$|3Y#`_o!T;;JO zP&%kqhp_75p9Wk~wT`uMFxgp@sV9Vmgakz5mxHfVc&6~mljwit5WE$s!WHm$Z;u+?$jCGmWi7-SpNFC`|c^gl> zCV{uMU4{U;Hal0Jc#PE^#DjT4{oSNxC9%qVXerLCei>nx4q1#yF&;t+&YGwNIR~kk zk_;9oN+ada8Dv5kfIdU3L3W;*aBk{ANoPMzCEy&9^=Md!fdRE8_2^Qk(8OVPWp^=wkN|_66Y6Ki@YY1Nsh(ad)$=6$%G9+>P!ztd4WYodEWh8 z*avF!i$z99pQ=b-l$VLj4NkU%BS^j+i7w}NBc^TNX-y+b;Md@85X z7nlI}d{Ne=i!_(rz3u^{<#eoPY{aAVxtvtwBX8pQOsjOWUe3G29v=tXQ7|_hH z-GB$;*7DlIf^fPFo#fiI8Vq3)e~@53==!J;_JHV4>x8m8T5=I?#$Dbjy~Cy4*PxYN zZVRtqdGt}@IcJB9tdlw$Ir`C>5`XS{+fwOwLhNhv%Sk%{j4JEpla?${m%x{DnZK2lXaXlFI zNCaS1_-uT*GTL?$Ak3t%fKdc$)I9V~dj~zK&mbE(CXMN1%aFnV&*akZ|o!RwU zGn2<&{pRA!3p3j8nU|mc#>~^l=8qkH^_Yv=zA*PZf&X9j@2v;;d1d|gujp@BsP+Ah z&v#_^%dbq%&mTDY;LrEH{QJ znU&Mq{{AO`Bz=4E2(TH)AMu@l34cK-#GeD=k9G1}8c)I{_JPA%;cpN4W1BjfrZ5r; z_#zKK>cSFi@dAFzi5T*SjTcdtKUT;e1>$!e8py#u;g4#6LrOG)pCPsp>;N7Q24F|Q zPQiuW#UKp+pb#M&4eaMC*T-p0XvW{|>Oce1sx^N*#rU(~t@L7< nUFZ$|3@v}4PJi(ER^?dV|8f5s<$xdSejlgx|Nr0rdldNJ+)>OU literal 0 HcmV?d00001 diff --git a/Installer/script.nsi b/Installer/script.nsi new file mode 100644 index 0000000..cfc5331 --- /dev/null +++ b/Installer/script.nsi @@ -0,0 +1,138 @@ +!include "MUI2.nsh" + +Name "OpenNest" + +OutFile "opennest-setup.exe" + +InstallDir "$PROGRAMFILES\OpenNest" + +RequestExecutionLevel admin + +!define DOT_MAJOR "4" +!define DOT_MINOR "0" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "../license.txt" +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +UninstPage uninstConfirm +UninstPage instfiles + +Section "OpenNest (required)" + Call IsDotNETInstalled + SectionIn RO +#!/usr/bin/env + ; Set output path to the installation directory. + SetOutPath $INSTDIR + + File "OpenNest.exe" + File "OpenNest.Core.dll" + File "OpenNest.Engine.dll" + File "Ionic.Zip.dll" + File "netDxf.dll" + + ; Write the installation path into the registry + WriteRegStr HKLM SOFTWARE\OpenNest "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "DisplayName" "OpenNest" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "NoRepair" 1 + WriteUninstaller "uninstall.exe" +SectionEnd + +Section "Start Menu Shortcuts" + CreateDirectory "$SMPROGRAMS\OpenNest" + CreateShortCut "$SMPROGRAMS\OpenNest\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 + CreateShortCut "$SMPROGRAMS\OpenNest\OpenNest.lnk" "$INSTDIR\OpenNest.exe" "" "$INSTDIR\OpenNest.exe" 0 +SectionEnd + +Section "Uninstall" + Delete "$INSTDIR\uninstall.exe" + Delete "$INSTDIR\OpenNest.exe" + Delete "$INSTDIR\OpenNest.Core.dll" + Delete "$INSTDIR\OpenNest.Engine.dll" + Delete "$INSTDIR\Ionic.Zip.dll" + Delete "$INSTDIR\netDxf.dll" + + Delete "$SMPROGRAMS\OpenNest\Uninstall.lnk" + Delete "$SMPROGRAMS\OpenNest\OpenNest.lnk" + Delete "$SMPROGRAMS\OpenNest" +SectionEnd + +; Usage +; Define in your script two constants: +; DOT_MAJOR "(Major framework version)" +; DOT_MINOR "{Minor framework version)" +; +; Call IsDotNetInstalled +; This function will abort the installation if the required version +; or higher version of the .NET Framework is not installed. Place it in +; either your .onInit function or your first install section before +; other code. +Function IsDotNetInstalled + + StrCpy $0 "0" + StrCpy $1 "SOFTWARE\Microsoft\.NETFramework" ;registry entry to look in. + StrCpy $2 0 + + StartEnum: + ;Enumerate the versions installed. + EnumRegKey $3 HKLM "$1\policy" $2 + + ;If we don't find any versions installed, it's not here. + StrCmp $3 "" noDotNet notEmpty + + ;We found something. + notEmpty: + ;Find out if the RegKey starts with 'v'. + ;If it doesn't, goto the next key. + StrCpy $4 $3 1 0 + StrCmp $4 "v" +1 goNext + StrCpy $4 $3 1 1 + + ;It starts with 'v'. Now check to see how the installed major version + ;relates to our required major version. + ;If it's equal check the minor version, if it's greater, + ;we found a good RegKey. + IntCmp $4 ${DOT_MAJOR} +1 goNext yesDotNetReg + ;Check the minor version. If it's equal or greater to our requested + ;version then we're good. + StrCpy $4 $3 1 3 + IntCmp $4 ${DOT_MINOR} yesDotNetReg goNext yesDotNetReg + + goNext: + ;Go to the next RegKey. + IntOp $2 $2 + 1 + goto StartEnum + + yesDotNetReg: + ;Now that we've found a good RegKey, let's make sure it's actually + ;installed by getting the install path and checking to see if the + ;mscorlib.dll exists. + EnumRegValue $2 HKLM "$1\policy\$3" 0 + ;$2 should equal whatever comes after the major and minor versions + ;(ie, v1.1.4322) + StrCmp $2 "" noDotNet + ReadRegStr $4 HKLM $1 "InstallRoot" + ;Hopefully the install root isn't empty. + StrCmp $4 "" noDotNet + ;build the actuall directory path to mscorlib.dll. + StrCpy $4 "$4$3.$2\mscorlib.dll" + IfFileExists $4 yesDotNet noDotNet + + noDotNet: + ;Nope, something went wrong along the way. Looks like the + ;proper .NET Framework isn't installed. + MessageBox MB_OK "You must have v${DOT_MAJOR}.${DOT_MINOR} or greater of the .NET Framework installed. Aborting!" + Abort + + yesDotNet: + ;Everything checks out. Go on with the rest of the installation. + +FunctionEnd \ No newline at end of file diff --git a/Source/OpenNest.Core/Align.cs b/Source/OpenNest.Core/Align.cs new file mode 100644 index 0000000..3de4469 --- /dev/null +++ b/Source/OpenNest.Core/Align.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using OpenNest.Geometry; + +namespace OpenNest +{ + public static class Align + { + public static void Vertically(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(fixedEntity.BoundingBox.Center.X - movableEntity.BoundingBox.Center.X, 0); + } + + public static void Vertically(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Vertically(fixedEntity, entity)); + } + + public static void Vertically(Part fixedPart, Part movablePart) + { + movablePart.Offset(fixedPart.BoundingBox.Center.X - movablePart.BoundingBox.Center.X, 0); + } + + public static void Vertically(Part fixedPart, List parts) + { + parts.ForEach(part => Vertically(fixedPart, part)); + } + + public static void Horizontally(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(0, fixedEntity.BoundingBox.Center.Y - movableEntity.BoundingBox.Center.Y); + } + + public static void Horizontally(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Horizontally(fixedEntity, entity)); + } + + public static void Horizontally(Part fixedPart, Part movablePart) + { + movablePart.Offset(0, fixedPart.BoundingBox.Center.Y - movablePart.BoundingBox.Center.Y); + } + + public static void Horizontally(Part fixedPart, List parts) + { + parts.ForEach(part => Horizontally(fixedPart, part)); + } + + public static void Left(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(fixedEntity.BoundingBox.Left - movableEntity.BoundingBox.Left, 0); + } + + public static void Left(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Left(fixedEntity, entity)); + } + + public static void Left(Part fixedPart, Part movablePart) + { + movablePart.Offset(fixedPart.BoundingBox.Left - movablePart.BoundingBox.Left, 0); + } + + public static void Left(Part fixedPart, List parts) + { + parts.ForEach(part => Left(fixedPart, part)); + } + + public static void Right(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(fixedEntity.BoundingBox.Right - movableEntity.BoundingBox.Right, 0); + } + + public static void Right(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Right(fixedEntity, entity)); + } + + public static void Right(Part fixedPart, Part movablePart) + { + movablePart.Offset(fixedPart.BoundingBox.Right - movablePart.BoundingBox.Right, 0); + } + + public static void Right(Part fixedPart, List parts) + { + parts.ForEach(part => Right(fixedPart, part)); + } + + public static void Top(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(0, fixedEntity.BoundingBox.Top - movableEntity.BoundingBox.Top); + } + + public static void Top(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Top(fixedEntity, entity)); + } + + public static void Top(Part fixedPart, Part movablePart) + { + movablePart.Offset(0, fixedPart.BoundingBox.Top - movablePart.BoundingBox.Top); + } + + public static void Top(Part fixedPart, List parts) + { + parts.ForEach(part => Top(fixedPart, part)); + } + + public static void Bottom(Entity fixedEntity, Entity movableEntity) + { + movableEntity.Offset(0, fixedEntity.BoundingBox.Bottom - movableEntity.BoundingBox.Bottom); + } + + public static void Bottom(Entity fixedEntity, List entities) + { + entities.ForEach(entity => Bottom(fixedEntity, entity)); + } + + public static void Bottom(Part fixedPart, Part movablePart) + { + movablePart.Offset(0, fixedPart.BoundingBox.Bottom - movablePart.BoundingBox.Bottom); + } + + public static void Bottom(Part fixedPart, List parts) + { + parts.ForEach(part => Bottom(fixedPart, part)); + } + + public static void EvenlyDistributeHorizontally(List parts) + { + if (parts.Count < 3) + return; + + var list = new List(parts); + list.Sort((p1, p2) => p1.BoundingBox.Center.X.CompareTo(p2.BoundingBox.Center.X)); + + var lastIndex = list.Count - 1; + + var first = list[0]; + var last = list[lastIndex]; + + var start = first.BoundingBox.Center.X; + var end = last.BoundingBox.Center.X; + var diff = end - start; + + var spacing = diff / lastIndex; + + for (int i = 1; i < lastIndex; ++i) + { + var part = list[i]; + var newX = start + i * spacing; + var curX = part.BoundingBox.Center.X; + + part.Offset(newX - curX, 0); + } + } + + public static void EvenlyDistributeVertically(List parts) + { + if (parts.Count < 3) + return; + + var list = new List(parts); + list.Sort((p1, p2) => p1.BoundingBox.Center.Y.CompareTo(p2.BoundingBox.Center.Y)); + + var lastIndex = list.Count - 1; + + var first = list[0]; + var last = list[lastIndex]; + + var start = first.BoundingBox.Center.Y; + var end = last.BoundingBox.Center.Y; + var diff = end - start; + + var spacing = diff / lastIndex; + + for (int i = 1; i < lastIndex; ++i) + { + var part = list[i]; + var newX = start + i * spacing; + var curX = part.BoundingBox.Center.Y; + + part.Offset(0, newX - curX); + } + } + } +} diff --git a/Source/OpenNest.Core/AlignType.cs b/Source/OpenNest.Core/AlignType.cs new file mode 100644 index 0000000..c56bfd1 --- /dev/null +++ b/Source/OpenNest.Core/AlignType.cs @@ -0,0 +1,15 @@ + +namespace OpenNest +{ + public enum AlignType + { + Left, + Right, + Top, + Bottom, + Horizontally, + Vertically, + EvenlySpaceHorizontally, + EvenlySpaceVertically + } +} diff --git a/Source/OpenNest.Core/Angle.cs b/Source/OpenNest.Core/Angle.cs new file mode 100644 index 0000000..fd70723 --- /dev/null +++ b/Source/OpenNest.Core/Angle.cs @@ -0,0 +1,125 @@ +using System; + +namespace OpenNest +{ + public static class Angle + { + ///