From 6492f25d1df6e162890e2001834b0ea77a95b400 Mon Sep 17 00:00:00 2001 From: MatterHackers Date: Sat, 11 Mar 2023 11:26:16 -0800 Subject: [PATCH] Improving arange all adding part sheet to main improving folder sorting and naming, fixing path rendering --- MatterControl.csproj | 1 - .../ApplicationView/AppViewState.cs | 2 +- .../WrappedLibraryContainer.cs | 2 +- .../Attributes/DirectoryPathAttribute.cs | 41 ++ .../Operations/Object3DExtensions.cs | 10 + .../Operations/Path/OutlinePathObject3D.cs | 10 - .../DesignTools/PublicPropertyEditor.cs | 33 +- .../Library/Interfaces/LibraryViewState.cs | 2 +- .../Providers/GitHub/GitHubContainer.cs | 4 +- .../Library/Providers/RootLibraryContainer.cs | 8 +- .../Library/Widgets/LibraryWidget.cs | 2 +- .../Widgets/ListView/LibraryListView.cs | 25 +- ...LibraryWidget.cs => PopupLibraryWidget.cs} | 49 +-- .../Widgets/TextEditWithInlineCancel.cs | 80 ++++ .../PartPreviewWindow/PlatingHelper.cs | 370 ++++++++++-------- .../PartPreviewWindow/View3D/SceneActions.cs | 2 +- .../PartPreviewWindow/ViewToolBarControls.cs | 6 +- MatterControlLib/PartSheet/PartSheetPlugin.cs | 76 ++++ .../PartSheet/PartsSheetCreator.cs | 342 ++++++++++++++++ 19 files changed, 819 insertions(+), 246 deletions(-) create mode 100644 MatterControlLib/DesignTools/Attributes/DirectoryPathAttribute.cs rename MatterControlLib/Library/Widgets/{PrintLibraryWidget.cs => PopupLibraryWidget.cs} (90%) create mode 100644 MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs create mode 100644 MatterControlLib/PartSheet/PartSheetPlugin.cs create mode 100644 MatterControlLib/PartSheet/PartsSheetCreator.cs diff --git a/MatterControl.csproj b/MatterControl.csproj index 93b6493a2..6fdff57d8 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -70,7 +70,6 @@ - diff --git a/MatterControlLib/ApplicationView/AppViewState.cs b/MatterControlLib/ApplicationView/AppViewState.cs index 09c66d238..b8760a7c3 100644 --- a/MatterControlLib/ApplicationView/AppViewState.cs +++ b/MatterControlLib/ApplicationView/AppViewState.cs @@ -34,6 +34,6 @@ namespace MatterHackers.MatterControl { public class AppViewState { - public PrintLibraryWidget.ListViewModes LibraryViewMode { get; set; } = PrintLibraryWidget.ListViewModes.IconListView; + public PopupLibraryWidget.ListViewModes LibraryViewMode { get; set; } = PopupLibraryWidget.ListViewModes.IconListView; } } \ No newline at end of file diff --git a/MatterControlLib/ApplicationView/WrappedLibraryContainer.cs b/MatterControlLib/ApplicationView/WrappedLibraryContainer.cs index 2790c02f7..6a5da46ab 100644 --- a/MatterControlLib/ApplicationView/WrappedLibraryContainer.cs +++ b/MatterControlLib/ApplicationView/WrappedLibraryContainer.cs @@ -69,7 +69,7 @@ namespace MatterHackers.MatterControl public ICustomSearch CustomSearch => _libraryContainer.CustomSearch; - public LibrarySortBehavior DefaultSort => null; + public LibrarySortBehavior DefaultSort => _libraryContainer?.DefaultSort; public event EventHandler ContentChanged; diff --git a/MatterControlLib/DesignTools/Attributes/DirectoryPathAttribute.cs b/MatterControlLib/DesignTools/Attributes/DirectoryPathAttribute.cs new file mode 100644 index 000000000..1699dd016 --- /dev/null +++ b/MatterControlLib/DesignTools/Attributes/DirectoryPathAttribute.cs @@ -0,0 +1,41 @@ +/* +Copyright (c) 2018, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Localizations; +using System; + +namespace MatterHackers.MatterControl.DesignTools +{ + [AttributeUsage(AttributeTargets.Property)] + public class DirectoryPathAttribute : Attribute + { + public string Message { get; set; } = "Selecte a directory".Localize(); + public string ActionLabel { get; set; } = "Select".Localize(); + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs b/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs index 84fe73fa8..8e7044d31 100644 --- a/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs +++ b/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs @@ -87,6 +87,16 @@ namespace MatterHackers.MatterControl.DesignTools.Operations } } + public static void RebuildAll(this IObject3D item) + { + var updateItems = Expressions.SortAndLockUpdateItems(item, (item) => + { + return true; + }, false); + + Expressions.SendInvalidateInRebuildOrder(updateItems, InvalidateType.Properties, null); + } + public static void RefreshToolBar(this IObject3D item) { var sceneContext = item.ContainingScene(); diff --git a/MatterControlLib/DesignTools/Operations/Path/OutlinePathObject3D.cs b/MatterControlLib/DesignTools/Operations/Path/OutlinePathObject3D.cs index c69d868a0..cfcb3bf04 100644 --- a/MatterControlLib/DesignTools/Operations/Path/OutlinePathObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/Path/OutlinePathObject3D.cs @@ -153,15 +153,5 @@ namespace MatterHackers.MatterControl.DesignTools.Operations VertexStorage.Add(0, 0, ShapePath.FlagsAndCommand.Stop); } - - public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e) - { - this.DrawPath(); - } - - public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer) - { - return this.GetWorldspaceAabbOfDrawPath(); - } } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/PublicPropertyEditor.cs b/MatterControlLib/DesignTools/PublicPropertyEditor.cs index 95c2f7a65..3db3d294c 100644 --- a/MatterControlLib/DesignTools/PublicPropertyEditor.cs +++ b/MatterControlLib/DesignTools/PublicPropertyEditor.cs @@ -1095,7 +1095,38 @@ namespace MatterHackers.MatterControl.DesignTools field.Content.HAnchor = HAnchor.Stretch; RegisterValueChanged(field, (valueString) => valueString); rowContainer = CreateSettingsRow(property, field.Content, theme, rows); - } + + // check for DirectoryPathAttribute + var directoryPathAttribute = property.PropertyInfo.GetCustomAttributes(true).OfType().FirstOrDefault(); + if (directoryPathAttribute != null) + { + // add a browse button + var browseButton = new ThemedIconButton(StaticData.Instance.LoadIcon("icon_search_24x24.png", 16, 16).SetToColor(theme.TextColor), theme) + { + ToolTipText = "Search".Localize(), + }; + browseButton.Click += (s, e) => + { + UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.SelectFolderDialog( + new SelectFolderDialogParams(directoryPathAttribute.Message) + { + ActionButtonLabel = directoryPathAttribute.ActionLabel, + Title = ApplicationController.Instance.ProductName + " - " + "Select A Folder".Localize() + }, + (openParams) => + { + if (!string.IsNullOrEmpty(openParams.FolderPath)) + { + field.SetValue(openParams.FolderPath, true); + } + }); + }); + }; + rowContainer.AddChild(browseButton); + } + } } } } diff --git a/MatterControlLib/Library/Interfaces/LibraryViewState.cs b/MatterControlLib/Library/Interfaces/LibraryViewState.cs index d78ee60ec..9517823b3 100644 --- a/MatterControlLib/Library/Interfaces/LibraryViewState.cs +++ b/MatterControlLib/Library/Interfaces/LibraryViewState.cs @@ -33,7 +33,7 @@ namespace MatterHackers.MatterControl.Library { public class LibraryViewState { - public PrintLibraryWidget.ListViewModes ViewMode { get; set; } + public PopupLibraryWidget.ListViewModes ViewMode { get; set; } public LibrarySortBehavior SortBehavior { get; set; } } diff --git a/MatterControlLib/Library/Providers/GitHub/GitHubContainer.cs b/MatterControlLib/Library/Providers/GitHub/GitHubContainer.cs index a7422ad34..96ffd95fb 100644 --- a/MatterControlLib/Library/Providers/GitHub/GitHubContainer.cs +++ b/MatterControlLib/Library/Providers/GitHub/GitHubContainer.cs @@ -71,12 +71,12 @@ namespace MatterHackers.MatterControl.Library private object locker = new object(); - public GitHubContainer(string containerName, string account, string repositor, string repoDirectory) + public GitHubContainer(string containerName, string account, string repository, string repoDirectory) { this.ChildContainers = new SafeList(); this.Name = containerName; this.Account = account; - this.Repository = repositor; + this.Repository = repository; this.RepoDirectory = repoDirectory; // Initialize a default CollectionData with a "Loading..." entry diff --git a/MatterControlLib/Library/Providers/RootLibraryContainer.cs b/MatterControlLib/Library/Providers/RootLibraryContainer.cs index ea7a6cf2b..a1404dd37 100644 --- a/MatterControlLib/Library/Providers/RootLibraryContainer.cs +++ b/MatterControlLib/Library/Providers/RootLibraryContainer.cs @@ -66,9 +66,13 @@ namespace MatterHackers.MatterControl.Library public ICustomSearch CustomSearch { get; } = null; - public LibrarySortBehavior DefaultSort => null; + public LibrarySortBehavior DefaultSort => new LibrarySortBehavior() + { + SortKey = SortKey.ModifiedDate, + Ascending = true + }; - public Task GetThumbnail(ILibraryItem item, int width, int height) + public Task GetThumbnail(ILibraryItem item, int width, int height) { return Task.FromResult(null); } diff --git a/MatterControlLib/Library/Widgets/LibraryWidget.cs b/MatterControlLib/Library/Widgets/LibraryWidget.cs index f41ee30f2..221205d2d 100644 --- a/MatterControlLib/Library/Widgets/LibraryWidget.cs +++ b/MatterControlLib/Library/Widgets/LibraryWidget.cs @@ -43,7 +43,7 @@ using MatterHackers.MatterControl.CustomWidgets; using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.MatterControl.PrinterCommunication; using MatterHackers.MatterControl.PrintQueue; -using static MatterHackers.MatterControl.Library.Widgets.PrintLibraryWidget; +using static MatterHackers.MatterControl.Library.Widgets.PopupLibraryWidget; namespace MatterHackers.MatterControl.Library.Widgets { diff --git a/MatterControlLib/Library/Widgets/ListView/LibraryListView.cs b/MatterControlLib/Library/Widgets/ListView/LibraryListView.cs index 85d065f5e..e27caeaa4 100644 --- a/MatterControlLib/Library/Widgets/ListView/LibraryListView.cs +++ b/MatterControlLib/Library/Widgets/ListView/LibraryListView.cs @@ -46,7 +46,7 @@ using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.MatterControl.PrintQueue; using MatterHackers.VectorMath; using static MatterHackers.MatterControl.CustomWidgets.LibraryListView; -using static MatterHackers.MatterControl.Library.Widgets.PrintLibraryWidget; +using static MatterHackers.MatterControl.Library.Widgets.PopupLibraryWidget; namespace MatterHackers.MatterControl.CustomWidgets { @@ -100,7 +100,12 @@ namespace MatterHackers.MatterControl.CustomWidgets context.ContainerChanged += ActiveContainer_Changed; context.ContentChanged += ActiveContainer_ContentChanged; - } + + if (ActiveContainer != null) + { + ActiveContainer_Changed(this, new ContainerChangedEventArgs(ActiveContainer, null)); + } + } private void ContentView_Click(object sender, MouseEventArgs e) { @@ -231,35 +236,35 @@ namespace MatterHackers.MatterControl.CustomWidgets public void SetUserSort(bool ascending) { - this.Ascending = true; + this.Ascending = ascending; this.PersistUserView(); } - public void SetContentView(PrintLibraryWidget.ListViewModes viewMode, bool userDriven = true) + public void SetContentView(PopupLibraryWidget.ListViewModes viewMode, bool userDriven = true) { ApplicationController.Instance.ViewState.LibraryViewMode = viewMode; switch (viewMode) { - case PrintLibraryWidget.ListViewModes.RowListView: + case PopupLibraryWidget.ListViewModes.RowListView: this.ListContentView = new RowListView(theme); break; - case PrintLibraryWidget.ListViewModes.IconListView18: + case PopupLibraryWidget.ListViewModes.IconListView18: this.ListContentView = new IconListView(theme, 18); break; - case PrintLibraryWidget.ListViewModes.IconListView70: + case PopupLibraryWidget.ListViewModes.IconListView70: this.ListContentView = new IconListView(theme, 70); break; - case PrintLibraryWidget.ListViewModes.IconListView256: + case PopupLibraryWidget.ListViewModes.IconListView256: this.ListContentView = new IconListView(theme, 256); break; - case PrintLibraryWidget.ListViewModes.IconListView: + case PopupLibraryWidget.ListViewModes.IconListView: default: - if (viewMode != PrintLibraryWidget.ListViewModes.IconListView) + if (viewMode != PopupLibraryWidget.ListViewModes.IconListView) { Debugger.Break(); // Unknown/unexpected value } diff --git a/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs b/MatterControlLib/Library/Widgets/PopupLibraryWidget.cs similarity index 90% rename from MatterControlLib/Library/Widgets/PrintLibraryWidget.cs rename to MatterControlLib/Library/Widgets/PopupLibraryWidget.cs index 18af46bab..da35c203c 100644 --- a/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs +++ b/MatterControlLib/Library/Widgets/PopupLibraryWidget.cs @@ -43,7 +43,7 @@ using MatterHackers.MatterControl.PrintQueue; namespace MatterHackers.MatterControl.Library.Widgets { - public class PrintLibraryWidget : GuiWidget, IIgnoredPopupChild + public class PopupLibraryWidget : GuiWidget, IIgnoredPopupChild { private FlowLayoutWidget buttonPanel; private ILibraryContext libraryContext; @@ -64,7 +64,7 @@ namespace MatterHackers.MatterControl.Library.Widgets public bool ShowContainers { get; private set; } = true; - public PrintLibraryWidget(MainViewWidget mainViewWidget, PartWorkspace workspace, ThemeConfig theme, Color libraryBackground, PopupMenuButton popupMenuButton) + public PopupLibraryWidget(MainViewWidget mainViewWidget, PartWorkspace workspace, ThemeConfig theme, Color libraryBackground, PopupMenuButton popupMenuButton) { this.theme = theme; this.mainViewWidget = mainViewWidget; @@ -416,49 +416,4 @@ namespace MatterHackers.MatterControl.Library.Widgets IconListView256 } } - - public class TextEditWithInlineCancel : GuiWidget - { - public ThemedTextEditWidget TextEditWidget { get; } - - public GuiWidget ResetButton { get; } - - public TextEditWithInlineCancel(ThemeConfig theme, string emptyText = null) - { - if (emptyText == null) - { - emptyText = "Search".Localize(); - } - - this.VAnchor = VAnchor.Center | VAnchor.Fit; - this.HAnchor = HAnchor.Stretch; - - TextEditWidget = new ThemedTextEditWidget("", theme, messageWhenEmptyAndNotSelected: emptyText) - { - HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Center - }; - this.AddChild(TextEditWidget); - - this.ResetButton = theme.CreateSmallResetButton(); - ResetButton.HAnchor |= HAnchor.Right; - ResetButton.VAnchor |= VAnchor.Center; - ResetButton.Name = "Close Search"; - ResetButton.ToolTipText = "Clear".Localize(); - - this.AddChild(ResetButton); - } - - public override void OnLoad(EventArgs args) - { - TextEditWidget.Focus(); - base.OnLoad(args); - } - - public override string Text - { - get => TextEditWidget.ActualTextEditWidget.Text; - set => TextEditWidget.ActualTextEditWidget.Text = value; - } - } } diff --git a/MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs b/MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs new file mode 100644 index 000000000..4261fafd3 --- /dev/null +++ b/MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs @@ -0,0 +1,80 @@ +/* +Copyright (c) 2022, Kevin Pope, John Lewin, Lars Brubaker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; + +namespace MatterHackers.MatterControl.Library.Widgets +{ + public class TextEditWithInlineCancel : GuiWidget + { + public ThemedTextEditWidget TextEditWidget { get; } + + public GuiWidget ResetButton { get; } + + public TextEditWithInlineCancel(ThemeConfig theme, string emptyText = null) + { + if (emptyText == null) + { + emptyText = "Search".Localize(); + } + + this.VAnchor = VAnchor.Center | VAnchor.Fit; + this.HAnchor = HAnchor.Stretch; + + TextEditWidget = new ThemedTextEditWidget("", theme, messageWhenEmptyAndNotSelected: emptyText) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Center + }; + this.AddChild(TextEditWidget); + + this.ResetButton = theme.CreateSmallResetButton(); + ResetButton.HAnchor |= HAnchor.Right; + ResetButton.VAnchor |= VAnchor.Center; + ResetButton.Name = "Close Search"; + ResetButton.ToolTipText = "Clear".Localize(); + + this.AddChild(ResetButton); + } + + public override void OnLoad(EventArgs args) + { + TextEditWidget.Focus(); + base.OnLoad(args); + } + + public override string Text + { + get => TextEditWidget.ActualTextEditWidget.Text; + set => TextEditWidget.ActualTextEditWidget.Text = value; + } + } +} diff --git a/MatterControlLib/PartPreviewWindow/PlatingHelper.cs b/MatterControlLib/PartPreviewWindow/PlatingHelper.cs index 3921929dd..c9953c23e 100644 --- a/MatterControlLib/PartPreviewWindow/PlatingHelper.cs +++ b/MatterControlLib/PartPreviewWindow/PlatingHelper.cs @@ -1,5 +1,5 @@ /* -Copyright (c) 2014, Lars Brubaker +Copyright (c) 2023, Lars Brubaker All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,202 +41,242 @@ using Polygons = System.Collections.Generic.List object3DList, Vector3 bedCenter) - { - if (object3DList.Count == 0) - { - return; - } + public enum PositionType + { + Center, + LowerLeft, + None, + } - // move them all out of the way - for (int i = 0; i < object3DList.Count; i++) - { - object3DList[i].Matrix *= Matrix4X4.CreateTranslation(10000, 10000, 0); - } + /// + /// Arrange the given parts on the bed and return a list of the parts that were arranged + /// + /// The parts to arrange + /// A position to arrange around + /// The way to consider the possition + /// Optional bounds to arrange into + /// The current progress of arranging + /// A list of the parts that were arranged + public static List ArrangeOnBed(List object3DList, + Vector3 arangePosition, + PositionType positionType, + RectangleDouble? bedBounds = null, + Action progressReporter = null) + { + if (object3DList.Count == 0) + { + return null; + } - // sort them by size - object3DList.Sort(SortOnBigToLittle); + var objectsThatWereArrange = new List(); + var objectsThatHaveBeenPlaced = new List(); - double ratioPerMeshGroup = 1.0 / object3DList.Count; - double currentRatioDone = 0; - // put them onto the plate (try the center) starting with the biggest and moving down - for (int meshGroupIndex = 0; meshGroupIndex < object3DList.Count; meshGroupIndex++) - { - var object3D = object3DList[meshGroupIndex]; - Vector3 meshLowerLeft = object3D.GetAxisAlignedBoundingBox().MinXYZ; - object3D.Matrix *= Matrix4X4.CreateTranslation(-meshLowerLeft); + // sort them by size + object3DList.Sort(SortOnBigToLittle); - PlatingHelper.MoveToOpenPositionRelativeGroup(object3D, object3DList); + double ratioPerMeshGroup = 1.0 / object3DList.Count; + double currentRatioDone = 0; + // put them onto the plate (try the center) starting with the biggest and moving down + for (int meshGroupIndex = 0; meshGroupIndex < object3DList.Count; meshGroupIndex++) + { + var object3D = object3DList[meshGroupIndex]; + Vector3 meshLowerLeft = object3D.GetAxisAlignedBoundingBox().MinXYZ; + object3D.Matrix *= Matrix4X4.CreateTranslation(-meshLowerLeft); - currentRatioDone += ratioPerMeshGroup; + if (MoveToOpenPositionRelativeGroup(object3D, objectsThatHaveBeenPlaced, bedBounds)) + { + objectsThatHaveBeenPlaced.Add(object3D); + objectsThatWereArrange.Add(object3D); + } - // and put it on the bed - PlatingHelper.PlaceOnBed(object3D); - } + progressReporter?.Invoke(Util.GetRatio(0, 1, meshGroupIndex, object3DList.Count), null); - // and finally center whatever we have as a group - { - AxisAlignedBoundingBox bounds = object3DList[0].GetAxisAlignedBoundingBox(); - for (int i = 1; i < object3DList.Count; i++) - { - bounds = AxisAlignedBoundingBox.Union(bounds, object3DList[i].GetAxisAlignedBoundingBox()); - } + currentRatioDone += ratioPerMeshGroup; - Vector3 boundsCenter = (bounds.MaxXYZ + bounds.MinXYZ) / 2; - for (int i = 0; i < object3DList.Count; i++) - { - object3DList[i].Matrix *= Matrix4X4.CreateTranslation(-boundsCenter + new Vector3(0, 0, bounds.ZSize / 2) + bedCenter); - } - } - } + // and put it on the bed (set the bottom to z = 0) + PlaceOnBed(object3D); + } - private static int SortOnBigToLittle(IObject3D a, IObject3D b) - { - AxisAlignedBoundingBox xAABB = b.GetAxisAlignedBoundingBox(); - AxisAlignedBoundingBox yAABB = a.GetAxisAlignedBoundingBox(); - return Math.Max(xAABB.XSize, xAABB.YSize).CompareTo(Math.Max(yAABB.XSize, yAABB.YSize)); - } + // and finally center whatever we have as a group + if (positionType != PositionType.None) + { + AxisAlignedBoundingBox bounds = object3DList[0].GetAxisAlignedBoundingBox(); + for (int i = 1; i < object3DList.Count; i++) + { + bounds = AxisAlignedBoundingBox.Union(bounds, object3DList[i].GetAxisAlignedBoundingBox()); + } - public static void PlaceOnBed(IObject3D object3D) - { - AxisAlignedBoundingBox bounds = object3D.GetAxisAlignedBoundingBox(); - Vector3 boundsCenter = (bounds.MaxXYZ + bounds.MinXYZ) / 2; + Vector3 offset = bounds.MinXYZ; + if (positionType == PositionType.Center) + { + offset = (bounds.MaxXYZ + bounds.MinXYZ) / 2; + offset.Z = 0; + } - object3D.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, -boundsCenter.Z + bounds.ZSize / 2)); - } + for (int i = 0; i < object3DList.Count; i++) + { + object3DList[i].Matrix *= Matrix4X4.CreateTranslation(arangePosition - offset); + } + } - /// - /// Moves the target object to the first non-colliding position, starting from the lower left corner of the bounding box containing all sceneItems - /// - /// The object to position - /// The objects to hit test against - public static void MoveToOpenPositionRelativeGroup(IObject3D objectToAdd, IEnumerable itemsToAvoid) - { - if (objectToAdd == null || !itemsToAvoid.Any()) - { - return; - } + return objectsThatWereArrange; + } - // find the bounds of all items in the scene - AxisAlignedBoundingBox allPlacedMeshBounds = itemsToAvoid.GetUnionedAxisAlignedBoundingBox(); + private static int SortOnBigToLittle(IObject3D a, IObject3D b) + { + AxisAlignedBoundingBox xAABB = b.GetAxisAlignedBoundingBox(); + AxisAlignedBoundingBox yAABB = a.GetAxisAlignedBoundingBox(); + return Math.Max(xAABB.XSize, xAABB.YSize).CompareTo(Math.Max(yAABB.XSize, yAABB.YSize)); + } - // move the part to the total bounds lower left side - Vector3 meshLowerLeft = objectToAdd.GetAxisAlignedBoundingBox().MinXYZ; - objectToAdd.Matrix *= Matrix4X4.CreateTranslation(-meshLowerLeft + allPlacedMeshBounds.MinXYZ); + public static void PlaceOnBed(IObject3D object3D) + { + AxisAlignedBoundingBox bounds = object3D.GetAxisAlignedBoundingBox(); + object3D.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, -bounds.MinXYZ.Z)); + } - // make sure it is on the 0 plane - var aabb = objectToAdd.GetAxisAlignedBoundingBox(); - objectToAdd.Matrix *= Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z); + /// + /// Moves the target object to the first non-colliding position, starting from the lower left corner of the bounding box containing all sceneItems + /// + /// The object to position + /// The objects to hit test against + public static bool MoveToOpenPositionRelativeGroup(IObject3D objectToAdd, IEnumerable itemsToAvoid, RectangleDouble? bedBounds = null) + { + if (objectToAdd == null) + { + return false; + } - // keep moving the item until its in an open slot - MoveToOpenPosition(objectToAdd, itemsToAvoid); - } + // move the part to the total bounds lower left side + Vector3 meshLowerLeft = objectToAdd.GetAxisAlignedBoundingBox().MinXYZ; + objectToAdd.Matrix *= Matrix4X4.CreateTranslation(-meshLowerLeft); - /// - /// Moves the target object to the first non-colliding position, starting at the initial position of the target object - /// - /// The object to position - /// The objects to hit test against - public static void MoveToOpenPosition(IObject3D itemToMove, IEnumerable itemsToAvoid) - { - if (itemToMove == null) - { - return; - } + // keep moving the item until its in an open slot + return MoveToOpenPosition(objectToAdd, itemsToAvoid, bedBounds); + } - // find a place to put it that doesn't hit anything - var currentBounds = itemToMove.GetAxisAlignedBoundingBox(); - var itemToMoveBounds = new AxisAlignedBoundingBox(currentBounds.MinXYZ, currentBounds.MaxXYZ); + /// + /// Moves the target object to the first non-colliding position, starting at the initial position of the target object + /// + /// The object to position + /// The objects to hit test against + public static bool MoveToOpenPosition(IObject3D itemToMove, IEnumerable itemsToAvoid, RectangleDouble? bedBounds = null) + { + if (itemToMove == null) + { + return false; + } - // add in a few mm so that it will not be touching - itemToMoveBounds.MinXYZ -= new Vector3(2, 2, 0); - itemToMoveBounds.MaxXYZ += new Vector3(2, 2, 0); + // find a place to put it that doesn't hit anything + var currentBounds = itemToMove.GetAxisAlignedBoundingBox(); + var itemToMoveBounds = new AxisAlignedBoundingBox(currentBounds.MinXYZ, currentBounds.MaxXYZ); - while (true) - { - int distance = 0; - while (true) - { - for (int i = 0; i <= distance; i++) - { - var transform = Matrix4X4.Identity; + // add in a few mm so that it will not be touching + itemToMoveBounds.MinXYZ -= new Vector3(2, 2, 0); + itemToMoveBounds.MaxXYZ += new Vector3(2, 2, 0); - if (CheckPosition(itemsToAvoid, itemToMove, itemToMoveBounds, i, distance, out transform)) - { - itemToMove.Matrix *= transform; - return; - } + while (true) + { + int distance = 0; + while (true) + { + for (int i = 0; i <= distance; i++) + { + Matrix4X4 transform; + if (CheckPosition(itemsToAvoid, itemToMove, itemToMoveBounds, i, distance, out transform)) + { + AxisAlignedBoundingBox testBounds = itemToMoveBounds.NewTransformed(transform); - // don't check if the position is the same the one we just checked - if (distance != i - && CheckPosition(itemsToAvoid, itemToMove, itemToMoveBounds, distance, i, out transform)) - { - itemToMove.Matrix *= transform; - return; - } - } + if (bedBounds != null + && (distance + testBounds.MaxXYZ.X > bedBounds.Value.Width + || distance + testBounds.MaxXYZ.Y > bedBounds.Value.Height)) + { + return false; + } - distance++; - } - } - } + itemToMove.Matrix *= transform; + return true; + } - private static bool CheckPosition(IEnumerable itemsToAvoid, IObject3D itemToMove, AxisAlignedBoundingBox meshToMoveBounds, int yStep, int xStep, out Matrix4X4 transform) - { - double xStepAmount = 5; - double yStepAmount = 5; + // don't check if the position is the same as the one we just checked + if (distance != i + && CheckPosition(itemsToAvoid, itemToMove, itemToMoveBounds, distance, i, out transform)) + { + AxisAlignedBoundingBox testBounds = itemToMoveBounds.NewTransformed(transform); - var positionTransform = Matrix4X4.CreateTranslation(xStep * xStepAmount, yStep * yStepAmount, 0); - Vector3 newPosition = Vector3Ex.Transform(Vector3.Zero, positionTransform); + if (bedBounds != null + && (distance + testBounds.MaxXYZ.X > bedBounds.Value.Width + || distance + testBounds.MaxXYZ.Y > bedBounds.Value.Height)) + { + return false; + } - transform = Matrix4X4.CreateTranslation(newPosition); + itemToMove.Matrix *= transform; + return true; + } + } - AxisAlignedBoundingBox testBounds = meshToMoveBounds.NewTransformed(transform); + distance++; + } + } + } - foreach (IObject3D meshToTest in itemsToAvoid) - { - if (meshToTest != itemToMove) - { - AxisAlignedBoundingBox existingMeshBounds = meshToTest.GetAxisAlignedBoundingBox(); - var intersection = AxisAlignedBoundingBox.Intersection(testBounds, existingMeshBounds); - if (intersection.XSize > 0 && intersection.YSize > 0) - { - return false; - } - } - } + private static bool CheckPosition(IEnumerable itemsToAvoid, IObject3D itemToMove, AxisAlignedBoundingBox meshToMoveBounds, int yStep, int xStep, out Matrix4X4 transform) + { + double xStepAmount = 5; + double yStepAmount = 5; - return true; - } - } + var positionTransform = Matrix4X4.CreateTranslation(xStep * xStepAmount, yStep * yStepAmount, 0); + Vector3 newPosition = Vector3Ex.Transform(Vector3.Zero, positionTransform); + + transform = Matrix4X4.CreateTranslation(newPosition); + + AxisAlignedBoundingBox testBounds = meshToMoveBounds.NewTransformed(transform); + + foreach (IObject3D meshToTest in itemsToAvoid) + { + if (meshToTest != itemToMove) + { + AxisAlignedBoundingBox existingMeshBounds = meshToTest.GetAxisAlignedBoundingBox(); + var intersection = AxisAlignedBoundingBox.Intersection(testBounds, existingMeshBounds); + if (intersection.XSize > 0 && intersection.YSize > 0) + { + return false; + } + } + } + + return true; + } + } } diff --git a/MatterControlLib/PartPreviewWindow/View3D/SceneActions.cs b/MatterControlLib/PartPreviewWindow/View3D/SceneActions.cs index 9bc9bd083..3252b7dca 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/SceneActions.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/SceneActions.cs @@ -89,7 +89,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow transformData.Add(new TransformData() { TransformedObject = child, UndoTransform = child.Matrix }); } - PlatingHelper.ArrangeOnBed(children, bedCenter); + PlatingHelper.ArrangeOnBed(children, bedCenter, PlatingHelper.PositionType.Center); int i = 0; foreach (var child in children) { diff --git a/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs b/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs index 3b7536404..8cd3ebe09 100644 --- a/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs +++ b/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs @@ -837,7 +837,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // Compute slight highlight of openColor for use as listView background color var slightHighlight = theme.ResolveColor(openColor, Color.White.WithAlpha(theme.IsDarkTheme ? 10 : 50)); - var printLibraryWidget = new PrintLibraryWidget(mainViewWidget, workspace, theme, slightHighlight, libraryPopup) + var popupLibraryWidget = new PopupLibraryWidget(mainViewWidget, workspace, theme, slightHighlight, libraryPopup) { HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Absolute, @@ -847,10 +847,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow systemWindow.SizeChanged += (s, e) => { - printLibraryWidget.Height = libraryPopup.TransformToScreenSpace(libraryPopup.Position).Y; + popupLibraryWidget.Height = libraryPopup.TransformToScreenSpace(libraryPopup.Position).Y; }; - verticalResizeContainer.AddChild(printLibraryWidget); + verticalResizeContainer.AddChild(popupLibraryWidget); systemWindow.MouseDown += SystemWindownMouseDown; diff --git a/MatterControlLib/PartSheet/PartSheetPlugin.cs b/MatterControlLib/PartSheet/PartSheetPlugin.cs new file mode 100644 index 000000000..8c5d3d1a8 --- /dev/null +++ b/MatterControlLib/PartSheet/PartSheetPlugin.cs @@ -0,0 +1,76 @@ +/* +Copyright (c) 2023, Lars Brubaker, Kevin Pope, John Lewin +All rights reserved. +*/ + +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.Extensibility; +using MatterHackers.MatterControl.Library; +using MatterHackers.MatterControl.PrintQueue; +using System.Linq; + +namespace MatterHackers.MatterControl +{ + public class PartSheetPlugin : IApplicationPlugin + { + public PluginInfo MetaData { get; } = new PluginInfo() + { + Name = "Part Sheets", + UUID = "580D8EF3-885C-4DD3-903A-4DB136AFD84B", + About = "A part sheet plugin", + Developer = "MatterHackers, Inc.", + Url = "https://www.matterhackers.com" + }; + + public void Initialize() + { + // PDF export is limited to Windows + if (AggContext.OperatingSystem != OSType.Windows) + { + return; + } + + // Needed for PDFSharp on .NET core. + // https://stackoverflow.com/questions/50858209/system-notsupportedexception-no-data-is-available-for-encoding-1252 + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + + ApplicationController.Instance.Library.MenuExtensions.Add( + new LibraryAction(ActionScope.ListItem) + { + Title = "Create Part Sheet".Localize(), + Action = (selectedLibraryItems, listView) => + { + UiThread.RunOnIdle(() => + { + var printItems = selectedLibraryItems.OfType(); + if (printItems.Any()) + { + AggContext.FileDialogs.SaveFileDialog( + new SaveFileDialogParams("Save Parts Sheet|*.pdf") + { + ActionButtonLabel = "Save Parts Sheet".Localize(), + Title = ApplicationController.Instance.ProductName + " - " + "Save".Localize() + }, + (saveParams) => + { + if (!string.IsNullOrEmpty(saveParams.FileName)) + { + var currentPartsInQueue = new PartsSheet(printItems, saveParams.FileName); + currentPartsInQueue.SaveSheets().ConfigureAwait(false); + } + }); + } + }); + }, + IsEnabled = (selectedListItems, listView) => + { + // Multiselect - disallow containers + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !(i.Model is ILibraryContainerLink)); + } + }); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/PartSheet/PartsSheetCreator.cs b/MatterControlLib/PartSheet/PartsSheetCreator.cs new file mode 100644 index 000000000..8c549c1e6 --- /dev/null +++ b/MatterControlLib/PartSheet/PartsSheetCreator.cs @@ -0,0 +1,342 @@ +/* +Copyright (c) 2023, Lars Brubaker, Kevin Pope, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Agg; +using MatterHackers.Agg.Font; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.Library; +using MatterHackers.VectorMath; +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MatterHackers.MatterControl +{ + public class PartsSheet + { + private const double inchesPerMm = 0.0393701; + + private static bool currentlySaving = false; + + private List itemSource; + + private List partImagesToPrint = new List(); + + private string pathAndFileToSaveTo; + private bool openAfterSave; + + public PartsSheet(IEnumerable itemSource, string pathAndFileToSaveTo, bool openAfterSave = true) + { + this.pathAndFileToSaveTo = pathAndFileToSaveTo; + this.openAfterSave = openAfterSave; + SheetDpi = 300; + SheetSizeInches = new Vector2(8.5, 11); + + this.itemSource = itemSource.ToList(); + } + + public BorderDouble PageMarginMM { get; } = new BorderDouble(10, 25, 10, 5); + + public BorderDouble PageMarginPixels => PageMarginMM * PixelsPerMM; + + public double PartMarginMM { get; } = 2; + + public double PartMarginPixels => PartMarginMM * PixelsPerMM; + + public double PartPaddingMM { get; } = 2; + + public double PartPaddingPixels => PartPaddingMM * PixelsPerMM; + + public double PixelsPerMM => inchesPerMm * SheetDpi; + + public int SheetDpi { get; set; } + + public Vector2 SheetSizeInches + { + get { return SheetSizeMM * inchesPerMm; } + set { SheetSizeMM = value / inchesPerMm; } + } + + public Vector2 SheetSizeMM { get; set; } + + public Vector2 SheetSizePixels => SheetSizeMM * PixelsPerMM; + + public static bool IsSaving() + { + return currentlySaving; + } + + private async Task ExportTask(Action reporter, CancellationTokenSource cancellationToken) + { + var processCount = 0.0; + currentlySaving = true; + // first create images for all the parts + foreach (var item in itemSource) + { + reporter?.Invoke(0, item.Name); + + var xxx = itemSource.Count(); + var yyy = itemSource.FirstOrDefault()?.Name; + + var object3D = await item.CreateContent(); + + var loadedMeshGroups = object3D.VisibleMeshes().ToList(); + if (loadedMeshGroups?.Count > 0) + { + AxisAlignedBoundingBox aabb = loadedMeshGroups[0].Mesh.GetAxisAlignedBoundingBox(loadedMeshGroups[0].WorldMatrix()); + + for (int i = 1; i < loadedMeshGroups.Count; i++) + { + aabb = AxisAlignedBoundingBox.Union(aabb, loadedMeshGroups[i].Mesh.GetAxisAlignedBoundingBox(loadedMeshGroups[i].WorldMatrix())); + } + + RectangleDouble bounds2D = new RectangleDouble(aabb.MinXYZ.X, aabb.MinXYZ.Y, aabb.MaxXYZ.X, aabb.MaxXYZ.Y); + double widthInMM = bounds2D.Width + PartMarginMM * 2; + double textSpaceMM = 5; + double heightMM = textSpaceMM + bounds2D.Height + PartMarginMM * 2; + + TypeFacePrinter typeFacePrinter = new TypeFacePrinter(item.Name, 28, Vector2.Zero, Justification.Center, Baseline.BoundsCenter); + double sizeOfNameX = typeFacePrinter.GetSize().X + PartMarginPixels * 2; + Vector2 sizeOfRender = new Vector2(widthInMM * PixelsPerMM, heightMM * PixelsPerMM); + + ImageBuffer imageOfPart = new ImageBuffer((int)(Math.Max(sizeOfNameX, sizeOfRender.X)), (int)(sizeOfRender.Y)); + typeFacePrinter.Origin = new Vector2(imageOfPart.Width / 2, (textSpaceMM / 2) * PixelsPerMM); + + Graphics2D partGraphics2D = imageOfPart.NewGraphics2D(); + + RectangleDouble rectBounds = new RectangleDouble(0, 0, imageOfPart.Width, imageOfPart.Height); + double strokeWidth = .5 * PixelsPerMM; + rectBounds.Inflate(-strokeWidth / 2); + RoundedRect rect = new RoundedRect(rectBounds, PartMarginMM * PixelsPerMM); + partGraphics2D.Render(rect, Color.LightGray); + Stroke rectOutline = new Stroke(rect, strokeWidth); + partGraphics2D.Render(rectOutline, Color.DarkGray); + + foreach (var meshGroup in loadedMeshGroups) + { + PolygonMesh.Rendering.OrthographicZProjection.DrawTo(partGraphics2D, meshGroup.Mesh, meshGroup.WorldMatrix(), new Vector2(-bounds2D.Left + PartMarginMM, -bounds2D.Bottom + textSpaceMM + PartMarginMM), PixelsPerMM, Color.Black); + } + partGraphics2D.Render(typeFacePrinter, Color.Black); + + partImagesToPrint.Add(new PartImage(imageOfPart)); + } + + reporter?.Invoke(Math.Min(processCount / itemSource.Count, .95), null); + processCount++; + } + + reporter?.Invoke(0, "Saving".Localize()); + + partImagesToPrint.Sort(BiggestToLittlestImages); + + PdfDocument document = new PdfDocument(); + document.Info.Title = "MatterHackers Parts Sheet"; + document.Info.Author = "MatterHackers Inc."; + document.Info.Subject = "This is a list of the parts that are in a queue from MatterControl."; + document.Info.Keywords = "MatterControl, STL, 3D Printing"; + + int nextPartToPrintIndex = 0; + int plateNumber = 1; + + while (nextPartToPrintIndex < partImagesToPrint.Count) + { + PdfPage pdfPage = document.AddPage(); + CreateOnePage(plateNumber++, ref nextPartToPrintIndex, pdfPage); + } + + try + { + // save the final document + document.Save(pathAndFileToSaveTo); + + if (openAfterSave) + { + // Now try and open the document. This will launch whatever PDF viewer is on the system and ask it + // to show the file (at least on Windows). + ApplicationController.ProcessStart(pathAndFileToSaveTo); + } + } + catch (Exception) + { + } + + currentlySaving = false; + + reporter?.Invoke(1, null); + } + + public async Task SaveSheets(Action reporter = null) + { + if (reporter == null) + { + await ApplicationController.Instance.Tasks.Execute("Export Part Sheet".Localize(), null, ExportTask); + } + else + { + await ExportTask(reporter, new CancellationTokenSource()); + } + } + + private static int BiggestToLittlestImages(PartImage one, PartImage two) + { + return two.image.Height.CompareTo(one.image.Height); + } + + private void CreateOnePage(int plateNumber, ref int nextPartToPrintIndex, PdfPage pdfPage) + { + ImageBuffer plateInventoryImage = new ImageBuffer((int)(SheetSizePixels.X), (int)(SheetSizePixels.Y)); + Graphics2D plateGraphics = plateInventoryImage.NewGraphics2D(); + double currentlyPrintingHeightPixels = PrintTopOfPage(plateInventoryImage, plateGraphics); + + Vector2 offset = new Vector2(PageMarginPixels.Left, currentlyPrintingHeightPixels); + double tallestHeight = 0; + List partsOnLine = new List(); + while (nextPartToPrintIndex < partImagesToPrint.Count) + { + ImageBuffer image = partImagesToPrint[nextPartToPrintIndex].image; + tallestHeight = Math.Max(tallestHeight, image.Height); + + if (partsOnLine.Count > 0 && offset.X + image.Width > plateInventoryImage.Width - PageMarginPixels.Right) + { + if (partsOnLine.Count == 1) + { + plateGraphics.Render(partsOnLine[0].image, plateInventoryImage.Width / 2 - partsOnLine[0].image.Width / 2, offset.Y - tallestHeight); + } + else + { + foreach (PartImage partToDraw in partsOnLine) + { + plateGraphics.Render(partToDraw.image, partToDraw.xOffset, offset.Y - tallestHeight); + } + } + + offset.X = PageMarginPixels.Left; + offset.Y -= (tallestHeight + PartPaddingPixels * 2); + tallestHeight = 0; + partsOnLine.Clear(); + if (offset.Y - image.Height < PageMarginPixels.Bottom) + { + break; + } + } + else + { + partImagesToPrint[nextPartToPrintIndex].xOffset = offset.X; + partsOnLine.Add(partImagesToPrint[nextPartToPrintIndex]); + //plateGraphics.Render(image, offset.x, offset.y - image.Height); + offset.X += image.Width + PartPaddingPixels * 2; + nextPartToPrintIndex++; + } + } + + // print the last line of parts + foreach (PartImage partToDraw in partsOnLine) + { + plateGraphics.Render(partToDraw.image, partToDraw.xOffset, offset.Y - tallestHeight); + } + + TypeFacePrinter printer = new TypeFacePrinter(string.Format("{0}", Path.GetFileNameWithoutExtension(pathAndFileToSaveTo)), 32, justification: Justification.Center); + printer.Origin = new Vector2(plateGraphics.DestImage.Width / 2, 110); + plateGraphics.Render(printer, Color.Black); + + printer = new TypeFacePrinter(string.Format("Page {0}", plateNumber), 28, justification: Justification.Center); + printer.Origin = new Vector2(plateGraphics.DestImage.Width / 2, 60); + plateGraphics.Render(printer, Color.Black); + + MemoryStream jpegStream = new MemoryStream(); + ImageIO.SaveImageData(jpegStream, ".jpeg", plateInventoryImage); + + XGraphics gfx = XGraphics.FromPdfPage(pdfPage); + jpegStream.Seek(0, SeekOrigin.Begin); + XImage jpegImage = XImage.FromStream(jpegStream); + //double width = jpegImage.PixelWidth * 72 / jpegImage.HorizontalResolution; + //double height = jpegImage.PixelHeight * 72 / jpegImage. .HorizontalResolution; + + gfx.DrawImage(jpegImage, 0, 0, pdfPage.Width, pdfPage.Height); + } + + private double PrintTopOfPage(ImageBuffer plateInventoryImage, Graphics2D plateGraphics) + { + plateGraphics.Clear(Color.White); + + double currentlyPrintingHeightPixels = plateInventoryImage.Height - PageMarginPixels.Top; + + string logoPathAndFile = Path.Combine("Images", "PartSheetLogo.png"); + if (StaticData.Instance.FileExists(logoPathAndFile)) + { + ImageBuffer logoImage = StaticData.Instance.LoadImage(logoPathAndFile); + currentlyPrintingHeightPixels -= logoImage.Height; + plateGraphics.Render(logoImage, (plateInventoryImage.Width - logoImage.Width) / 2, currentlyPrintingHeightPixels); + } + + currentlyPrintingHeightPixels -= PartPaddingPixels; + + double underlineHeightMM = 1; + + var lineBounds = new RectangleDouble(0, 0, plateInventoryImage.Width - PageMarginPixels.Left * 2, underlineHeightMM * PixelsPerMM); + lineBounds.Offset(PageMarginPixels.Left, currentlyPrintingHeightPixels - lineBounds.Height); + plateGraphics.FillRectangle(lineBounds, Color.Black); + + return currentlyPrintingHeightPixels - (lineBounds.Height + PartPaddingPixels); + } + + public class FileNameAndPresentationName + { + public string fileName; + public string presentationName; + + public FileNameAndPresentationName(string fileName, string presentationName) + { + this.fileName = fileName; + this.presentationName = presentationName; + } + } + + internal class PartImage + { + internal ImageBuffer image; + internal bool wasDrawn = false; + internal double xOffset = 0; + + public PartImage(ImageBuffer imageOfPart) + { + this.image = imageOfPart; + } + } + } +} \ No newline at end of file