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