Improving arange all

adding part sheet to main
improving folder sorting and naming, fixing path rendering
This commit is contained in:
MatterHackers 2023-03-11 11:26:16 -08:00
parent 0416a941d7
commit 6492f25d1d
19 changed files with 819 additions and 246 deletions

View file

@ -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" />

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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; }
}

View file

@ -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

View file

@ -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);
}

View file

@ -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
{

View file

@ -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
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)
{

View file

@ -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;

View 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));
}
});
}
}
}

View 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;
}
}
}
}