mattercontrol/ApplicationView/ApplicationController.cs

2051 lines
62 KiB
C#
Raw Normal View History

/*
Copyright (c) 2017, 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 System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DataStorage;
using MatterHackers.MatterControl.PrinterCommunication;
using MatterHackers.MatterControl.PrintQueue;
using MatterHackers.MatterControl.SlicerConfiguration;
using MatterHackers.MatterControl.DesignTools.Operations;
using Newtonsoft.Json;
using System.Collections.ObjectModel;
namespace MatterHackers.MatterControl
{
2018-04-04 10:11:57 -07:00
using System.ComponentModel;
using System.IO.Compression;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using Agg.Font;
using Agg.Image;
using CustomWidgets;
using MatterHackers.Agg.Platform;
using MatterHackers.DataConverters3D;
using MatterHackers.DataConverters3D.UndoCommands;
using MatterHackers.MatterControl.ConfigurationPage.PrintLeveling;
using MatterHackers.MatterControl.DesignTools;
2018-01-29 13:50:55 -08:00
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.MatterControl.Library;
using MatterHackers.MatterControl.PartPreviewWindow;
using MatterHackers.MatterControl.PartPreviewWindow.View3D;
using MatterHackers.MatterControl.PrinterControls.PrinterConnections;
2018-02-26 17:48:15 -08:00
using MatterHackers.PolygonMesh;
using MatterHackers.RenderOpenGl;
using MatterHackers.SerialPortCommunication;
using MatterHackers.VectorMath;
using SettingsManagement;
public class AppContext
{
/// <summary>
/// Native platform features
/// </summary>
public static INativePlatformFeatures Platform { get; set; }
public static bool IsLoading { get; internal set; } = true;
/// <summary>
/// The root SystemWindow
/// </summary>
public static SystemWindow RootSystemWindow { get; internal set; }
}
public class ApplicationController
{
private Dictionary<Type, HashSet<IObject3DEditor>> objectEditorsByType;
public ThemeConfig Theme { get; set; } = new ThemeConfig();
public RunningTasksConfig Tasks { get; set; } = new RunningTasksConfig();
// A list of printers which are open (i.e. displaying a tab) on this instance of MatterControl
public IEnumerable<PrinterConfig> ActivePrinters { get; } = new List<PrinterConfig>();
private static PrinterConfig emptyPrinter = new PrinterConfig(PrinterSettings.Empty);
private static string cacheDirectory = Path.Combine(ApplicationDataStorage.ApplicationUserDataPath, "data", "temp", "cache");
// TODO: Any references to this property almost certainly need to be reconsidered. ActiveSliceSettings static references that assume a single printer
// selection are being redirected here. This allows us to break the dependency to the original statics and consolidates
// us down to a single point where code is making assumptions about the presence of a printer, printer counts, etc. If we previously checked for
// PrinterConnection.IsPrinterConnected, that could should be updated to iterate ActiverPrinters, checking each one and acting on each as it would
// have for the single case
public PrinterConfig ActivePrinter { get; private set; } = emptyPrinter;
public Action RedeemDesignCode;
public Action EnterShareCode;
private static ApplicationController globalInstance;
public RootedObjectEventHandler CloudSyncStatusChanged = new RootedObjectEventHandler();
public RootedObjectEventHandler DoneReloadingAll = new RootedObjectEventHandler();
public static Action SignInAction;
public static Action SignOutAction;
public static Action WebRequestFailed;
public static Action WebRequestSucceeded;
#if DEBUG
public const string EnvironmentName = "TestEnv_";
#else
public const string EnvironmentName = "";
#endif
public bool ApplicationExiting { get; internal set; } = false;
public static Func<string, Task<Dictionary<string, string>>> GetProfileHistory;
private readonly static object thumbsLock = new object();
private Queue<Func<Task>> queuedThumbCallbacks = new Queue<Func<Task>>();
public async Task SetActivePrinter(PrinterConfig printer, bool allowChangedEvent = true)
{
var initialPrinter = this.ActivePrinter;
if (initialPrinter?.Settings.ID != printer.Settings.ID)
{
// TODO: Consider if autosave is appropriate
if (initialPrinter != emptyPrinter)
{
await initialPrinter.Bed.SaveChanges(null, CancellationToken.None);
}
// If we have an active printer, run Disable
if (initialPrinter.Settings != PrinterSettings.Empty)
{
initialPrinter?.Connection?.Disable();
}
// ActivePrinters is IEnumerable to force us to use SetActivePrinter until it's ingrained in our pattern - cast to list since it is and we need to add
(this.ActivePrinters as List<PrinterConfig>).Add(printer);
this.ActivePrinter = printer;
// TODO: Decide if non-printer contexts should prompt for a printer, if we should have a default printer, or get "ActiveTab printer" working
// HACK: short term solution to resolve printer reference for non-printer related contexts
DragDropData.Printer = printer;
if (!AppContext.IsLoading)
{
// Fire printer changed event
}
BedSettings.SetMakeAndModel(
printer.Settings.GetValue(SettingsKey.make),
printer.Settings.GetValue(SettingsKey.model));
ActiveSliceSettings.SwitchToPrinterTheme();
if (allowChangedEvent)
{
ActiveSliceSettings.OnActivePrinterChanged(null);
}
if (!AppContext.IsLoading
&& printer.Settings.PrinterSelected
&& printer.Settings.GetValue<bool>(SettingsKey.auto_connect))
{
UiThread.RunOnIdle(() =>
{
printer.Settings.printer.Connection.Connect();
}, 2);
}
}
}
internal async Task ClearActivePrinter()
{
await this.SetActivePrinter(emptyPrinter);
}
public void LaunchBrowser(string targetUri)
{
UiThread.RunOnIdle(() =>
{
if (!string.IsNullOrEmpty(OemSettings.Instance.AffiliateCode)
&& targetUri.Contains("matterhackers.com"))
{
if (targetUri.Contains("?"))
{
targetUri += $"&aff={OemSettings.Instance.AffiliateCode}";
}
else
{
targetUri += $"?aff={OemSettings.Instance.AffiliateCode}";
}
}
Process.Start(targetUri);
});
}
public void RefreshActiveInstance(PrinterSettings updatedPrinterSettings)
{
ActivePrinter.SwapToSettings(updatedPrinterSettings);
/*
// TODO: Should we rebroadcast settings changed events for each settings?
bool themeChanged = ActivePrinter.Settings.GetValue(SettingsKey.active_theme_name) != updatedProfile.GetValue(SettingsKey.active_theme_name);
ActiveSliceSettings.SettingChanged.CallEvents(null, new StringEventArgs(SettingsKey.printer_name));
2017-09-23 09:19:32 -07:00
// TODO: Decide if non-printer contexts should prompt for a printer, if we should have a default printer, or get "ActiveTab printer" working
// HACK: short term solution to resolve printer reference for non-printer related contexts
DragDropData.Printer = printer;
if (themeChanged)
{
UiThread.RunOnIdle(ActiveSliceSettings.SwitchToPrinterTheme);
}
else
{
UiThread.RunOnIdle(ApplicationController.Instance.ReloadAdvancedControlsPanel);
}*/
}
private AutoResetEvent thumbGenResetEvent = new AutoResetEvent(false);
Task thumbnailGenerator = null;
internal void QueueForGeneration(Func<Task> func)
{
lock (thumbsLock)
{
if (thumbnailGenerator == null)
{
// Spin up a new thread once needed
thumbnailGenerator = Task.Run((Action)ThumbGeneration);
}
queuedThumbCallbacks.Enqueue(func);
thumbGenResetEvent.Set();
}
}
private async void ThumbGeneration()
{
Thread.CurrentThread.Name = $"ThumbnailGeneration";
while (!this.ApplicationExiting)
{
Thread.Sleep(100);
try
{
if (queuedThumbCallbacks.Count > 0)
{
Func<Task> callback;
lock (thumbsLock)
{
callback = queuedThumbCallbacks.Dequeue();
}
await callback();
}
else
{
// Process until queuedThumbCallbacks is empty then wait for new tasks via QueueForGeneration
thumbGenResetEvent.WaitOne();
}
}
catch (AppDomainUnloadedException)
{
return;
}
catch (ThreadAbortException)
{
2017-11-21 07:05:16 -08:00
return;
}
catch (Exception ex)
{
Console.WriteLine("Error generating thumbnail: " + ex.Message);
}
}
// Null task reference on exit
thumbnailGenerator = null;
}
public static Func<PrinterInfo, string, Task<PrinterSettings>> GetPrinterProfileAsync;
public static Func<string, IProgress<ProgressStatus>, Task> SyncPrinterProfiles;
public static Func<Task<OemProfileDictionary>> GetPublicProfileList;
public static Func<string, Task<PrinterSettings>> DownloadPublicProfileAsync;
public SlicePresetsWindow EditMaterialPresetsWindow { get; set; }
public SlicePresetsWindow EditQualityPresetsWindow { get; set; }
public GuiWidget MainView;
private EventHandler unregisterEvents;
private Dictionary<string, List<PrintItemAction>> registeredLibraryActions = new Dictionary<string, List<PrintItemAction>>();
2017-09-18 17:57:06 -07:00
private List<SceneSelectionOperation> registeredSceneOperations = new List<SceneSelectionOperation>()
{
new SceneSelectionOperation()
2017-10-13 15:16:14 -07:00
{
TitleResolver = () => "Group".Localize(),
2018-03-28 17:20:47 -07:00
Action = (scene) =>
{
var selectedItem = scene.SelectedItem;
scene.SelectedItem = null;
var newGroup = new Object3D()
{
Name = "Group".Localize()
};
// When grouping items, move them to be centered on their bounding box
newGroup.Children.Modify((gChildren) =>
{
selectedItem.Clone().Children.Modify((sChildren) =>
{
var center = selectedItem.GetAxisAlignedBoundingBox().Center;
foreach (var child in sChildren)
{
child.Translate(-center.X, -center.Y, 0);
gChildren.Add(child);
}
newGroup.Translate(center.X, center.Y, 0);
});
});
scene.UndoBuffer.AddAndDo(new ReplaceCommand(selectedItem.Children.ToList(), new List<IObject3D> { newGroup }));
newGroup.MakeNameNonColliding();
scene.SelectedItem = newGroup;
},
IsEnabled = (scene) => scene.HasSelection
&& scene.SelectedItem is SelectionGroup
&& scene.SelectedItem.Children.Count > 1,
Icon = AggContext.StaticData.LoadIcon("group.png", 16, 16).SetPreMultiply(),
},
new SceneSelectionOperation()
{
TitleResolver = () => "Ungroup".Localize(),
Action = (scene) => scene.UngroupSelection(),
IsEnabled = (scene) => scene.HasSelection,
Icon = AggContext.StaticData.LoadIcon("ungroup.png", 16, 16).SetPreMultiply(),
},
2018-01-18 07:33:12 -08:00
new SceneSelectionSeparator(),
new SceneSelectionOperation()
{
TitleResolver = () => "Duplicate".Localize(),
Action = (scene) => scene.DuplicateItem(),
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(),
},
2018-01-18 07:33:12 -08:00
new SceneSelectionSeparator(),
new SceneSelectionOperation()
{
TitleResolver = () => "Align".Localize(),
Action = (scene) =>
{
scene.AddSelectionAsChildren(new Align3D());
if(scene.SelectedItem is Align3D arange)
{
arange.Rebuild(null);
}
},
Icon = AggContext.StaticData.LoadIcon("align_left.png").SetPreMultiply(),
IsEnabled = (scene) => scene.SelectedItem is SelectionGroup,
},
new SceneSelectionOperation()
{
TitleResolver = () => "Lay Flat".Localize(),
Action = (scene) =>
{
if (scene.HasSelection)
{
scene.MakeLowestFaceFlat(scene.SelectedItem);
}
},
IsEnabled = (scene) => scene.HasSelection,
Icon = AggContext.StaticData.LoadIcon("lay_flat.png").SetPreMultiply(),
},
2018-01-18 07:33:12 -08:00
new SceneSelectionOperation()
{
TitleResolver = () => "Make Support".Localize(),
Action = (scene) =>
{
if (scene.SelectedItem != null
&& !scene.SelectedItem.VisibleMeshes().All(i => i.object3D.OutputType == PrintOutputTypes.Support))
2018-01-18 07:33:12 -08:00
{
scene.UndoBuffer.AddAndDo(new MakeSupport(scene.SelectedItem));
}
},
Icon = AggContext.StaticData.LoadIcon("support.png").SetPreMultiply(),
IsEnabled = (scene) => scene.HasSelection,
},
new SceneSelectionSeparator(),
new SceneSelectionOperation()
{
TitleResolver = () => "Combine".Localize(),
2018-03-15 10:03:48 -07:00
Action = (scene) => MeshWrapperObject3D.WrapSelection(new CombineObject3D(), scene),
Icon = AggContext.StaticData.LoadIcon("combine.png").SetPreMultiply(),
IsEnabled = (scene) => scene.SelectedItem is SelectionGroup,
},
new SceneSelectionOperation()
{
TitleResolver = () => "Subtract".Localize(),
2018-03-15 10:03:48 -07:00
Action = (scene) => MeshWrapperObject3D.WrapSelection(new SubtractObject3D(), scene),
Icon = AggContext.StaticData.LoadIcon("subtract.png").SetPreMultiply(),
IsEnabled = (scene) => scene.SelectedItem is SelectionGroup,
},
new SceneSelectionOperation()
2017-10-18 13:27:57 -07:00
{
TitleResolver = () => "Intersect".Localize(),
2018-03-15 10:03:48 -07:00
Action = (scene) => MeshWrapperObject3D.WrapSelection(new IntersectionObject3D(), scene),
Icon = AggContext.StaticData.LoadIcon("intersect.png"),
IsEnabled = (scene) => scene.SelectedItem is SelectionGroup,
2017-10-18 13:27:57 -07:00
},
new SceneSelectionOperation()
{
2018-01-09 18:40:02 -08:00
TitleResolver = () => "Subtract & Replace".Localize(),
2018-03-15 10:03:48 -07:00
Action = (scene) => MeshWrapperObject3D.WrapSelection(new SubtractAndReplaceObject3D(), scene),
2018-02-07 15:57:27 -08:00
Icon = AggContext.StaticData.LoadIcon("subtract_and_replace.png").SetPreMultiply(),
IsEnabled = (scene) => scene.SelectedItem is SelectionGroup,
},
2018-01-10 11:38:28 -08:00
new SceneSelectionSeparator(),
new SceneSelectionOperation()
{
TitleResolver = () => "Linear Array".Localize(),
Action = (scene) =>
{
scene.AddSelectionAsChildren(new ArrayLinear3D());
if(scene.SelectedItem is ArrayLinear3D array)
2018-02-07 15:57:27 -08:00
{
array.Rebuild(null);
2018-02-07 15:57:27 -08:00
}
},
Icon = AggContext.StaticData.LoadIcon("array_linear.png").SetPreMultiply(),
2018-02-07 15:57:27 -08:00
IsEnabled = (scene) => scene.HasSelection && !(scene.SelectedItem is SelectionGroup),
},
new SceneSelectionOperation()
{
TitleResolver = () => "Radial Array".Localize(),
Action = (scene) =>
{
scene.AddSelectionAsChildren(new ArrayRadial3D());
if(scene.SelectedItem is ArrayRadial3D array)
{
array.Rebuild(null);
}
},
Icon = AggContext.StaticData.LoadIcon("array_radial.png").SetPreMultiply(),
IsEnabled = (scene) => scene.HasSelection && !(scene.SelectedItem is SelectionGroup),
},
new SceneSelectionOperation()
{
TitleResolver = () => "Advanced Array".Localize(),
Action = (scene) =>
{
scene.AddSelectionAsChildren(new ArrayAdvanced3D());
if(scene.SelectedItem is ArrayAdvanced3D array)
{
array.Rebuild(null);
}
},
Icon = AggContext.StaticData.LoadIcon("array_advanced.png").SetPreMultiply(),
IsEnabled = (scene) => scene.HasSelection && !(scene.SelectedItem is SelectionGroup),
},
2018-04-01 16:06:31 -07:00
new SceneSelectionSeparator(),
new SceneSelectionOperation()
{
TitleResolver = () => "Pinch".Localize(),
Action = (scene) =>
{
var pinch = new PinchObject3D();
MeshWrapperObject3D.WrapSelection(pinch, scene);
},
Icon = AggContext.StaticData.LoadIcon("pinch.png", 16, 16),
IsEnabled = (scene) => scene.HasSelection,
},
new SceneSelectionOperation()
2018-04-02 14:15:27 -07:00
{
TitleResolver = () => "Curve".Localize(),
Action = (scene) =>
{
var curve = new CurveObject3D();
MeshWrapperObject3D.WrapSelection(curve, scene);
},
Icon = AggContext.StaticData.LoadIcon("curve.png", 16, 16),
IsEnabled = (scene) => scene.HasSelection,
},
new SceneSelectionOperation()
{
TitleResolver = () => "Fit to Bounds".Localize(),
Action = (scene) =>
{
var selectedItem = scene.SelectedItem;
scene.SelectedItem = null;
var fit = FitToBounds3D.Create(selectedItem.Clone());
fit.MakeNameNonColliding();
scene.UndoBuffer.AddAndDo(new ReplaceCommand(new List<IObject3D> { selectedItem }, new List<IObject3D> { fit }));
scene.SelectedItem = fit;
},
//Icon = AggContext.StaticData.LoadIcon("array_linear.png").SetPreMultiply(),
IsEnabled = (scene) => scene.HasSelection && !(scene.SelectedItem is SelectionGroup),
},
#if DEBUG // keep this work in progress to the editor for now
2018-02-11 13:52:19 -08:00
new SceneSelectionSeparator(),
2018-01-31 22:53:27 -08:00
new SceneSelectionOperation()
{
2018-02-11 13:52:19 -08:00
TitleResolver = () => "Package".Localize(),
2018-01-31 22:53:27 -08:00
Action = (scene) =>
{
var selectedItem = scene.SelectedItem;
scene.SelectedItem = null;
var package = Package3D.Create(selectedItem.Clone());
package.MakeNameNonColliding();
scene.UndoBuffer.AddAndDo(new ReplaceCommand(new List<IObject3D> { selectedItem }, new List<IObject3D> { package }));
scene.SelectedItem = package;
2018-01-31 22:53:27 -08:00
},
2018-02-07 15:57:27 -08:00
IsEnabled = (scene) => scene.HasSelection,
},
new SceneSelectionOperation()
2017-09-18 17:57:06 -07:00
{
TitleResolver = () => "Bend".Localize(),
Action = (scene) => new BendObject3D(scene.SelectedItem),
IsEnabled = (scene) => scene.HasSelection,
2017-09-18 17:57:06 -07:00
},
#endif
2017-09-18 17:57:06 -07:00
};
static int applicationInstanceCount = 0;
public static int ApplicationInstanceCount
{
get
{
if (applicationInstanceCount == 0)
{
Assembly mcAssembly = Assembly.GetEntryAssembly();
if (mcAssembly != null)
{
string applicationName = Path.GetFileNameWithoutExtension(mcAssembly.Location).ToUpper();
Process[] p1 = Process.GetProcesses();
foreach (System.Diagnostics.Process pro in p1)
{
try
{
if (pro?.ProcessName != null
&& pro.ProcessName.ToUpper().Contains(applicationName))
{
applicationInstanceCount++;
}
}
catch
{
}
}
}
}
return applicationInstanceCount;
}
}
public event EventHandler ShowHelpChanged;
public LibraryConfig Library { get; }
public GraphConfig Graph { get; }
private void InitializeLibrary()
{
if (Directory.Exists(ApplicationDataStorage.Instance.DownloadsDirectory))
{
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Downloads".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "download_folder.png")),
() => new FileSystemContainer(ApplicationDataStorage.Instance.DownloadsDirectory)
{
UseIncrementedNameDuringTypeChange = true
}));
}
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Calibration Parts".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "folder.png")),
() => new CalibrationPartsContainer())
{
IsReadOnly = true
});
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Print Queue".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "queue_folder.png")),
() => new PrintQueueContainer()));
var rootLibraryCollection = Datastore.Instance.dbSQLite.Table<PrintItemCollection>().Where(v => v.Name == "_library").Take(1).FirstOrDefault();
if (rootLibraryCollection != null)
{
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Local Library".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "library_folder.png")),
() => new SqliteLibraryContainer(rootLibraryCollection.Id)));
}
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Print History".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "folder.png")),
() => new PrintHistoryContainer())
{
IsReadOnly = true
});
if (File.Exists(ApplicationDataStorage.Instance.CustomLibraryFoldersPath))
{
// Add each path defined in the CustomLibraryFolders file as a new FileSystemContainerItem
foreach (string directory in File.ReadLines(ApplicationDataStorage.Instance.CustomLibraryFoldersPath))
{
2017-12-15 14:55:10 -08:00
//if (Directory.Exists(directory))
{
this.Library.RegisterContainer(
new FileSystemContainer.DirectoryContainerLink(directory)
{
UseIncrementedNameDuringTypeChange = true
});
}
}
}
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "SD Card".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "sd_folder.png")),
() => new SDCardContainer(),
() =>
{
var printer = this.ActivePrinter;
return printer.Settings.GetValue<bool>(SettingsKey.has_sd_card_reader);
})
{
IsReadOnly = true
});
this.Library.PlatingHistory = new PlatingHistoryContainer();
this.Library.RegisterContainer(
new DynamicContainerLink(
() => "Plating History".Localize(),
2017-12-15 14:55:10 -08:00
AggContext.StaticData.LoadIcon(Path.Combine("FileDialog", "folder.png")),
() => ApplicationController.Instance.Library.PlatingHistory));
}
public ApplicationController()
{
Object3D.AssetsPath = Path.Combine(ApplicationDataStorage.Instance.ApplicationLibraryDataPath, "Assets");
2017-11-10 23:03:48 -08:00
ScrollBar.DefaultMargin = new BorderDouble(right: 1);
ScrollBar.ScrollBarWidth = 8 * GuiWidget.DeviceScale;
ScrollBar.GrowThumbBy = 2;
// Initialize statics
2017-10-31 11:43:25 -07:00
DefaultThumbBackground.DefaultBackgroundColor = Color.Transparent;
Object3D.AssetsPath = ApplicationDataStorage.Instance.LibraryAssetsPath;
this.Library = new LibraryConfig();
this.Graph = new GraphConfig();
this.Library.ContentProviders.Add(new[] { "stl", "obj", "amf", "mcx" }, new MeshContentProvider());
this.Library.ContentProviders.Add("gcode", new GCodeContentProvider());
// Name = "MainSlidePanel";
ActiveTheme.ThemeChanged.RegisterEvent((s, e) =>
{
if (!AppContext.IsLoading)
{
ReloadAll();
}
}, ref unregisterEvents);
ActiveSliceSettings.SettingChanged.RegisterEvent((s, e) =>
{
if (e is StringEventArgs stringArg
&& SettingsOrganizer.SettingsData.TryGetValue(stringArg.Data, out SliceSettingData settingsData)
&& settingsData.ReloadUiWhenChanged)
{
UiThread.RunOnIdle(ReloadAll);
}
}, ref unregisterEvents);
PrinterConnection.HeatTurningOffSoon.RegisterEvent((s, e) =>
{
var printerConnection = ApplicationController.Instance.ActivePrinter.Connection;
if (printerConnection.AnyHeatIsOn)
{
Tasks.Execute("Disable Heaters".Localize(), (reporter, cancellationToken) =>
{
var progressStatus = new ProgressStatus();
while (printerConnection.SecondsUntilTurnOffHeaters > 0
&& !cancellationToken.IsCancellationRequested
&& printerConnection.ContinuWaitingToTurnOffHeaters)
{
reporter.Report(progressStatus);
progressStatus.Status = "Turn Off Heat in".Localize() + " " + printerConnection.SecondsUntilTurnOffHeaters.ToString("0");
Thread.Sleep(100);
}
if (!cancellationToken.IsCancellationRequested
&& printerConnection.ContinuWaitingToTurnOffHeaters)
{
printerConnection.TurnOffBedAndExtruders(TurnOff.Now);
}
if (cancellationToken.IsCancellationRequested)
{
printerConnection.ContinuWaitingToTurnOffHeaters = false;
}
return Task.CompletedTask;
});
}
}, ref unregisterEvents);
2017-10-10 16:40:59 -07:00
PrinterConnection.ErrorReported.RegisterEvent((s, e) =>
{
var foundStringEventArgs = e as FoundStringEventArgs;
if (foundStringEventArgs != null)
{
string message = "Your printer is reporting a hardware Error. This may prevent your printer from functioning properly.".Localize()
+ "\n"
+ "\n"
+ "Error Reported".Localize() + ":"
+ $" \"{foundStringEventArgs.LineToCheck}\".";
UiThread.RunOnIdle(() =>
StyledMessageBox.ShowMessageBox(message, "Printer Hardware Error".Localize())
2017-10-10 16:40:59 -07:00
);
}
}, ref unregisterEvent);
this.InitializeLibrary();
2017-09-17 21:08:16 -07:00
PrinterConnection.AnyConnectionSucceeded.RegisterEvent((s, e) =>
{
// run the print leveling wizard if we need to for this printer
2017-09-17 21:08:16 -07:00
var printer = ApplicationController.Instance.ActivePrinters.Where(p => p.Connection == s).FirstOrDefault();
if (printer != null)
{
if (PrintLevelingData.NeedsToBeRun(printer))
{
2017-09-17 21:08:16 -07:00
UiThread.RunOnIdle(() => LevelWizardBase.ShowPrintLevelWizard(printer));
}
}
}, ref unregisterEvents);
HashSet<IObject3DEditor> mappedEditors;
objectEditorsByType = new Dictionary<Type, HashSet<IObject3DEditor>>();
foreach (IObject3DEditor editor in PluginFinder.CreateInstancesOf<IObject3DEditor>())
{
foreach (Type type in editor.SupportedTypes())
{
if (!objectEditorsByType.TryGetValue(type, out mappedEditors))
{
mappedEditors = new HashSet<IObject3DEditor>();
objectEditorsByType.Add(type, mappedEditors);
}
mappedEditors.Add(editor);
}
}
}
public HashSet<IObject3DEditor> GetEditorsForType(Type selectedItemType)
{
HashSet<IObject3DEditor> mappedEditors;
objectEditorsByType.TryGetValue(selectedItemType, out mappedEditors);
if (mappedEditors == null)
{
foreach (var kvp in objectEditorsByType)
{
var editorType = kvp.Key;
if (editorType.IsAssignableFrom(selectedItemType))
{
mappedEditors = kvp.Value;
break;
}
}
}
return mappedEditors;
}
internal void Shutdown()
{
// Ensure all threads shutdown gracefully on close
// Release any waiting generator threads
thumbGenResetEvent?.Set();
// Kill all long running tasks (this will release the silcing thread if running)
foreach(var task in Tasks.RunningTasks)
{
task.CancelTask();
}
}
private static TypeFace monoSpacedTypeFace = null;
public static TypeFace MonoSpacedTypeFace
{
get
{
if (monoSpacedTypeFace == null)
{
monoSpacedTypeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "LiberationMono.svg")));
}
return monoSpacedTypeFace;
}
}
2018-01-28 07:53:23 -08:00
private static TypeFace titilliumTypeFace = null;
public static TypeFace TitilliumTypeFace
{
get
{
if (titilliumTypeFace == null)
{
titilliumTypeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "TitilliumWeb-Black.svg")));
}
return titilliumTypeFace;
}
}
private static TypeFace damionTypeFace = null;
public static TypeFace DamionTypeFace
{
get
{
if (damionTypeFace == null)
{
damionTypeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "Damion-Regular.svg")));
}
return damionTypeFace;
}
}
public static Task<T> LoadCacheableAsync<T>(string cacheKey, string cacheScope, string staticDataFallbackPath = null) where T : class
{
string cachePath = CacheablePath(cacheScope, cacheKey);
try
{
if (File.Exists(cachePath))
{
// Load from cache and deserialize
return Task.FromResult(
JsonConvert.DeserializeObject<T>(File.ReadAllText(cachePath)));
}
}
catch
{
// Fall back to StaticData
}
try
{
if (staticDataFallbackPath != null
&& AggContext.StaticData.FileExists(staticDataFallbackPath))
{
return Task.FromResult(
JsonConvert.DeserializeObject<T>(AggContext.StaticData.ReadAllText(staticDataFallbackPath)));
}
}
catch
{
}
return Task.FromResult(default(T));
}
/// <summary>
/// Requests fresh content from online services, falling back to cached content if offline
/// </summary>
/// <param name="collector">The custom collector function to load the content</param>
/// <returns></returns>
public async static Task<T> LoadCacheableAsync<T>(string cacheKey, string cacheScope, Func<Task<T>> collector, string staticDataFallbackPath = null) where T : class
{
string cachePath = CacheablePath(cacheScope, cacheKey);
try
{
// Try to update the document
T item = await collector();
if (item != null)
{
// update cache on success
File.WriteAllText(cachePath, JsonConvert.SerializeObject(item, Formatting.Indented));
return item;
}
}
catch
{
// Fall back to preexisting cache if failed
}
return await LoadCacheableAsync<T>(cacheKey, cacheScope, staticDataFallbackPath);
}
public static string CacheablePath(string cacheScope, string cacheKey)
{
string scopeDirectory = Path.Combine(cacheDirectory, cacheScope);
// Ensure directory exists
Directory.CreateDirectory(scopeDirectory);
2017-12-28 16:45:34 -08:00
return Path.Combine(scopeDirectory, cacheKey);
}
// Indicates if given file can be opened on the design surface
public bool IsLoadableFile(string filePath)
{
string extension = Path.GetExtension(filePath).ToLower();
string extensionWithoutPeriod = extension.Trim('.');
return !string.IsNullOrEmpty(extension)
&& (ApplicationSettings.OpenDesignFileParams.Contains(extension)
2017-11-07 12:06:43 -08:00
|| this.Library.ContentProviders.Keys.Contains(extensionWithoutPeriod));
}
public bool IsReloading { get; private set; } = false;
2017-11-07 13:54:09 -08:00
public void ReloadAll()
{
var reloadingOverlay = new GuiWidget
{
HAnchor = HAnchor.Stretch,
VAnchor = VAnchor.Stretch,
BackgroundColor = this.Theme.DarkShade
};
2017-11-07 12:06:43 -08:00
2017-11-07 13:54:09 -08:00
reloadingOverlay.AddChild(new TextWidget("Reloading".Localize() + "...", textColor: Color.White, pointSize: this.Theme.DefaultFontSize * 1.5)
{
2017-11-07 12:06:43 -08:00
HAnchor = HAnchor.Center,
VAnchor = VAnchor.Center
});
AppContext.RootSystemWindow.AddChild(reloadingOverlay);
this.IsReloading = true;
UiThread.RunOnIdle(() =>
{
using (new QuickTimer($"ReloadAll_{reloadCount++}:"))
{
MainView = new WidescreenPanel();
this.DoneReloadingAll?.CallEvents(null, null);
using (new QuickTimer("Time to AddMainview: "))
{
AppContext.RootSystemWindow.CloseAllChildren();
AppContext.RootSystemWindow.AddChild(MainView);
}
}
this.IsReloading = false;
2017-11-07 13:54:09 -08:00
});
}
static int reloadCount = 0;
public async void OnApplicationClosed()
{
// Release the waiting ThumbnailGeneration task so it can shutdown gracefully
thumbGenResetEvent?.Set();
// Save changes before close
if (this.ActivePrinter != null
&& this.ActivePrinter != emptyPrinter)
{
await this.ActivePrinter.Bed.SaveChanges(null, CancellationToken.None);
}
ApplicationSettings.Instance.ReleaseClientToken();
}
internal static void LoadOemOrDefaultTheme()
{
// if not check for the oem color and use it if set
// else default to "Blue - Light"
string oemColor = OemSettings.Instance.ThemeColor;
if (string.IsNullOrEmpty(oemColor))
{
ActiveTheme.Instance = ActiveTheme.GetThemeColors("Blue - Light");
}
else
{
ActiveTheme.Instance = ActiveTheme.GetThemeColors(oemColor);
}
}
public static ApplicationController Instance
{
get
{
if (globalInstance == null)
{
globalInstance = new ApplicationController();
ActiveSliceSettings.ActivePrinterChanged.RegisterEvent((s, e) =>
{
if (!AppContext.IsLoading)
{
ApplicationController.Instance.ReloadAll();
}
}, ref globalInstance.unregisterEvents);
}
return globalInstance;
}
}
2017-09-16 01:11:44 -07:00
public DragDropData DragDropData { get; set; } = new DragDropData();
public View3DWidget ActiveView3DWidget { get; internal set; }
2017-09-15 18:45:21 -07:00
public string PrintingItemName { get; set; }
2017-09-25 22:27:46 -07:00
public string ShortProductName => "MatterControl";
public string ProductName => "MatterHackers: MatterControl";
2017-09-28 21:52:35 -07:00
public string ThumbnailCachePath(ILibraryItem libraryItem)
{
// TODO: Use content SHA
return string.IsNullOrEmpty(libraryItem.ID) ? null : ApplicationController.CacheablePath("ItemThumbnails", $"{libraryItem.ID}.png");
}
2017-12-17 11:07:32 -08:00
public string ThumbnailCachePath(ILibraryItem libraryItem, int width, int height)
{
// TODO: Use content SHA
return string.IsNullOrEmpty(libraryItem.ID) ? null : ApplicationController.CacheablePath("ItemThumbnails", $"{libraryItem.ID}-{width}x{height}.png");
}
public void SwitchToPurchasedLibrary()
{
var purchasedContainer = Library.RootLibaryContainer.ChildContainers.Where(c => c.ID == "LibraryProviderPurchasedKey").FirstOrDefault();
if (purchasedContainer != null)
{
// TODO: Navigate to purchased container
throw new NotImplementedException("SwitchToPurchasedLibrary");
}
}
public void OnLoadActions()
{
// TODO: Calling UserChanged seems wrong. Load the right user before we spin up controls, rather than after
// Pushing this after load fixes that empty printer list
/////////////////////ApplicationController.Instance.UserChanged();
bool showAuthWindow = PrinterSetup.ShouldShowAuthPanel?.Invoke() ?? false;
if (showAuthWindow)
{
if (ApplicationSettings.Instance.get(ApplicationSettingsKey.SuppressAuthPanel) != "True")
{
//Launch window to prompt user to sign in
UiThread.RunOnIdle(() => DialogWindow.Show(PrinterSetup.GetBestStartPage()));
}
}
else
{
//If user in logged in sync before checking to prompt to create printer
if (ApplicationController.SyncPrinterProfiles == null)
{
RunSetupIfRequired();
}
else
{
ApplicationController.SyncPrinterProfiles.Invoke("ApplicationController.OnLoadActions()", null).ContinueWith((task) =>
{
RunSetupIfRequired();
});
}
}
// TODO: This should be moved into the splash screen and shown instead of MainView
if (AggContext.OperatingSystem == OSType.Android)
{
// show this last so it is on top
if (UserSettings.Instance.get("SoftwareLicenseAccepted") != "true")
{
UiThread.RunOnIdle(() => DialogWindow.Show<LicenseAgreementPage>());
}
}
if (ApplicationController.Instance.ActivePrinter is PrinterConfig printer
&& printer.Settings.PrinterSelected
&& printer.Settings.GetValue<bool>(SettingsKey.auto_connect))
{
UiThread.RunOnIdle(() =>
{
//PrinterConnectionAndCommunication.Instance.HaltConnectionThread();
printer.Connection.Connect();
}, 2);
}
if (AssetObject3D.AssetManager == null)
{
AssetObject3D.AssetManager = new AssetManager();
}
//HtmlWindowTest();
}
private static void RunSetupIfRequired()
{
if (!ProfileManager.Instance.ActiveProfiles.Any())
{
// Start the setup wizard if no profiles exist
UiThread.RunOnIdle(() => DialogWindow.Show(PrinterSetup.GetBestStartPage()));
}
}
public void SwitchToSharedLibrary()
{
// Switch to the shared library
var libraryContainer = Library.RootLibaryContainer.ChildContainers.Where(c => c.ID == "LibraryProviderSharedKey").FirstOrDefault();
if (libraryContainer != null)
{
// TODO: Navigate to purchased container
throw new NotImplementedException("SwitchToSharedLibrary");
}
}
public void ChangeCloudSyncStatus(bool userAuthenticated, string reason = "")
{
UserSettings.Instance.set(UserSettingsKey.CredentialsInvalid, userAuthenticated ? "false" : "true");
UserSettings.Instance.set(UserSettingsKey.CredentialsInvalidReason, userAuthenticated ? "" : reason);
CloudSyncStatusChanged.CallEvents(this, new CloudSyncEventArgs() { IsAuthenticated = userAuthenticated });
// Only fire UserChanged if it actually happened - prevents runaway positive feedback loop
if (!string.IsNullOrEmpty(AuthenticationData.Instance.ActiveSessionUsername)
&& AuthenticationData.Instance.ActiveSessionUsername != AuthenticationData.Instance.LastSessionUsername)
{
// only set it if it is an actual user name
AuthenticationData.Instance.LastSessionUsername = AuthenticationData.Instance.ActiveSessionUsername;
}
UiThread.RunOnIdle(this.UserChanged);
}
// Called after every startup and at the completion of every authentication change
public void UserChanged()
{
ProfileManager.ReloadActiveUser();
// Ensure SQLite printers are imported
ProfileManager.Instance.EnsurePrintersImported();
var guest = ProfileManager.Load("guest");
// If profiles.json was created, run the import wizard to pull in any SQLite printers
if (guest?.Profiles?.Any() == true
&& !ProfileManager.Instance.IsGuestProfile
&& !ProfileManager.Instance.PrintersImported)
{
// Show the import printers wizard
DialogWindow.Show<CopyGuestProfilesToUser>();
}
}
private EventHandler unregisterEvent;
public Stream LoadHttpAsset(string url)
{
string fingerPrint = ToSHA1(url);
string cachePath = ApplicationController.CacheablePath("HttpAssets", fingerPrint);
if (File.Exists(cachePath))
{
return File.Open(cachePath, FileMode.Open);
}
else
{
var client = new WebClient();
var bytes = client.DownloadData(url);
File.WriteAllBytes(cachePath, bytes);
return new MemoryStream(bytes);
}
}
/// <summary>
/// Compute hash for string encoded as UTF8
/// </summary>
/// <param name="s">String to be hashed</param>
public static string ToSHA1(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
// var timer = Stopwatch.StartNew();
using (var sha1 = System.Security.Cryptography.SHA1.Create())
{
byte[] hash = sha1.ComputeHash(bytes);
string SHA1 = BitConverter.ToString(hash).Replace("-", string.Empty);
// Console.WriteLine("{0} {1} {2}", SHA1, timer.ElapsedMilliseconds, filePath);
return SHA1;
}
}
/// <summary>
/// Download an image from the web into the specified ImageBuffer
/// </summary>
/// <param name="uri"></param>
public void DownloadToImageAsync(ImageBuffer imageToLoadInto, string uriToLoad, bool scaleToImageX, IRecieveBlenderByte scalingBlender = null)
{
if (scalingBlender == null)
{
scalingBlender = new BlenderBGRA();
}
WebClient client = new WebClient();
client.DownloadDataCompleted += (object sender, DownloadDataCompletedEventArgs e) =>
{
try // if we get a bad result we can get a target invocation exception. In that case just don't show anything
{
// scale the loaded image to the size of the target image
byte[] raw = e.Result;
Stream stream = new MemoryStream(raw);
ImageBuffer unScaledImage = new ImageBuffer(10, 10);
if (scaleToImageX)
{
AggContext.StaticData.LoadImageData(stream, unScaledImage);
// If the source image (the one we downloaded) is more than twice as big as our dest image.
while (unScaledImage.Width > imageToLoadInto.Width * 2)
{
// The image sampler we use is a 2x2 filter so we need to scale by a max of 1/2 if we want to get good results.
// So we scale as many times as we need to get the Image to be the right size.
// If this were going to be a non-uniform scale we could do the x and y separately to get better results.
ImageBuffer halfImage = new ImageBuffer(unScaledImage.Width / 2, unScaledImage.Height / 2, 32, scalingBlender);
halfImage.NewGraphics2D().Render(unScaledImage, 0, 0, 0, halfImage.Width / (double)unScaledImage.Width, halfImage.Height / (double)unScaledImage.Height);
unScaledImage = halfImage;
}
2017-09-16 01:11:44 -07:00
double finalScale = imageToLoadInto.Width / (double)unScaledImage.Width;
imageToLoadInto.Allocate(imageToLoadInto.Width, (int)(unScaledImage.Height * finalScale), imageToLoadInto.Width * (imageToLoadInto.BitDepth / 8), imageToLoadInto.BitDepth);
imageToLoadInto.NewGraphics2D().Render(unScaledImage, 0, 0, 0, finalScale, finalScale);
}
else
{
AggContext.StaticData.LoadImageData(stream, imageToLoadInto);
}
imageToLoadInto.MarkImageChanged();
}
catch
{
}
};
try
{
client.DownloadDataAsync(new Uri(uriToLoad));
}
catch
{
}
}
/// <summary>
/// Cancels prints within the first two minutes or interactively prompts the user to confirm cancellation
/// </summary>
/// <returns>A boolean value indicating if the print was canceled</returns>
public bool ConditionalCancelPrint()
{
bool canceled = false;
2017-09-17 21:08:16 -07:00
if (this.ActivePrinter.Connection.SecondsPrinted > 120)
{
StyledMessageBox.ShowMessageBox(
(bool response) =>
{
if (response)
{
2017-09-17 21:08:16 -07:00
UiThread.RunOnIdle(() => this.ActivePrinter.Connection.Stop());
canceled = true;
}
canceled = false;
},
"Cancel the current print?".Localize(),
"Cancel Print?".Localize(),
StyledMessageBox.MessageType.YES_NO,
"Cancel Print".Localize(),
"Continue Printing".Localize());
}
else
{
2017-09-17 21:08:16 -07:00
this.ActivePrinter.Connection.Stop();
canceled = false;
}
return canceled;
}
/// <summary>
/// Register the given PrintItemAction into the named section
/// </summary>
/// <param name="section">The section to register in</param>
/// <param name="printItemAction">The action to register</param>
public void RegisterLibraryAction(string section, PrintItemAction printItemAction)
{
List<PrintItemAction> items;
if (!registeredLibraryActions.TryGetValue(section, out items))
{
items = new List<PrintItemAction>();
registeredLibraryActions.Add(section, items);
}
items.Add(printItemAction);
}
/// <summary>
/// Enumerate the given section, returning all registered actions
/// </summary>
/// <param name="section">The section to enumerate</param>
/// <returns></returns>
public IEnumerable<PrintItemAction> RegisteredLibraryActions(string section)
{
List<PrintItemAction> items;
if (registeredLibraryActions.TryGetValue(section, out items))
{
return items;
}
return Enumerable.Empty<PrintItemAction>();
}
public IEnumerable<SceneSelectionOperation> RegisteredSceneOperations => registeredSceneOperations;
2017-09-18 17:57:06 -07:00
public static IObject3D ClipboardItem { get; internal set; }
2017-08-07 14:19:28 -07:00
public event EventHandler<WidgetSourceEventArgs> AddPrintersTabRightElement;
public void NotifyPrintersTabRightElement(GuiWidget sourceExentionArea)
{
AddPrintersTabRightElement?.Invoke(this, new WidgetSourceEventArgs(sourceExentionArea));
}
private string doNotAskAgainMessage = "Don't remind me again".Localize();
public async Task PrintPart(EditContext editContext, PrinterConfig printer, IProgress<ProgressStatus> reporter, CancellationToken cancellationToken, bool overrideAllowGCode = false)
{
var object3D = editContext.Content;
var partFilePath = editContext.SourceFilePath;
var gcodeFilePath = editContext.GCodeFilePath;
var printItemName = editContext.SourceItem.Name;
// Exit if called in a non-applicable state
if (this.ActivePrinter.Connection.CommunicationState != CommunicationStates.Connected
&& this.ActivePrinter.Connection.CommunicationState != CommunicationStates.FinishedPrint)
{
return;
}
try
{
// If leveling is required or is currently on
if (ActiveSliceSettings.Instance.GetValue<bool>(SettingsKey.print_leveling_required_to_print)
|| ActiveSliceSettings.Instance.GetValue<bool>(SettingsKey.print_leveling_enabled))
{
if (PrintLevelingData.NeedsToBeRun(printer))
{
2017-11-30 17:53:29 -08:00
LevelWizardBase.ShowPrintLevelWizard(printer);
return;
}
}
//if (!string.IsNullOrEmpty(partFilePath) && File.Exists(partFilePath))
{
this.PrintingItemName = printItemName;
2017-09-23 09:19:32 -07:00
if (ActiveSliceSettings.Instance.IsValid())
{
{
// clear the output cache prior to starting a print
2017-09-17 21:08:16 -07:00
this.ActivePrinter.Connection.TerminalLog.Clear();
string hideGCodeWarning = ApplicationSettings.Instance.get(ApplicationSettingsKey.HideGCodeWarning);
if (Path.GetExtension(partFilePath).ToUpper() == ".GCODE"
&& hideGCodeWarning == null
&& !overrideAllowGCode)
{
var hideGCodeWarningCheckBox = new CheckBox(doNotAskAgainMessage)
{
TextColor = ActiveTheme.Instance.PrimaryTextColor,
Margin = new BorderDouble(top: 6, left: 6),
HAnchor = Agg.UI.HAnchor.Left
};
hideGCodeWarningCheckBox.Click += (sender, e) =>
{
if (hideGCodeWarningCheckBox.Checked)
{
ApplicationSettings.Instance.set(ApplicationSettingsKey.HideGCodeWarning, "true");
}
else
{
ApplicationSettings.Instance.set(ApplicationSettingsKey.HideGCodeWarning, null);
}
};
2017-09-15 18:45:21 -07:00
UiThread.RunOnIdle(() =>
{
StyledMessageBox.ShowMessageBox(
2017-12-11 22:22:56 -08:00
(messageBoxResponse) =>
2017-09-15 18:45:21 -07:00
{
if (messageBoxResponse)
{
2017-09-17 21:08:16 -07:00
this.ActivePrinter.Connection.CommunicationState = CommunicationStates.PreparingToPrint;
this.ArchiveAndStartPrint(partFilePath, gcodeFilePath);
2017-09-15 18:45:21 -07:00
}
},
"The file you are attempting to print is a GCode file.\n\nIt is recommended that you only print Gcode files known to match your printer's configuration.\n\nAre you sure you want to print this GCode file?".Localize(),
2017-09-15 18:45:21 -07:00
"Warning - GCode file".Localize(),
new GuiWidget[]
{
new VerticalSpacer(),
hideGCodeWarningCheckBox
},
StyledMessageBox.MessageType.YES_NO);
});
}
else
{
2017-09-17 21:08:16 -07:00
this.ActivePrinter.Connection.CommunicationState = CommunicationStates.PreparingToPrint;
await ApplicationController.Instance.SliceItemLoadOutput(
printer,
printer.Bed.Scene,
gcodeFilePath);
this.ArchiveAndStartPrint(partFilePath, gcodeFilePath);
}
2017-12-11 22:22:56 -08:00
await MonitorPrintTask(printer);
}
}
}
}
catch (Exception)
{
}
}
public void ResetTranslationMap()
{
TranslationMap.ActiveTranslationMap = new TranslationMap("Translations", UserSettings.Instance.Language);
}
public async Task MonitorPrintTask(PrinterConfig printer)
{
await ApplicationController.Instance.Tasks.Execute("Printing".Localize(),
(reporterB, cancellationTokenB) =>
{
var progressStatus = new ProgressStatus();
reporterB.Report(progressStatus);
return Task.Run(() =>
{
string printing = "Printing".Localize();
int totalLayers = printer.Connection.TotalLayersInPrint;
while (!printer.Connection.PrinterIsPrinting
&& !cancellationTokenB.IsCancellationRequested)
{
// Wait for printing
Thread.Sleep(200);
}
while ((printer.Connection.PrinterIsPrinting || printer.Connection.PrinterIsPaused)
&& !cancellationTokenB.IsCancellationRequested)
{
//progressStatus.Status = $"{printing} Layer ({printer.Connection.CurrentlyPrintingLayer } of {totalLayers})";
progressStatus.Status = $"{printing} ({printer.Connection.CurrentlyPrintingLayer + 1})";
progressStatus.Progress0To1 = printer.Connection.PercentComplete / 100;
reporterB.Report(progressStatus);
Thread.Sleep(200);
}
});
},
taskActions: new RunningTaskActions()
{
RichProgressWidget = () => PrinterTabPage.PrintProgressWidget(printer),
Pause = () => UiThread.RunOnIdle(() =>
{
printer.Connection.RequestPause();
}),
Resume = () => UiThread.RunOnIdle(() =>
{
printer.Connection.Resume();
}),
Stop = () => UiThread.RunOnIdle(() =>
{
ApplicationController.Instance.ConditionalCancelPrint();
})
});
}
/// <summary>
/// Archives MCX and validates GCode results before starting a print operation
/// </summary>
/// <param name="sourcePath">The source file which originally caused the slice->print operation</param>
/// <param name="gcodeFilePath">The resulting GCode to print</param>
private void ArchiveAndStartPrint(string sourcePath, string gcodeFilePath)
{
if (File.Exists(sourcePath)
&& File.Exists(gcodeFilePath))
{
//if (gcodeFilePath != "")
{
bool originalIsGCode = Path.GetExtension(sourcePath).ToUpper() == ".GCODE";
2017-11-15 08:04:29 -08:00
if (File.Exists(gcodeFilePath))
{
// Create archive point for printing attempt
if (Path.GetExtension(sourcePath).ToUpper() == ".MCX")
{
// TODO: We should zip mcx and settings when starting a print
string platingDirectory = Path.Combine(ApplicationDataStorage.Instance.ApplicationLibraryDataPath, "PrintHistory");
Directory.CreateDirectory(platingDirectory);
string now = "Workspace " + DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss");
string archivePath = Path.Combine(platingDirectory, now + ".zip");
using (var file = File.OpenWrite(archivePath))
using (var zip = new ZipArchive(file, ZipArchiveMode.Create))
{
zip.CreateEntryFromFile(sourcePath, "PrinterPlate.mcx");
zip.CreateEntryFromFile(ActiveSliceSettings.Instance.DocumentPath, ActiveSliceSettings.Instance.GetValue(SettingsKey.printer_name) + ".printer");
2017-11-15 08:04:29 -08:00
zip.CreateEntryFromFile(gcodeFilePath, "sliced.gcode");
}
}
if (originalIsGCode)
{
2017-11-15 08:04:29 -08:00
this.ActivePrinter.Connection.StartPrint(gcodeFilePath);
return;
}
else
{
// read the last few k of the file and see if it says "filament used". We use this marker to tell if the file finished writing
int bufferSize = 32000;
2017-11-15 08:04:29 -08:00
using (Stream fileStream = new FileStream(gcodeFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byte[] buffer = new byte[bufferSize];
fileStream.Seek(Math.Max(0, fileStream.Length - bufferSize), SeekOrigin.Begin);
int numBytesRead = fileStream.Read(buffer, 0, bufferSize);
fileStream.Close();
string fileEnd = System.Text.Encoding.UTF8.GetString(buffer);
if (fileEnd.Contains("filament used"))
{
2017-11-15 08:04:29 -08:00
this.ActivePrinter.Connection.StartPrint(gcodeFilePath);
return;
}
}
}
}
2017-09-17 21:08:16 -07:00
this.ActivePrinter.Connection.CommunicationState = CommunicationStates.Connected;
}
}
}
public async Task SliceItemLoadOutput(PrinterConfig printer, IObject3D object3D, string gcodeFilePath)
{
// Slice
bool slicingSucceeded = false;
await ApplicationController.Instance.Tasks.Execute("Slicing".Localize(), async (reporter, cancellationToken) =>
{
slicingSucceeded = await Slicer.SliceItem(
object3D,
gcodeFilePath,
printer,
new SliceProgressReporter(reporter, printer),
2018-01-17 08:32:40 -08:00
cancellationToken);
});
// Skip loading GCode output if slicing failed
if (!slicingSucceeded)
{
return;
}
await ApplicationController.Instance.Tasks.Execute("Loading GCode".Localize(), (innerProgress, token) =>
{
var status = new ProgressStatus();
innerProgress.Report(status);
printer.Bed.LoadGCode(gcodeFilePath, token, (progress0to1, statusText) =>
{
UiThread.RunOnIdle(() =>
{
status.Progress0To1 = progress0to1;
status.Status = statusText;
innerProgress.Report(status);
});
});
return Task.CompletedTask;
});
}
internal GuiWidget GetViewOptionButtons(BedConfig sceneContext, PrinterConfig printer, ThemeConfig theme)
{
var container = new FlowLayoutWidget();
var bedButton = new RadioIconButton(AggContext.StaticData.LoadIcon("bed.png", IconColor.Theme), theme)
{
Name = "Bed Button",
ToolTipText = "Show Print Bed".Localize(),
Checked = sceneContext.RendererOptions.RenderBed,
Margin = theme.ButtonSpacing,
ToggleButton = true,
Height = 24,
Width = 24
};
bedButton.CheckedStateChanged += (s, e) =>
{
sceneContext.RendererOptions.RenderBed = bedButton.Checked;
};
container.AddChild(bedButton);
2018-04-04 10:11:57 -07:00
RadioIconButton printAreaButton = null;
if (sceneContext.BuildHeight > 0
&& printer?.ViewState.ViewMode != PartViewMode.Layers2D)
{
2018-04-04 10:11:57 -07:00
printAreaButton = new RadioIconButton(AggContext.StaticData.LoadIcon("print_area.png", IconColor.Theme), theme)
{
Name = "Bed Button",
ToolTipText = "Show Print Area".Localize(),
Checked = sceneContext.RendererOptions.RenderBuildVolume,
Margin = theme.ButtonSpacing,
ToggleButton = true,
Height = 24,
Width = 24
};
printAreaButton.CheckedStateChanged += (s, e) =>
{
sceneContext.RendererOptions.RenderBuildVolume = printAreaButton.Checked;
};
container.AddChild(printAreaButton);
}
this. BindBedOptions(container, bedButton, printAreaButton, sceneContext.RendererOptions);
2018-04-04 10:11:57 -07:00
return container;
}
2018-04-04 10:11:57 -07:00
public void BindBedOptions(GuiWidget container, ICheckbox bedButton, ICheckbox printAreaButton, View3DConfig renderOptions)
{
PropertyChangedEventHandler syncProperties = (s, e) =>
{
switch (e.PropertyName)
{
case nameof(renderOptions.RenderBed):
bedButton.Checked = renderOptions.RenderBed;
break;
case nameof(renderOptions.RenderBuildVolume) when printAreaButton != null:
printAreaButton.Checked = renderOptions.RenderBuildVolume;
break;
}
};
renderOptions.PropertyChanged += syncProperties;
container.Closed += (s, e) =>
{
renderOptions.PropertyChanged -= syncProperties;
};
}
2017-09-16 01:11:44 -07:00
public class CloudSyncEventArgs : EventArgs
{
public bool IsAuthenticated { get; set; }
}
2017-08-07 14:19:28 -07:00
}
public class WidgetSourceEventArgs : EventArgs
{
public GuiWidget Source { get; }
2017-08-07 14:19:28 -07:00
public WidgetSourceEventArgs(GuiWidget source)
{
this.Source = source;
}
}
2017-09-16 01:11:44 -07:00
public class DragDropData
{
public View3DWidget View3DWidget { get; set; }
2017-09-23 09:19:32 -07:00
public PrinterConfig Printer { get; internal set; }
2017-10-05 22:32:23 -07:00
public BedConfig SceneContext { get; set; }
2017-09-16 01:11:44 -07:00
public void Reset()
{
this.View3DWidget = null;
2017-10-05 22:32:23 -07:00
this.SceneContext = null;
2017-09-16 01:11:44 -07:00
}
}
public class RunningTaskDetails : IProgress<ProgressStatus>
{
public event EventHandler<ProgressStatus> ProgressChanged;
public Func<GuiWidget> DetailsItemAction { get; set; }
2017-12-11 22:22:56 -08:00
public RunningTaskDetails(CancellationTokenSource tokenSource)
{
this.tokenSource = tokenSource;
}
public string Title { get; set; }
public Action<Printer> PauseAction { get; internal set; }
public Action<Printer> ResumeAction { get; internal set; }
public Action<Printer> StopAction { get; internal set; }
public RunningTaskActions TaskActions { get; internal set; }
private CancellationTokenSource tokenSource;
public void Report(ProgressStatus progressStatus)
{
this.ProgressChanged?.Invoke(this, progressStatus);
}
public void CancelTask()
{
this.tokenSource.Cancel();
}
}
public class RunningTaskActions
{
public Func<GuiWidget> RichProgressWidget { get; set; }
public Action Pause { get; set; }
public Action Resume { get; set; }
public Action Stop { get; set; }
}
public class RunningTasksConfig
{
public event EventHandler TasksChanged;
private ObservableCollection<RunningTaskDetails> executingTasks = new ObservableCollection<RunningTaskDetails>();
public IEnumerable<RunningTaskDetails> RunningTasks => executingTasks.ToList();
public RunningTasksConfig()
{
executingTasks.CollectionChanged += (s, e) =>
{
this.TasksChanged?.Invoke(this, null);
};
}
public Task Execute(string taskTitle, Func<IProgress<ProgressStatus>, CancellationToken, Task> func, RunningTaskActions taskActions = null)
{
var tokenSource = new CancellationTokenSource();
2017-12-11 22:22:56 -08:00
var taskDetails = new RunningTaskDetails(tokenSource)
{
TaskActions = taskActions,
Title = taskTitle
2017-12-11 22:22:56 -08:00
};
executingTasks.Add(taskDetails);
return Task.Run(async () =>
{
try
{
await func?.Invoke(taskDetails, tokenSource.Token);
}
catch
{
}
executingTasks.Remove(taskDetails);
});
}
}
public enum ReportSeverity2 { Warning, Error }
public interface INativePlatformFeatures
{
event EventHandler PictureTaken;
void TakePhoto(string imageFileName);
void OpenCameraPreview();
void PlaySound(string fileName);
void ConfigureWifi();
bool CameraInUseByExternalProcess { get; set; }
bool IsNetworkConnected();
void FindAndInstantiatePlugins(SystemWindow systemWindow);
void ProcessCommandline();
void PlatformInit(Action<string> reporter);
}
public static class Application
{
private static ProgressBar progressBar;
private static TextWidget statusText;
private static FlowLayoutWidget progressPanel;
private static string lastSection = "";
private static Stopwatch timer;
public static string PlatformFeaturesProvider { get; set; } = "MatterHackers.MatterControl.WindowsPlatformsFeatures, MatterControl";
public static SystemWindow LoadRootWindow(int width, int height)
{
timer = Stopwatch.StartNew();
var systemWindow = new RootSystemWindow(width, height);
2018-02-26 17:48:15 -08:00
var overlay = new GuiWidget()
{
BackgroundColor = Color.DarkGray
};
overlay.AnchorAll();
systemWindow.AddChild(overlay);
2018-03-11 17:46:59 -07:00
var spinner = new LogoSpinner(overlay, rotateX: -0.05);
progressPanel = new FlowLayoutWidget(FlowDirection.TopToBottom)
{
Position = new Vector2(0, height*.25),
HAnchor = HAnchor.Center | HAnchor.Fit,
VAnchor = VAnchor.Fit,
MinimumSize = new Vector2(400, 100),
Margin = new BorderDouble(0, 0, 0, 200)
};
overlay.AddChild(progressPanel);
progressPanel.AddChild(statusText = new TextWidget("", textColor: new Color("#9ad5dd"))
{
2018-02-27 08:46:54 -08:00
MinimumSize = new Vector2(200, 30),
HAnchor = HAnchor.Center,
AutoExpandBoundsToText = true
});
progressPanel.AddChild(progressBar = new ProgressBar()
{
FillColor = new Color("#049eb6"),
BorderColor = new Color("#006f83"),
Height = 11,
2018-02-27 08:46:54 -08:00
Width = 230,
HAnchor = HAnchor.Center,
VAnchor = VAnchor.Absolute
});
AppContext.RootSystemWindow = systemWindow;
// Hook SystemWindow load and spin up MatterControl once we've hit first draw
systemWindow.Load += (s, e) =>
{
ReportStartupProgress(0.02, "First draw->RunOnIdle");
//UiThread.RunOnIdle(() =>
Task.Run(async () =>
{
try
{
ReportStartupProgress(0.15, "MatterControlApplication.Initialize");
var mainView = await Initialize(systemWindow, (progress0To1, status) =>
{
ReportStartupProgress(0.2 + progress0To1 * 0.7, status);
});
TranslationMap.ActiveTranslationMap = new TranslationMap("Translations", UserSettings.Instance.Language);
ReportStartupProgress(0.9, "AddChild->MainView");
systemWindow.AddChild(mainView, 0);
ReportStartupProgress(1, "");
systemWindow.BackgroundColor = Color.Transparent;
overlay.Close();
}
catch (Exception ex)
{
UiThread.RunOnIdle(() =>
{
var theme = ApplicationController.Instance.Theme;
statusText.Visible = false;
var errorTextColor = Color.White;
progressPanel.Margin = 0;
progressPanel.VAnchor = VAnchor.Center | VAnchor.Fit;
progressPanel.BackgroundColor = Color.DarkGray;
progressPanel.Padding = 20;
progressPanel.Border = 1;
progressPanel.BorderColor = Color.Red;
progressPanel.AddChild(
new TextWidget("Startup Failure".Localize() + ":", pointSize: theme.DefaultFontSize, textColor: errorTextColor));
progressPanel.AddChild(
new TextWidget(ex.Message, pointSize: theme.FontSize9, textColor: errorTextColor));
var closeButton = new TextButton("Close", theme, Color.White)
{
BackgroundColor = theme.SlightShade,
HAnchor = HAnchor.Right,
VAnchor = VAnchor.Absolute
};
closeButton.Click += (s1, e1) =>
{
systemWindow.Close();
};
spinner.SpinLogo = false;
progressBar.Visible = false;
progressPanel.AddChild(closeButton);
});
}
AppContext.IsLoading = false;
});
};
// Block indefinitely
ReportStartupProgress(0, "ShowAsSystemWindow");
return systemWindow;
}
public static async Task<GuiWidget> Initialize(SystemWindow systemWindow, Action<double, string> reporter)
{
AppContext.Platform = AggContext.CreateInstanceFrom<INativePlatformFeatures>(PlatformFeaturesProvider);
reporter?.Invoke(0.01, "PlatformInit");
AppContext.Platform.PlatformInit((status) =>
{
reporter?.Invoke(0.01, status);
});
// TODO: Appears to be unused and should be removed
// set this at startup so that we can tell next time if it got set to true in close
UserSettings.Instance.Fields.StartCount = UserSettings.Instance.Fields.StartCount + 1;
reporter?.Invoke(0.05, "ApplicationController");
var na = ApplicationController.Instance;
// Set the default theme colors
reporter?.Invoke(0.1, "LoadOemOrDefaultTheme");
ApplicationController.LoadOemOrDefaultTheme();
// Accessing any property on ProfileManager will run the static constructor and spin up the ProfileManager instance
reporter?.Invoke(0.2, "ProfileManager");
bool na2 = ProfileManager.Instance.IsGuestProfile;
await ProfileManager.Instance.Initialize();
reporter?.Invoke(0.3, "MainView");
ApplicationController.Instance.MainView = new WidescreenPanel();
// now that we are all set up lets load our plugins and allow them their chance to set things up
reporter?.Invoke(0.8, "Plugins");
AppContext.Platform.FindAndInstantiatePlugins(systemWindow);
reporter?.Invoke(0.9, "Process Commandline");
AppContext.Platform.ProcessCommandline();
reporter?.Invoke(0.91, "OnLoadActions");
ApplicationController.Instance.OnLoadActions();
UiThread.SetInterval(() =>
{
ApplicationController.Instance.ActivePrinter.Connection.OnIdle();
}, .1, () => true);
return ApplicationController.Instance.MainView;
}
private static void ReportStartupProgress(double progress0To1, string section)
{
2017-12-18 18:36:41 -08:00
UiThread.RunOnIdle(() =>
{
statusText.Text = section;
progressBar.RatioComplete = progress0To1;
progressPanel.Invalidate();
2017-12-18 18:36:41 -08:00
Console.WriteLine($"Time to '{lastSection}': {timer.ElapsedMilliseconds}");
timer.Restart();
2017-12-18 18:36:41 -08:00
lastSection = section;
});
}
}
}