diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index 98c3b55a4..4a0efc5da 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -300,42 +300,98 @@ namespace MatterHackers.MatterControl private List registeredSceneOperations = new List() { + new SceneSelectionOperation() { - () => "Make Support".Localize(), - (scene) => + TitleResolver = () => "Group".Localize(), + Action = (scene) => scene.GroupSelection(), + IsEnabled = (scene) => scene.HasSelection + && scene.SelectedItem is SelectionGroup + && scene.SelectedItem.Children.Count > 1, + Icon = AggContext.StaticData.LoadIcon("group.png").SetPreMultiply(), + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Ungroup".Localize(), + Action = (scene) => scene.UngroupSelection(), + IsEnabled = (scene) => scene.HasSelection, + Icon = AggContext.StaticData.LoadIcon("ungroup.png").SetPreMultiply(), + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Duplicate".Localize(), + Action = (scene) => scene.DuplicateSelection(), + IsEnabled = (scene) => scene.HasSelection, + Icon = AggContext.StaticData.LoadIcon("duplicate.png").SetPreMultiply(), + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Remove".Localize(), + Action = (scene) => scene.DeleteSelection(), + IsEnabled = (scene) => scene.HasSelection, + Icon = AggContext.StaticData.LoadIcon("remove.png").SetPreMultiply(), + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Lay Flat".Localize(), + Action = (scene) => + { + if (scene.HasSelection) + { + scene.MakeLowestFaceFlat(scene.SelectedItem, scene.RootItem); + } + }, + IsEnabled = (scene) => scene.HasSelection, + Icon = AggContext.StaticData.LoadIcon("lay_flat.png").SetPreMultiply(), + }, + + new SceneSelectionOperation() + { + TitleResolver = () => "Subtract".Localize(), + Action = (scene) => DoOpperation(scene, nameof(SubtractEditor), "Subtract"), + Icon = AggContext.StaticData.LoadIcon("subtract.png").SetPreMultiply(), + + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Intersect".Localize(), + Action = (scene) => DoOpperation(scene, nameof(IntersectionEditor), "Intersect"), + Icon = AggContext.StaticData.LoadIcon("intersect.png") + }, +#if DEBUG // keep this work in progress to the editor for now + new SceneSelectionOperation() + { + TitleResolver = () => "Subtract & Replace".Localize(), + Action = (scene) => DoOpperation(scene, nameof(PaintMaterialEditor), "Subtract & Replace"), + Icon = AggContext.StaticData.LoadIcon("paint.png").SetPreMultiply(), + }, + new SceneSelectionOperation() + { + TitleResolver = () => "Make Support".Localize(), + Action = (scene) => { if (scene.SelectedItem != null && scene.SelectedItem.OutputType != PrintOutputTypes.Support) { scene.UndoBuffer.AddAndDo(new MakeSupport(scene.SelectedItem)); } - } + }, + Icon = AggContext.StaticData.LoadIcon("support.png").SetPreMultiply(), }, + new SceneSelectionOperation() { - () => "Subtract".Localize(), - (scene) => DoOpperation(scene, nameof(SubtractEditor), "Subtract") + TitleResolver = () => "Bend".Localize(), + Action = (scene) => new BendOperation(scene.SelectedItem), }, + new SceneSelectionOperation() { - () => "Intersect".Localize(), - (scene) => DoOpperation(scene, nameof(IntersectionEditor), "Intersect") - }, -#if DEBUG // keep this work in progress to the editor for now - { - () => "Paint Material".Localize(), - (scene) => DoOpperation(scene, nameof(PaintMaterialEditor), "Material Paint") - }, - { - () => "Bend".Localize(), - (scene) => new BendOperation(scene.SelectedItem) - }, - { - () => "Cut Out".Localize(), - (scene) => Console.WriteLine("Cut out") + TitleResolver = () => "Cut Out".Localize(), + Action = (scene) => Console.WriteLine("Cut out") }, + new SceneSelectionOperation() { // Should be a pinch command that makes a pinch object with the correct controls - () => "Pinch".Localize(), - (scene) => scene.UndoBuffer.AddAndDo(new GroupCommand(scene, scene.SelectedItem)) + TitleResolver = () => "Pinch".Localize(), + Action = (scene) => scene.UndoBuffer.AddAndDo(new GroupCommand(scene, scene.SelectedItem)) } #endif }; @@ -1105,10 +1161,7 @@ namespace MatterHackers.MatterControl return Enumerable.Empty(); } - public IEnumerable RegisteredSceneOperations() - { - return registeredSceneOperations; - } + public IEnumerable RegisteredSceneOperations => registeredSceneOperations; public event EventHandler AddPrintersTabRightElement; diff --git a/ApplicationView/PrinterModels.cs b/ApplicationView/PrinterModels.cs index 2252e98b0..6f74ab478 100644 --- a/ApplicationView/PrinterModels.cs +++ b/ApplicationView/PrinterModels.cs @@ -623,14 +623,15 @@ namespace MatterHackers.MatterControl { if (double.TryParse(UserSettings.Instance.get(UserSettingsKey.SelectedObjectPanelWidth), out double controlWidth)) { - return controlWidth; + return Math.Max(controlWidth, 150); } return 200; } set { - UserSettings.Instance.set(UserSettingsKey.SelectedObjectPanelWidth, value.ToString()); + var minimumValue = Math.Max(value, 150); + UserSettings.Instance.set(UserSettingsKey.SelectedObjectPanelWidth, minimumValue.ToString()); } } } diff --git a/CustomWidgets/NamedAction.cs b/CustomWidgets/NamedAction.cs index 61e44de2f..433d85adf 100644 --- a/CustomWidgets/NamedAction.cs +++ b/CustomWidgets/NamedAction.cs @@ -32,7 +32,6 @@ using System; using System.Collections.Generic; using MatterHackers.Agg.Image; using MatterHackers.DataConverters3D; -using MatterHackers.MeshVisualizer; namespace MatterHackers.Agg.UI { @@ -54,11 +53,13 @@ namespace MatterHackers.Agg.UI { public Func TitleResolver { get; set; } public string Title => this.TitleResolver?.Invoke(); + public ImageBuffer Icon { get; set; } } public class SceneSelectionOperation : LocalizedAction { public Action Action { get; set; } + public Func IsEnabled { get; set; } } public static class NamedActionExtensions diff --git a/Library/ExtensionMethods.cs b/Library/ExtensionMethods.cs index 736bd29d7..313fe194e 100644 --- a/Library/ExtensionMethods.cs +++ b/Library/ExtensionMethods.cs @@ -34,7 +34,6 @@ using MatterHackers.Agg.Image; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.ImageProcessing; -using MatterHackers.MatterControl.PrintQueue; namespace MatterHackers.MatterControl.Library { @@ -64,13 +63,20 @@ namespace MatterHackers.MatterControl.Library var contentProvider = ApplicationController.Instance.Library.GetContentProvider(item) as ISceneContentProvider; return contentProvider?.CreateItem(item, reporter); } - + // Color ExtensionMethods public static ImageBuffer MultiplyWithPrimaryAccent(this ImageBuffer sourceImage) { return sourceImage.Multiply(ActiveTheme.Instance.PrimaryAccentColor); } + public static ImageBuffer SetPreMultiply(this ImageBuffer sourceImage) + { + sourceImage.SetRecieveBlender(new BlenderPreMultBGRA()); + + return sourceImage; + } + public static ImageBuffer AlphaToPrimaryAccent(this ImageBuffer sourceImage) { return sourceImage.AnyAlphaToColor(ActiveTheme.Instance.PrimaryAccentColor); diff --git a/PartPreviewWindow/SelectedObjectPanel.cs b/PartPreviewWindow/SelectedObjectPanel.cs index 6f58dfb70..055ac28b5 100644 --- a/PartPreviewWindow/SelectedObjectPanel.cs +++ b/PartPreviewWindow/SelectedObjectPanel.cs @@ -30,7 +30,6 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Threading.Tasks; using MatterHackers.Agg; @@ -38,10 +37,8 @@ using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; using MatterHackers.MatterControl.CustomWidgets; -using MatterHackers.MatterControl.DataStorage; using MatterHackers.MatterControl.Library; using MatterHackers.MeshVisualizer; -using MatterHackers.PolygonMesh; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.PartPreviewWindow @@ -50,17 +47,17 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { private IObject3D item = new Object3D(); - private FlowLayoutWidget editorPanel; + private FlowLayoutWidget scrollableContent; private TextWidget itemName; private ThemeConfig theme; private View3DWidget view3DWidget; private InteractiveScene scene; private PrinterConfig printer; - private List sceneActions; private Dictionary> objectEditorsByType; private ObservableCollection materialButtons = new ObservableCollection(); private SectionWidget editorSection; private TextButton editButton; + private GuiWidget editorPanel; public SelectedObjectPanel(View3DWidget view3DWidget, InteractiveScene scene, ThemeConfig theme, PrinterConfig printer) : base(FlowDirection.TopToBottom) @@ -68,68 +65,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.HAnchor = HAnchor.Stretch; this.VAnchor = VAnchor.Top | VAnchor.Fit; this.Padding = 0; // new BorderDouble(8, 10); - //this.MinimumSize = new VectorMath.Vector2(220, 0); + //this.MinimumSize = new VectorMath.Vector2(220, 0); this.view3DWidget = view3DWidget; this.theme = theme; this.scene = scene; this.printer = printer; - sceneActions = new List - { - new NamedAction() - { - Title = "Ungroup".Localize(), - Action = () => - { - scene.UngroupSelection(); - }, - IsEnabled = () => scene.HasSelection - }, - new NamedAction() - { - Title = "Group".Localize(), - Action = () => - { - scene.GroupSelection(); - }, - IsEnabled = () => scene.HasSelection - && scene.SelectedItem is SelectionGroup - && scene.SelectedItem.Children.Count > 1 - }, - - new NamedAction() - { - Title = "Lay Flat".Localize(), - Action = () => - { - if (scene.HasSelection) - { - MakeLowestFaceFlat(scene.SelectedItem, scene.RootItem); - } - }, - IsEnabled = () => scene.HasSelection - }, - new NamedAction() - { - Title = "Duplicate".Localize(), - Action = () => - { - scene.DuplicateSelection(); - }, - IsEnabled = () => scene.HasSelection - }, - new NamedAction() - { - Title = "Remove".Localize(), - Action = () => - { - scene.DeleteSelection(); - }, - IsEnabled = () => scene.HasSelection - } - }; - var firstPanel = new FlowLayoutWidget(FlowDirection.TopToBottom) { Padding = 10, @@ -145,54 +87,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow Margin = new BorderDouble(bottom: 10) }); - var behavior3DTypeButtons = new FlowLayoutWidget(); - firstPanel.AddChild(behavior3DTypeButtons); - - var buttonMargin = new BorderDouble(2, 5); - - // put in the button for making the behavior solid - var solidButtonView = new TextButton("Color".Localize(), theme) - { - BackgroundColor = theme.MinimalShade - }; - var solidBehaviorButton = new PopupButton(solidButtonView) - { - Name = "Solid Colors", - AlignToRightEdge = true, - PopupContent = new ColorSwatchSelector(scene) - { - HAnchor = HAnchor.Fit, - VAnchor = VAnchor.Fit, - }, - }; - - behavior3DTypeButtons.AddChild(solidBehaviorButton); - - editButton = new TextButton("Edit".Localize(), theme) - { - BackgroundColor = theme.MinimalShade, - Margin = theme.ButtonSpacing - }; - editButton.Click += async (s, e) => - { - BedConfig bed; - - var partPreviewContent = this.Parents().FirstOrDefault(); - partPreviewContent.CreatePartTab( - "New Part", - bed = new BedConfig(), - theme); - - await bed.LoadContent( - new EditContext() - { - ContentStore = ApplicationController.Instance.Library.PlatingHistory, - SourceItem = new InMemoryItem(this.item), - }); - }; - behavior3DTypeButtons.AddChild(editButton); - - editorPanel = new FlowLayoutWidget(FlowDirection.TopToBottom) + scrollableContent = new FlowLayoutWidget(FlowDirection.TopToBottom) { HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Fit, @@ -205,40 +100,43 @@ namespace MatterHackers.MatterControl.PartPreviewWindow VAnchor = VAnchor.Stretch, }; - scrollable.AddChild(editorPanel); + scrollable.AddChild(scrollableContent); scrollable.ScrollArea.HAnchor = HAnchor.Stretch; this.AddChild(scrollable); // Add heading separator - editorPanel.AddChild(new HorizontalLine(25) + scrollableContent.AddChild(new HorizontalLine(25) { Margin = new BorderDouble(0) }); var operationsContainer = new FlowLeftRightWithWrapping(); - foreach (var sceneAction in sceneActions) + foreach (var namedAction in ApplicationController.Instance.RegisteredSceneOperations) { - var button = new TextButton(sceneAction.Title, theme) - { - Name = sceneAction.Title + " Button", - Margin = theme.ButtonSpacing, - BackgroundColor = theme.MinimalShade - }; - button.Click += (s, e) => sceneAction.Action.Invoke(); + GuiWidget button; - operationsContainer.AddChild(button); - } - - foreach (var namedAction in ApplicationController.Instance.RegisteredSceneOperations()) - { - var button = new TextButton(namedAction.Title, theme) + if (namedAction.Icon != null) { - Name = namedAction.Title + " Button", - Margin = theme.ButtonSpacing, - BackgroundColor = theme.MinimalShade - }; + button = new IconButton(namedAction.Icon, theme) + { + Name = namedAction.Title + " Button", + ToolTipText = namedAction.Title, + Margin = theme.ButtonSpacing, + BackgroundColor = theme.MinimalShade + }; + } + else + { + button = new TextButton(namedAction.Title, theme) + { + Name = namedAction.Title + " Button", + Margin = theme.ButtonSpacing, + BackgroundColor = theme.MinimalShade + }; + } + button.Click += (s, e) => { namedAction.Action.Invoke(scene); @@ -246,43 +144,101 @@ namespace MatterHackers.MatterControl.PartPreviewWindow operationsContainer.AddChild(button); } + var editorColumn = new FlowLayoutWidget(FlowDirection.TopToBottom) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit + }; + var toolbar = new Toolbar() + { + Padding = theme.ToolbarPadding, + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit + }; + editorColumn.AddChild(toolbar); + // put in the button for making the behavior solid + var solidBehaviorButton = new PopupButton(new TextButton("Color".Localize(), theme)) + { + Name = "Solid Colors", + AlignToRightEdge = true, + PopupContent = new ColorSwatchSelector(scene) + { + HAnchor = HAnchor.Fit, + VAnchor = VAnchor.Fit, + }, + Margin = theme.ButtonSpacing.Clone(left: 0), + BackgroundColor = theme.MinimalShade + }; + toolbar.AddChild(solidBehaviorButton); - var operationsSection = new SectionWidget("Operations".Localize(), ActiveTheme.Instance.PrimaryTextColor, operationsContainer); - editorPanel.AddChild(operationsSection); + editButton = new TextButton("Edit".Localize(), theme) + { + BackgroundColor = theme.MinimalShade, + Margin = theme.ButtonSpacing + }; + editButton.Click += async (s, e) => + { + var bed = new BedConfig(); - editorSection = new SectionWidget("Editor", ActiveTheme.Instance.PrimaryTextColor, new GuiWidget()); - editorPanel.AddChild(editorSection); + var partPreviewContent = this.Parents().FirstOrDefault(); + partPreviewContent.CreatePartTab( + "New Part", + bed, + theme); + + await bed.LoadContent( + new EditContext() + { + ContentStore = ApplicationController.Instance.Library.PlatingHistory, + SourceItem = new InMemoryItem(this.item), + }); + }; + toolbar.AddChild(editButton); + + // Add container used to host the current specialized editor for the selection + editorColumn.AddChild(editorPanel = new GuiWidget() + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit, + Padding = 6 + }); + + editorSection = new SectionWidget("Editor", ActiveTheme.Instance.PrimaryTextColor, editorColumn); + scrollableContent.AddChild(editorSection); // TODO: Implements - //alignButton.Enabled = this.scene.HasSelection - // && this.scene.SelectedItem is SelectionGroup - // && this.scene.SelectedItem.Children.Count > 1; + //alignButton.Enabled = this.scene.HasSelection + // && this.scene.SelectedItem is SelectionGroup + // && this.scene.SelectedItem.Children.Count > 1; + + var operationsSection = new SectionWidget("Operations".Localize(), ActiveTheme.Instance.PrimaryTextColor, operationsContainer); + scrollableContent.AddChild(operationsSection); var alignSection = new SectionWidget("Align".Localize(), ActiveTheme.Instance.PrimaryTextColor, this.AddAlignControls(), expanded: false) { Name = "Align Panel", }; - editorPanel.AddChild(alignSection); + scrollableContent.AddChild(alignSection); var mirrorSection = new SectionWidget("Mirror".Localize(), ActiveTheme.Instance.PrimaryTextColor, new MirrorControls(scene), expanded: false) { Name = "Mirror Panel", }; - editorPanel.AddChild(mirrorSection); + scrollableContent.AddChild(mirrorSection); var scaleSection = new SectionWidget("Scale".Localize(), ActiveTheme.Instance.PrimaryTextColor, new ScaleControls(scene, ActiveTheme.Instance.PrimaryTextColor), expanded: false) { Name = "Scale Panel", }; - editorPanel.AddChild(scaleSection); + scrollableContent.AddChild(scaleSection); var materialsSection = new SectionWidget("Materials".Localize(), ActiveTheme.Instance.PrimaryTextColor, this.AddMaterialControls(), expanded: false) { Name = "Materials Panel", }; - editorPanel.AddChild(materialsSection); + scrollableContent.AddChild(materialsSection); HashSet mappedEditors; objectEditorsByType = new Dictionary>(); @@ -348,7 +304,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } } - if (mappedEditors != null) + if (mappedEditors == null) + { + editorPanel.CloseAllChildren(); + editorPanel.Invalidate(); + } + else { //var dropDownList = new DropDownList("", ActiveTheme.Instance.PrimaryTextColor, maxHeight: 300) //{ @@ -415,6 +376,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private void ShowObjectEditor(IObject3DEditor editor) { + editorPanel.CloseAllChildren(); + if (editor == null) { return; @@ -424,7 +387,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow editorWidget.HAnchor = HAnchor.Stretch; editorWidget.VAnchor = VAnchor.Fit; - editorSection.SetContentWidget(editorWidget); + editorPanel.AddChild(editorWidget); } public void Save(ILibraryItem item, IObject3D content) @@ -495,104 +458,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow return widget; } - private void MakeLowestFaceFlat(IObject3D objectToLayFlatGroup, IObject3D root) - { - bool firstVertex = true; - - IObject3D objectToLayFlat = objectToLayFlatGroup; - - IVertex lowestVertex = null; - Vector3 lowestVertexPosition = Vector3.Zero; - IObject3D itemToLayFlat = null; - - // Process each child, checking for the lowest vertex - var objectsToCheck = objectToLayFlat.VisibleMeshes(); - foreach (var itemToCheck in objectsToCheck) - { - // find the lowest point on the model - for (int testIndex = 0; testIndex < itemToCheck.Mesh.Vertices.Count; testIndex++) - { - var vertex = itemToCheck.Mesh.Vertices[testIndex]; - Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToCheck.WorldMatrix(root)); - if (firstVertex) - { - lowestVertex = itemToCheck.Mesh.Vertices[testIndex]; - lowestVertexPosition = vertexPosition; - itemToLayFlat = itemToCheck; - firstVertex = false; - } - else if (vertexPosition.Z < lowestVertexPosition.Z) - { - lowestVertex = itemToCheck.Mesh.Vertices[testIndex]; - lowestVertexPosition = vertexPosition; - itemToLayFlat = itemToCheck; - } - } - } - - if (lowestVertex == null) - { - // didn't find any selected mesh - return; - } - - Face faceToLayFlat = null; - double lowestAngleOfAnyFace = double.MaxValue; - // Check all the faces that are connected to the lowest point to find out which one to lay flat. - foreach (Face face in lowestVertex.ConnectedFaces()) - { - double biggestAngleToFaceVertex = double.MinValue; - foreach (IVertex faceVertex in face.Vertices()) - { - if (faceVertex != lowestVertex) - { - Vector3 faceVertexPosition = Vector3.Transform(faceVertex.Position, itemToLayFlat.WorldMatrix(root)); - Vector3 pointRelLowest = faceVertexPosition - lowestVertexPosition; - double xLeg = new Vector2(pointRelLowest.X, pointRelLowest.Y).Length; - double yLeg = pointRelLowest.Z; - double angle = Math.Atan2(yLeg, xLeg); - if (angle > biggestAngleToFaceVertex) - { - biggestAngleToFaceVertex = angle; - } - } - } - if (biggestAngleToFaceVertex < lowestAngleOfAnyFace) - { - lowestAngleOfAnyFace = biggestAngleToFaceVertex; - faceToLayFlat = face; - } - } - - double maxDistFromLowestZ = 0; - List faceVertices = new List(); - foreach (IVertex vertex in faceToLayFlat.Vertices()) - { - Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToLayFlat.WorldMatrix(root)); - faceVertices.Add(vertexPosition); - maxDistFromLowestZ = Math.Max(maxDistFromLowestZ, vertexPosition.Z - lowestVertexPosition.Z); - } - - if (maxDistFromLowestZ > .001) - { - Vector3 xPositive = (faceVertices[1] - faceVertices[0]).GetNormal(); - Vector3 yPositive = (faceVertices[2] - faceVertices[0]).GetNormal(); - Vector3 planeNormal = Vector3.Cross(xPositive, yPositive).GetNormal(); - - // this code takes the minimum rotation required and looks much better. - Quaternion rotation = new Quaternion(planeNormal, new Vector3(0, 0, -1)); - Matrix4X4 partLevelMatrix = Matrix4X4.CreateRotation(rotation); - - // rotate it - objectToLayFlatGroup.Matrix = PlatingHelper.ApplyAtCenter(objectToLayFlatGroup, partLevelMatrix); - - scene.Invalidate(); - Invalidate(); - } - - PlatingHelper.PlaceOnBed(objectToLayFlatGroup); - } - private List GetTransforms(int axisIndex, AxisAlignment alignment) { var transformDatas = new List(); diff --git a/PartPreviewWindow/View3D/Actions/PaintMaterialEditor.cs b/PartPreviewWindow/View3D/Actions/PaintMaterialEditor.cs index ab46176c0..439377276 100644 --- a/PartPreviewWindow/View3D/Actions/PaintMaterialEditor.cs +++ b/PartPreviewWindow/View3D/Actions/PaintMaterialEditor.cs @@ -30,7 +30,6 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using MatterHackers.Agg; using MatterHackers.Agg.UI; @@ -46,7 +45,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D { private MeshWrapperOperation group; private View3DWidget view3DWidget; - public string Name => "Paint Material"; + public string Name => "Subtract & Replace"; public bool Unlocked { get; } = true; diff --git a/PartPreviewWindow/View3D/SceneActions.cs b/PartPreviewWindow/View3D/SceneActions.cs index 6a50c5571..639b0ecf7 100644 --- a/PartPreviewWindow/View3D/SceneActions.cs +++ b/PartPreviewWindow/View3D/SceneActions.cs @@ -27,6 +27,7 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -34,6 +35,7 @@ using System.Threading.Tasks; using MatterHackers.Agg; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; +using MatterHackers.PolygonMesh; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.PartPreviewWindow @@ -212,6 +214,102 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } } + public static void MakeLowestFaceFlat(this InteractiveScene scene, IObject3D objectToLayFlatGroup, IObject3D root) + { + bool firstVertex = true; + + IObject3D objectToLayFlat = objectToLayFlatGroup; + + IVertex lowestVertex = null; + Vector3 lowestVertexPosition = Vector3.Zero; + IObject3D itemToLayFlat = null; + + // Process each child, checking for the lowest vertex + foreach (var itemToCheck in objectToLayFlat.VisibleMeshes()) + { + // find the lowest point on the model + for (int testIndex = 0; testIndex < itemToCheck.Mesh.Vertices.Count; testIndex++) + { + var vertex = itemToCheck.Mesh.Vertices[testIndex]; + Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToCheck.WorldMatrix(root)); + if (firstVertex) + { + lowestVertex = itemToCheck.Mesh.Vertices[testIndex]; + lowestVertexPosition = vertexPosition; + itemToLayFlat = itemToCheck; + firstVertex = false; + } + else if (vertexPosition.Z < lowestVertexPosition.Z) + { + lowestVertex = itemToCheck.Mesh.Vertices[testIndex]; + lowestVertexPosition = vertexPosition; + itemToLayFlat = itemToCheck; + } + } + } + + if (lowestVertex == null) + { + // didn't find any selected mesh + return; + } + + Face faceToLayFlat = null; + double lowestAngleOfAnyFace = double.MaxValue; + // Check all the faces that are connected to the lowest point to find out which one to lay flat. + foreach (Face face in lowestVertex.ConnectedFaces()) + { + double biggestAngleToFaceVertex = double.MinValue; + foreach (IVertex faceVertex in face.Vertices()) + { + if (faceVertex != lowestVertex) + { + Vector3 faceVertexPosition = Vector3.Transform(faceVertex.Position, itemToLayFlat.WorldMatrix(root)); + Vector3 pointRelLowest = faceVertexPosition - lowestVertexPosition; + double xLeg = new Vector2(pointRelLowest.X, pointRelLowest.Y).Length; + double yLeg = pointRelLowest.Z; + double angle = Math.Atan2(yLeg, xLeg); + if (angle > biggestAngleToFaceVertex) + { + biggestAngleToFaceVertex = angle; + } + } + } + if (biggestAngleToFaceVertex < lowestAngleOfAnyFace) + { + lowestAngleOfAnyFace = biggestAngleToFaceVertex; + faceToLayFlat = face; + } + } + + double maxDistFromLowestZ = 0; + List faceVertices = new List(); + foreach (IVertex vertex in faceToLayFlat.Vertices()) + { + Vector3 vertexPosition = Vector3.Transform(vertex.Position, itemToLayFlat.WorldMatrix(root)); + faceVertices.Add(vertexPosition); + maxDistFromLowestZ = Math.Max(maxDistFromLowestZ, vertexPosition.Z - lowestVertexPosition.Z); + } + + if (maxDistFromLowestZ > .001) + { + Vector3 xPositive = (faceVertices[1] - faceVertices[0]).GetNormal(); + Vector3 yPositive = (faceVertices[2] - faceVertices[0]).GetNormal(); + Vector3 planeNormal = Vector3.Cross(xPositive, yPositive).GetNormal(); + + // this code takes the minimum rotation required and looks much better. + Quaternion rotation = new Quaternion(planeNormal, new Vector3(0, 0, -1)); + Matrix4X4 partLevelMatrix = Matrix4X4.CreateRotation(rotation); + + // rotate it + objectToLayFlatGroup.Matrix = PlatingHelper.ApplyAtCenter(objectToLayFlatGroup, partLevelMatrix); + + scene.Invalidate(); + } + + PlatingHelper.PlaceOnBed(objectToLayFlatGroup); + } + internal class ArangeUndoCommand : IUndoRedoCommand { private List allUndoTransforms = new List(); diff --git a/PartPreviewWindow/ViewControls3D.cs b/PartPreviewWindow/ViewControls3D.cs index 9581d1ff3..7bb119914 100644 --- a/PartPreviewWindow/ViewControls3D.cs +++ b/PartPreviewWindow/ViewControls3D.cs @@ -317,30 +317,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow buttonGroupB.Add(layers2DButton); this.AddChild(layers2DButton); - - Button addButton = theme.SmallMarginButtonFactory.Generate("Insert".Localize(), AggContext.StaticData.LoadIcon("cube.png", 14, 14, IconColor.Theme)); - addButton.Margin = 0; - addButton.Click += (sender, e) => - { - UiThread.RunOnIdle(() => - { - AggContext.FileDialogs.OpenFileDialog( - new OpenFileDialogParams(ApplicationSettings.OpenDesignFileParams, multiSelect: true), - (openParams) => - { - this.LoadAndAddPartsToPlate(openParams.FileNames, sceneContext.Scene); - }); - }); - }; - this.AddChild(addButton); - - var buttonSpacing = theme.ButtonSpacing; - var buttonView = new FlowLayoutWidget(); buttonView.AddChild(new ImageWidget(AggContext.StaticData.LoadIcon((IsPrinterMode) ? "bed.png" : "cube.png", IconColor.Theme)) { - Margin = new BorderDouble(left: 10), - VAnchor = VAnchor.Center + VAnchor = VAnchor.Center, + Margin = theme.ButtonSpacing, }); var buttonText = (IsPrinterMode) ? "Bed".Localize() : "Part".Localize(); @@ -497,6 +478,23 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // Bed menu return new[] { + new NamedAction() + { + Title = "Insert".Localize(), + Icon = AggContext.StaticData.LoadIcon("cube.png", 16, 16, IconColor.Raw), + Action = () => + { + UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.OpenFileDialog( + new OpenFileDialogParams(ApplicationSettings.OpenDesignFileParams, multiSelect: true), + (openParams) => + { + this.LoadAndAddPartsToPlate(openParams.FileNames, sceneContext.Scene); + }); + }); + } + }, new NamedAction() { Title = "Save".Localize(), diff --git a/StaticData/Icons/duplicate.png b/StaticData/Icons/duplicate.png new file mode 100644 index 000000000..a8cadee79 Binary files /dev/null and b/StaticData/Icons/duplicate.png differ diff --git a/StaticData/Icons/group.png b/StaticData/Icons/group.png new file mode 100644 index 000000000..0c15bdf9b Binary files /dev/null and b/StaticData/Icons/group.png differ diff --git a/StaticData/Icons/intersect.png b/StaticData/Icons/intersect.png new file mode 100644 index 000000000..bc468f6ed Binary files /dev/null and b/StaticData/Icons/intersect.png differ diff --git a/StaticData/Icons/lay_flat.png b/StaticData/Icons/lay_flat.png new file mode 100644 index 000000000..7ede537c5 Binary files /dev/null and b/StaticData/Icons/lay_flat.png differ diff --git a/StaticData/Icons/paint.png b/StaticData/Icons/paint.png new file mode 100644 index 000000000..e9adbc1f0 Binary files /dev/null and b/StaticData/Icons/paint.png differ diff --git a/StaticData/Icons/remove.png b/StaticData/Icons/remove.png new file mode 100644 index 000000000..89b486293 Binary files /dev/null and b/StaticData/Icons/remove.png differ diff --git a/StaticData/Icons/subtract.png b/StaticData/Icons/subtract.png new file mode 100644 index 000000000..d8eeb5e37 Binary files /dev/null and b/StaticData/Icons/subtract.png differ diff --git a/StaticData/Icons/support.png b/StaticData/Icons/support.png new file mode 100644 index 000000000..e5ddec4c5 Binary files /dev/null and b/StaticData/Icons/support.png differ diff --git a/StaticData/Icons/ungroup.png b/StaticData/Icons/ungroup.png new file mode 100644 index 000000000..3464c7f2f Binary files /dev/null and b/StaticData/Icons/ungroup.png differ