Improving arange all
adding part sheet to main improving folder sorting and naming, fixing path rendering
This commit is contained in:
parent
0416a941d7
commit
6492f25d1d
19 changed files with 819 additions and 246 deletions
|
|
@ -70,7 +70,6 @@
|
|||
<ProjectReference Include="MatterControl.SLA\MatterControl.SLA.csproj" />
|
||||
<ProjectReference Include="MatterControl.Winforms\MatterControl.Winforms.csproj" />
|
||||
<ProjectReference Include="MatterControlLib\MatterControlLib.csproj" />
|
||||
<ProjectReference Include="Plugins\MatterControl.PartSheet\MatterControl.PartSheet.csproj" />
|
||||
<ProjectReference Include="PrinterDriverInstaller\InfInstaller.csproj" />
|
||||
<ProjectReference Include="Submodules\agg-sharp\agg\Agg.csproj" />
|
||||
<ProjectReference Include="Submodules\agg-sharp\Glfw\GlfwProvider.csproj" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DirectoryPathAttribute>().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ILibraryContainerLink>();
|
||||
this.Name = containerName;
|
||||
this.Account = account;
|
||||
this.Repository = repositor;
|
||||
this.Repository = repository;
|
||||
this.RepoDirectory = repoDirectory;
|
||||
|
||||
// Initialize a default CollectionData with a "Loading..." entry
|
||||
|
|
|
|||
|
|
@ -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<ImageBuffer> GetThumbnail(ILibraryItem item, int width, int height)
|
||||
public Task<ImageBuffer> GetThumbnail(ILibraryItem item, int width, int height)
|
||||
{
|
||||
return Task.FromResult<ImageBuffer>(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs
Normal file
80
MatterControlLib/Library/Widgets/TextEditWithInlineCancel.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<System.Collections.Generic.List
|
|||
|
||||
namespace MatterHackers.MatterControl
|
||||
{
|
||||
public static class PlatingHelper
|
||||
{
|
||||
public static VertexStorage PolygonToPathStorage(this Polygons polygons)
|
||||
{
|
||||
var output = new VertexStorage();
|
||||
public static class PlatingHelper
|
||||
{
|
||||
public static VertexStorage PolygonToPathStorage(this Polygons polygons)
|
||||
{
|
||||
var output = new VertexStorage();
|
||||
|
||||
foreach (Polygon polygon in polygons)
|
||||
{
|
||||
bool first = true;
|
||||
foreach (IntPoint point in polygon)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
output.Add(point.X, point.Y, ShapePath.FlagsAndCommand.MoveTo);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Add(point.X, point.Y, ShapePath.FlagsAndCommand.LineTo);
|
||||
}
|
||||
}
|
||||
foreach (Polygon polygon in polygons)
|
||||
{
|
||||
bool first = true;
|
||||
foreach (IntPoint point in polygon)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
output.Add(point.X, point.Y, ShapePath.FlagsAndCommand.MoveTo);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Add(point.X, point.Y, ShapePath.FlagsAndCommand.LineTo);
|
||||
}
|
||||
}
|
||||
|
||||
output.ClosePolygon();
|
||||
}
|
||||
output.ClosePolygon();
|
||||
}
|
||||
|
||||
output.Add(0, 0, ShapePath.FlagsAndCommand.Stop);
|
||||
output.Add(0, 0, ShapePath.FlagsAndCommand.Stop);
|
||||
|
||||
return output;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void ArrangeOnBed(List<IObject3D> 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);
|
||||
}
|
||||
/// <summary>
|
||||
/// Arrange the given parts on the bed and return a list of the parts that were arranged
|
||||
/// </summary>
|
||||
/// <param name="object3DList">The parts to arrange</param>
|
||||
/// <param name="arangePosition">A position to arrange around</param>
|
||||
/// <param name="positionType">The way to consider the possition</param>
|
||||
/// <param name="bedBounds">Optional bounds to arrange into</param>
|
||||
/// <param name="progressReporter">The current progress of arranging</param>
|
||||
/// <returns>A list of the parts that were arranged</returns>
|
||||
public static List<IObject3D> ArrangeOnBed(List<IObject3D> object3DList,
|
||||
Vector3 arangePosition,
|
||||
PositionType positionType,
|
||||
RectangleDouble? bedBounds = null,
|
||||
Action<double, string> progressReporter = null)
|
||||
{
|
||||
if (object3DList.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// sort them by size
|
||||
object3DList.Sort(SortOnBigToLittle);
|
||||
var objectsThatWereArrange = new List<IObject3D>();
|
||||
var objectsThatHaveBeenPlaced = new List<IObject3D>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the target object to the first non-colliding position, starting from the lower left corner of the bounding box containing all sceneItems
|
||||
/// </summary>
|
||||
/// <param name="objectToAdd">The object to position</param>
|
||||
/// <param name="itemsToAvoid">The objects to hit test against</param>
|
||||
public static void MoveToOpenPositionRelativeGroup(IObject3D objectToAdd, IEnumerable<IObject3D> 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);
|
||||
/// <summary>
|
||||
/// Moves the target object to the first non-colliding position, starting from the lower left corner of the bounding box containing all sceneItems
|
||||
/// </summary>
|
||||
/// <param name="objectToAdd">The object to position</param>
|
||||
/// <param name="itemsToAvoid">The objects to hit test against</param>
|
||||
public static bool MoveToOpenPositionRelativeGroup(IObject3D objectToAdd, IEnumerable<IObject3D> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the target object to the first non-colliding position, starting at the initial position of the target object
|
||||
/// </summary>
|
||||
/// <param name="itemToMove">The object to position</param>
|
||||
/// <param name="itemsToAvoid">The objects to hit test against</param>
|
||||
public static void MoveToOpenPosition(IObject3D itemToMove, IEnumerable<IObject3D> 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);
|
||||
/// <summary>
|
||||
/// Moves the target object to the first non-colliding position, starting at the initial position of the target object
|
||||
/// </summary>
|
||||
/// <param name="itemToMove">The object to position</param>
|
||||
/// <param name="itemsToAvoid">The objects to hit test against</param>
|
||||
public static bool MoveToOpenPosition(IObject3D itemToMove, IEnumerable<IObject3D> 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<IObject3D> 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<IObject3D> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
76
MatterControlLib/PartSheet/PartSheetPlugin.cs
Normal file
76
MatterControlLib/PartSheet/PartSheetPlugin.cs
Normal file
|
|
@ -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<ILibraryAssetStream>();
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
342
MatterControlLib/PartSheet/PartsSheetCreator.cs
Normal file
342
MatterControlLib/PartSheet/PartsSheetCreator.cs
Normal file
|
|
@ -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<ILibraryAssetStream> itemSource;
|
||||
|
||||
private List<PartImage> partImagesToPrint = new List<PartImage>();
|
||||
|
||||
private string pathAndFileToSaveTo;
|
||||
private bool openAfterSave;
|
||||
|
||||
public PartsSheet(IEnumerable<ILibraryAssetStream> 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<double, string> 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<double, string> 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<PartImage> partsOnLine = new List<PartImage>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue