integrating offline changes
This commit is contained in:
parent
121623bad3
commit
3f8eeda65b
125 changed files with 5442 additions and 5434 deletions
|
|
@ -714,20 +714,14 @@ namespace MatterHackers.MatterControl
|
|||
null,
|
||||
(progress, cancellationToken) =>
|
||||
{
|
||||
var status = new ProgressStatus();
|
||||
|
||||
int itemCount = ApplicationController.StartupActions.Count;
|
||||
|
||||
double i = 1;
|
||||
|
||||
foreach (var action in ApplicationController.StartupActions.OrderByDescending(t => t.Priority))
|
||||
{
|
||||
status.Status = action.Title;
|
||||
progress.Report(status);
|
||||
|
||||
action.Action?.Invoke();
|
||||
status.Progress0To1 = i++ / itemCount;
|
||||
progress.Report(status);
|
||||
progress?.Invoke(i++ / (double)itemCount, action.Title);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ namespace MatterHackers.MatterControl
|
|||
public static Func<PrinterInfo, string, Task<PrinterSettings>> GetPrinterProfileAsync;
|
||||
|
||||
// Executes the user printer profile sync logic in the webservices plugin
|
||||
public static Func<string, IProgress<ProgressStatus>, Task> SyncCloudProfiles;
|
||||
public static Func<string, Action<double, string>, Task> SyncCloudProfiles;
|
||||
|
||||
public static Action<string> QueueCloudProfileSync;
|
||||
|
||||
|
|
@ -1223,12 +1223,10 @@ namespace MatterHackers.MatterControl
|
|||
(progress, cancellationToken) =>
|
||||
{
|
||||
var time = UiThread.CurrentTimerMs;
|
||||
var status = new ProgressStatus();
|
||||
while (UiThread.CurrentTimerMs < time + durationSeconds * 1000)
|
||||
{
|
||||
Thread.Sleep(30);
|
||||
status.Progress0To1 = (UiThread.CurrentTimerMs - time) / 1000.0 / durationSeconds;
|
||||
progress.Report(status);
|
||||
progress?.Invoke((UiThread.CurrentTimerMs - time) / 1000.0 / durationSeconds, null);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -1289,21 +1287,20 @@ namespace MatterHackers.MatterControl
|
|||
var paused = false;
|
||||
Tasks.Execute("", printerConnection.Printer, (reporter, cancellationToken) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
|
||||
while (printerConnection.SecondsToHoldTemperature > 0
|
||||
&& !cancellationToken.IsCancellationRequested
|
||||
&& printerConnection.ContinueHoldingTemperature)
|
||||
{
|
||||
var status = "";
|
||||
if (paused)
|
||||
{
|
||||
progressStatus.Status = "Holding Temperature".Localize();
|
||||
status = "Holding Temperature".Localize();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (printerConnection.SecondsToHoldTemperature > 60)
|
||||
{
|
||||
progressStatus.Status = string.Format(
|
||||
status = string.Format(
|
||||
"{0} {1:0}m {2:0}s",
|
||||
"Automatic Heater Shutdown in".Localize(),
|
||||
(int)printerConnection.SecondsToHoldTemperature / 60,
|
||||
|
|
@ -1311,15 +1308,15 @@ namespace MatterHackers.MatterControl
|
|||
}
|
||||
else
|
||||
{
|
||||
progressStatus.Status = string.Format(
|
||||
status = string.Format(
|
||||
"{0} {1:0}s",
|
||||
"Automatic Heater Shutdown in".Localize(),
|
||||
printerConnection.SecondsToHoldTemperature);
|
||||
}
|
||||
}
|
||||
|
||||
progressStatus.Progress0To1 = printerConnection.SecondsToHoldTemperature / printerConnection.TimeToHoldTemperature;
|
||||
reporter.Report(progressStatus);
|
||||
var progress = printerConnection.SecondsToHoldTemperature / printerConnection.TimeToHoldTemperature;
|
||||
reporter?.Invoke(progress, status);
|
||||
Thread.Sleep(20);
|
||||
}
|
||||
|
||||
|
|
@ -1869,7 +1866,6 @@ namespace MatterHackers.MatterControl
|
|||
null,
|
||||
async (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
for (int i=0; i<persistedWorkspaces.Count; i++)
|
||||
{
|
||||
var persistedWorkspace = persistedWorkspaces[i];
|
||||
|
|
@ -1915,9 +1911,9 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
var ratioPerWorkspace = 1.0 / persistedWorkspaces.Count;
|
||||
var completed = ratioPerWorkspace * i;
|
||||
progressStatus.Progress0To1 = completed + progress * ratioPerWorkspace;
|
||||
progressStatus.Status = message;
|
||||
reporter.Report(progressStatus);
|
||||
var progress2 = completed + progress * ratioPerWorkspace;
|
||||
var status = message;
|
||||
reporter?.Invoke(progress2, status);
|
||||
});
|
||||
|
||||
this.RestoreWorkspace(workspace);
|
||||
|
|
@ -2041,7 +2037,7 @@ namespace MatterHackers.MatterControl
|
|||
leftChild.Padding = new BorderDouble(padding.Left, padding.Bottom, sourceExentionArea.Width, padding.Height);
|
||||
}
|
||||
|
||||
public async Task PrintPart(EditContext editContext, PrinterConfig printerConfig, IProgress<ProgressStatus> reporter, CancellationToken cancellationToken, PrinterConnection.PrintingModes printingMode)
|
||||
public async Task PrintPart(EditContext editContext, PrinterConfig printerConfig, Action<double, string> reporter, CancellationToken cancellationToken, PrinterConnection.PrintingModes printingMode)
|
||||
{
|
||||
var partFilePath = editContext.SourceFilePath;
|
||||
var gcodeFilePath = await editContext.GCodeFilePath(printerConfig);
|
||||
|
|
@ -2248,9 +2244,6 @@ namespace MatterHackers.MatterControl
|
|||
printer,
|
||||
(reporterB, cancellationTokenB) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporterB.Report(progressStatus);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
string printing = "Printing".Localize();
|
||||
|
|
@ -2267,10 +2260,10 @@ namespace MatterHackers.MatterControl
|
|||
&& !cancellationTokenB.IsCancellationRequested)
|
||||
{
|
||||
var layerCount = printer.Bed.LoadedGCode == null ? "?" : printer.Bed.LoadedGCode.LayerCount.ToString();
|
||||
progressStatus.Status = $"{printing} ({printer.Connection.CurrentlyPrintingLayer + 1} of {layerCount}) - {printer.Connection.PercentComplete:0}%";
|
||||
var status = $"{printing} ({printer.Connection.CurrentlyPrintingLayer + 1} of {layerCount}) - {printer.Connection.PercentComplete:0}%";
|
||||
|
||||
progressStatus.Progress0To1 = printer.Connection.PercentComplete / 100;
|
||||
reporterB.Report(progressStatus);
|
||||
var progress0To1 = printer.Connection.PercentComplete / 100;
|
||||
reporterB?.Invoke(progress0To1, status);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
});
|
||||
|
|
@ -2448,18 +2441,14 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
await this.Tasks.Execute("Loading GCode".Localize(), printer, (innerProgress, concelationTokenSource) =>
|
||||
{
|
||||
var status = new ProgressStatus();
|
||||
|
||||
innerProgress.Report(status);
|
||||
|
||||
printer.Bed.LoadActiveSceneGCode(gcodeFilePath, concelationTokenSource.Token, (progress0to1, statusText) =>
|
||||
{
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
status.Progress0To1 = progress0to1;
|
||||
status.Status = statusText;
|
||||
var progress0To1 = progress0to1;
|
||||
var status = statusText;
|
||||
|
||||
innerProgress.Report(status);
|
||||
innerProgress?.Invoke(progress0To1, status);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -2667,7 +2656,7 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
public int Priority { get; set; }
|
||||
|
||||
public Func<IProgress<ProgressStatus>, CancellationTokenSource, Task> Action { get; set; }
|
||||
public Func<Action<double, string>, CancellationTokenSource, Task> Action { get; set; }
|
||||
}
|
||||
|
||||
public class StartupAction
|
||||
|
|
|
|||
|
|
@ -162,14 +162,9 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
await ApplicationController.Instance.Tasks.Execute("Loading G-Code".Localize(), Printer, (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
|
||||
this.LoadGCode(stream, cancellationTokenSource.Token, (progress0To1, status) =>
|
||||
{
|
||||
progressStatus.Status = status;
|
||||
progressStatus.Progress0To1 = progress0To1;
|
||||
reporter.Report(progressStatus);
|
||||
reporter?.Invoke(progress0To1, status);
|
||||
});
|
||||
|
||||
this.Scene.Children.Modify(children => children.Clear());
|
||||
|
|
@ -676,7 +671,7 @@ namespace MatterHackers.MatterControl
|
|||
/// <param name="progress">Allows for progress reporting</param>
|
||||
/// <param name="cancellationTokenSource">Allows for cancellation during processing</param>
|
||||
/// <returns>A task representing success</returns>
|
||||
public async Task SaveChanges(IProgress<ProgressStatus> progress, CancellationTokenSource cancellationTokenSource)
|
||||
public async Task SaveChanges(Action<double, string> progress, CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
if (this.EditContext.ContentStore == null)
|
||||
{
|
||||
|
|
@ -693,13 +688,10 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
var status = "Saving Changes".Localize();
|
||||
|
||||
var progressStatus = new ProgressStatus()
|
||||
{
|
||||
Status = "Saving Changes"
|
||||
};
|
||||
|
||||
progress?.Report(progressStatus);
|
||||
progress?.Invoke(0, status);
|
||||
|
||||
if (this.Scene.Persistable)
|
||||
{
|
||||
|
|
@ -721,12 +713,7 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
await this.Scene.PersistAssets((progress0to1, status) =>
|
||||
{
|
||||
if (progress != null)
|
||||
{
|
||||
progressStatus.Status = status;
|
||||
progressStatus.Progress0To1 = progress0to1;
|
||||
progress.Report(progressStatus);
|
||||
}
|
||||
progress?.Invoke(progress0to1, status);
|
||||
});
|
||||
|
||||
await this.EditContext?.Save(this.Scene);
|
||||
|
|
|
|||
|
|
@ -338,7 +338,6 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
waitingForHeat = HeatingStatus.Bed;
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
heatStart = printerConnection.ActualBedTemperature;
|
||||
heatDistance = Math.Abs(printerConnection.TargetBedTemperature - heatStart);
|
||||
|
||||
|
|
@ -346,9 +345,8 @@ namespace MatterHackers.MatterControl
|
|||
&& waitingForHeat == HeatingStatus.Bed)
|
||||
{
|
||||
var remainingDistance = Math.Abs(printerConnection.TargetBedTemperature - printerConnection.ActualBedTemperature);
|
||||
progressStatus.Status = $"Heating Bed ({printerConnection.ActualBedTemperature:0}/{printerConnection.TargetBedTemperature:0})";
|
||||
progressStatus.Progress0To1 = (heatDistance - remainingDistance) / heatDistance;
|
||||
reporter.Report(progressStatus);
|
||||
var status = $"Heating Bed ({0:0}/{1:0})".Localize().FormatWith(printerConnection.ActualBedTemperature, printerConnection.TargetBedTemperature);
|
||||
reporter?.Invoke((heatDistance - remainingDistance) / heatDistance, status);
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
|
|
@ -368,8 +366,6 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
waitingForHeat = HeatingStatus.T0;
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
|
||||
heatStart = printerConnection.GetActualHotendTemperature(0);
|
||||
heatDistance = Math.Abs(printerConnection.GetTargetHotendTemperature(0) - heatStart);
|
||||
|
||||
|
|
@ -377,9 +373,9 @@ namespace MatterHackers.MatterControl
|
|||
&& waitingForHeat == HeatingStatus.T0)
|
||||
{
|
||||
var currentDistance = Math.Abs(printerConnection.GetTargetHotendTemperature(0) - printerConnection.GetActualHotendTemperature(0));
|
||||
progressStatus.Progress0To1 = (heatDistance - currentDistance) / heatDistance;
|
||||
progressStatus.Status = $"Heating Nozzle ({printerConnection.GetActualHotendTemperature(0):0}/{printerConnection.GetTargetHotendTemperature(0):0})";
|
||||
reporter.Report(progressStatus);
|
||||
var progress0To1 = (heatDistance - currentDistance) / heatDistance;
|
||||
var status = $"Heating Nozzle ({0}/{1})".Localize().FormatWith(printerConnection.GetActualHotendTemperature(0), printerConnection.GetTargetHotendTemperature(0));
|
||||
reporter?.Invoke(progress0To1, status);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
|
|
@ -399,8 +395,6 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
waitingForHeat = HeatingStatus.T1;
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
|
||||
heatStart = printerConnection.GetActualHotendTemperature(1);
|
||||
heatDistance = Math.Abs(printerConnection.GetTargetHotendTemperature(1) - heatStart);
|
||||
|
||||
|
|
@ -408,9 +402,9 @@ namespace MatterHackers.MatterControl
|
|||
&& waitingForHeat == HeatingStatus.T1)
|
||||
{
|
||||
var currentDistance = Math.Abs(printerConnection.GetTargetHotendTemperature(1) - printerConnection.GetActualHotendTemperature(1));
|
||||
progressStatus.Progress0To1 = (heatDistance - currentDistance) / heatDistance;
|
||||
progressStatus.Status = $"Heating Nozzle ({printerConnection.GetActualHotendTemperature(1):0}/{printerConnection.GetTargetHotendTemperature(1):0})";
|
||||
reporter.Report(progressStatus);
|
||||
var progress0To1 = (heatDistance - currentDistance) / heatDistance;
|
||||
var status = $"Heating Nozzle ({printerConnection.GetActualHotendTemperature(1):0}/{printerConnection.GetTargetHotendTemperature(1):0})";
|
||||
reporter?.Invoke(progress0To1, status);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,49 +44,52 @@ using System.Threading.Tasks;
|
|||
namespace MatterHackers.MatterControl
|
||||
{
|
||||
public class RunningTasksConfig
|
||||
{
|
||||
public event EventHandler TasksChanged;
|
||||
{
|
||||
public event EventHandler TasksChanged;
|
||||
|
||||
private ObservableCollection<RunningTaskDetails> executingTasks = new ObservableCollection<RunningTaskDetails>();
|
||||
private ObservableCollection<RunningTaskDetails> executingTasks = new ObservableCollection<RunningTaskDetails>();
|
||||
|
||||
public IEnumerable<RunningTaskDetails> RunningTasks => executingTasks.ToList();
|
||||
public IEnumerable<RunningTaskDetails> RunningTasks => executingTasks.ToList();
|
||||
|
||||
public RunningTasksConfig()
|
||||
{
|
||||
executingTasks.CollectionChanged += (s, e) =>
|
||||
{
|
||||
UiThread.RunOnIdle(() => this.TasksChanged?.Invoke(this, null));
|
||||
};
|
||||
}
|
||||
public RunningTasksConfig()
|
||||
{
|
||||
executingTasks.CollectionChanged += (s, e) =>
|
||||
{
|
||||
UiThread.RunOnIdle(() => this.TasksChanged?.Invoke(this, null));
|
||||
};
|
||||
}
|
||||
|
||||
public Task Execute(string taskTitle, object owner, Func<IProgress<ProgressStatus>, CancellationTokenSource, Task> func, RunningTaskOptions taskActions = null)
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
public Task Execute(string taskTitle, object owner, Func<Action<double, string>, CancellationTokenSource, Task> func, RunningTaskOptions taskActions = null)
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
|
||||
var taskDetails = new RunningTaskDetails(tokenSource)
|
||||
{
|
||||
Options = taskActions,
|
||||
Title = taskTitle,
|
||||
Owner = owner,
|
||||
};
|
||||
var taskDetails = new RunningTaskDetails(tokenSource)
|
||||
{
|
||||
Options = taskActions,
|
||||
Title = taskTitle,
|
||||
Owner = owner,
|
||||
};
|
||||
|
||||
executingTasks.Add(taskDetails);
|
||||
executingTasks.Add(taskDetails);
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await func?.Invoke(taskDetails, tokenSource);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await func?.Invoke((ratio, message) =>
|
||||
{
|
||||
taskDetails.Report(ratio, message);
|
||||
}, tokenSource);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
executingTasks.Remove(taskDetails);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
executingTasks.Remove(taskDetails);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
Task LoadLibraryContent(ILibraryItem libraryItem, Action<double, string> progressReporter);
|
||||
|
||||
Task SaveChanges(IProgress<ProgressStatus> progress, CancellationTokenSource cancellationToken);
|
||||
Task SaveChanges(Action<double, string> progress, CancellationTokenSource cancellationToken);
|
||||
|
||||
bool HadSaveError { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ using Newtonsoft.Json.Linq;
|
|||
namespace MatterHackers.MatterControl
|
||||
{
|
||||
|
||||
public class RunningTaskDetails : IProgress<ProgressStatus>
|
||||
public class RunningTaskDetails
|
||||
{
|
||||
public event EventHandler<ProgressStatus> ProgressChanged;
|
||||
public event EventHandler<(double ratio, string message)> ProgressChanged;
|
||||
|
||||
public Func<GuiWidget> DetailsItemAction { get; set; }
|
||||
public Func<GuiWidget> DetailsItemAction { get; set; }
|
||||
|
||||
private CancellationTokenSource tokenSource;
|
||||
|
||||
|
|
@ -139,12 +139,12 @@ namespace MatterHackers.MatterControl
|
|||
}
|
||||
}
|
||||
|
||||
public void Report(ProgressStatus progressStatus)
|
||||
public void Report(double ratio, string message)
|
||||
{
|
||||
this.ProgressChanged?.Invoke(this, progressStatus);
|
||||
}
|
||||
this.ProgressChanged?.Invoke(this, (ratio, message));
|
||||
}
|
||||
|
||||
public void CancelTask()
|
||||
public void CancelTask()
|
||||
{
|
||||
this.tokenSource.Cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2018, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -43,6 +43,7 @@ using MatterHackers.ImageProcessing;
|
|||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.DesignTools;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.MatterControl.DesignTools.Primitives;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow.View3D;
|
||||
using MatterHackers.MatterControl.SettingsManagement;
|
||||
|
|
@ -1171,7 +1172,7 @@ namespace MatterHackers.MatterControl
|
|||
{
|
||||
try
|
||||
{
|
||||
var updateItems = SheetObject3D.SortAndLockUpdateItems(selectedItem.Parent, (item) =>
|
||||
var updateItems = Expressions.SortAndLockUpdateItems(selectedItem.Parent, (item) =>
|
||||
{
|
||||
if (item == selectedItem || item.Parent == selectedItem)
|
||||
{
|
||||
|
|
@ -1181,7 +1182,7 @@ namespace MatterHackers.MatterControl
|
|||
return true;
|
||||
}, false);
|
||||
|
||||
SheetObject3D.SendInvalidateInRebuildOrder(updateItems, InvalidateType.Properties, null);
|
||||
Expressions.SendInvalidateInRebuildOrder(updateItems, InvalidateType.Properties, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -1533,4 +1534,10 @@ namespace MatterHackers.MatterControl
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPrimaryOperationsSpecifier
|
||||
{
|
||||
IEnumerable<SceneOperation> GetOperations();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -38,175 +38,175 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.CustomWidgets.ColorPicker
|
||||
{
|
||||
public class RadialColorPicker : GuiWidget
|
||||
{
|
||||
private (double h, double s, double l) hsl;
|
||||
private float alpha;
|
||||
private Color downColor;
|
||||
public class RadialColorPicker : GuiWidget
|
||||
{
|
||||
private (double h, double s, double l) hsl;
|
||||
private float alpha;
|
||||
private Color downColor;
|
||||
|
||||
private enum DownState
|
||||
{
|
||||
None,
|
||||
OnRing,
|
||||
OnTriangle,
|
||||
}
|
||||
private enum DownState
|
||||
{
|
||||
None,
|
||||
OnRing,
|
||||
OnTriangle,
|
||||
}
|
||||
|
||||
private DownState downState = DownState.None;
|
||||
private DownState downState = DownState.None;
|
||||
|
||||
public RadialColorPicker()
|
||||
{
|
||||
BackgroundColor = Color.White;
|
||||
public RadialColorPicker()
|
||||
{
|
||||
BackgroundColor = Color.White;
|
||||
|
||||
this.Width = 100;
|
||||
this.Height = 100;
|
||||
}
|
||||
this.Width = 100;
|
||||
this.Height = 100;
|
||||
}
|
||||
|
||||
public event EventHandler IncrementalColorChanged;
|
||||
public event EventHandler IncrementalColorChanged;
|
||||
|
||||
|
||||
public event EventHandler SelectedColorChanged;
|
||||
public event EventHandler SelectedColorChanged;
|
||||
|
||||
public double RingWidth { get => Width / 10; }
|
||||
public double RingWidth { get => Width / 10; }
|
||||
|
||||
public void SetColorWithoutChangeEvent(Color color)
|
||||
{
|
||||
color.ToColorF().GetHSL(out double h, out double s, out double l);
|
||||
// if the color is not white or black, set the angle (otherwise leave it where it is)
|
||||
if ((color.red != color.green || color.green != color.blue || color.blue != 0)
|
||||
&& (color.red != color.green || color.green != color.blue || color.blue != 255))
|
||||
{
|
||||
hsl.h = h;
|
||||
}
|
||||
hsl.s = s;
|
||||
hsl.l = l;
|
||||
alpha = color.Alpha0To1;
|
||||
public void SetColorWithoutChangeEvent(Color color)
|
||||
{
|
||||
color.ToColorF().GetHSL(out double h, out double s, out double l);
|
||||
// if the color is not white or black, set the angle (otherwise leave it where it is)
|
||||
if ((color.red != color.green || color.green != color.blue || color.blue != 0)
|
||||
&& (color.red != color.green || color.green != color.blue || color.blue != 255))
|
||||
{
|
||||
hsl.h = h;
|
||||
}
|
||||
hsl.s = s;
|
||||
hsl.l = l;
|
||||
alpha = color.Alpha0To1;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public override void OnKeyDown(KeyEventArgs keyEvent)
|
||||
{
|
||||
if (downState != DownState.None
|
||||
&& keyEvent.KeyCode == Keys.Escape)
|
||||
{
|
||||
downState = DownState.None;
|
||||
SelectedColor = downColor;
|
||||
}
|
||||
public override void OnKeyDown(KeyEventArgs keyEvent)
|
||||
{
|
||||
if (downState != DownState.None
|
||||
&& keyEvent.KeyCode == Keys.Escape)
|
||||
{
|
||||
downState = DownState.None;
|
||||
SelectedColor = downColor;
|
||||
}
|
||||
|
||||
base.OnKeyDown(keyEvent);
|
||||
}
|
||||
base.OnKeyDown(keyEvent);
|
||||
}
|
||||
|
||||
public Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorF.FromHSL(hsl.h, hsl.s, hsl.l, alpha).ToColor();
|
||||
}
|
||||
public Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorF.FromHSL(hsl.h, hsl.s, hsl.l, alpha).ToColor();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != SelectedColor)
|
||||
{
|
||||
SetColorWithoutChangeEvent(value);
|
||||
set
|
||||
{
|
||||
if (value != SelectedColor)
|
||||
{
|
||||
SetColorWithoutChangeEvent(value);
|
||||
|
||||
SelectedColorChanged?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedColorChanged?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Color SelectedHueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorF.FromHSL(hsl.h, 1, .5).ToColor();
|
||||
}
|
||||
public Color SelectedHueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ColorF.FromHSL(hsl.h, 1, .5).ToColor();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
value.ToColorF().GetHSL(out double h, out _, out _);
|
||||
hsl.h = h;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
value.ToColorF().GetHSL(out double h, out _, out _);
|
||||
hsl.h = h;
|
||||
}
|
||||
}
|
||||
|
||||
private double InnerRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return RingRadius - RingWidth / 2;
|
||||
}
|
||||
}
|
||||
private double InnerRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return RingRadius - RingWidth / 2;
|
||||
}
|
||||
}
|
||||
|
||||
private double RingRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return Width / 2 - RingWidth / 2 - 2;
|
||||
}
|
||||
}
|
||||
private double RingRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return Width / 2 - RingWidth / 2 - 2;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDraw(Graphics2D graphics2D)
|
||||
{
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
public override void OnDraw(Graphics2D graphics2D)
|
||||
{
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
|
||||
// draw the big outside ring (color part)
|
||||
DrawColorRing(graphics2D, RingRadius, RingWidth);
|
||||
// draw the big outside ring (color part)
|
||||
DrawColorRing(graphics2D, RingRadius, RingWidth);
|
||||
|
||||
// draw the inner triangle (color part)
|
||||
DrawColorTriangle(graphics2D, InnerRadius, SelectedHueColor);
|
||||
// draw the inner triangle (color part)
|
||||
DrawColorTriangle(graphics2D, InnerRadius, SelectedHueColor);
|
||||
|
||||
// draw the big ring outline
|
||||
graphics2D.Ring(center, RingRadius + RingWidth / 2, 1, Color.Black);
|
||||
graphics2D.Ring(center, RingRadius - RingWidth / 2, 1, Color.Black);
|
||||
// draw the big ring outline
|
||||
graphics2D.Ring(center, RingRadius + RingWidth / 2, 1, Color.Black);
|
||||
graphics2D.Ring(center, RingRadius - RingWidth / 2, 1, Color.Black);
|
||||
|
||||
// draw the triangle outline
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
graphics2D.Line(GetTrianglePoint(0, InnerRadius, colorAngle), GetTrianglePoint(1, InnerRadius, colorAngle), Color.Black);
|
||||
graphics2D.Line(GetTrianglePoint(1, InnerRadius, colorAngle), GetTrianglePoint(2, InnerRadius, colorAngle), Color.Black);
|
||||
graphics2D.Line(GetTrianglePoint(2, InnerRadius, colorAngle), GetTrianglePoint(0, InnerRadius, colorAngle), Color.Black);
|
||||
// draw the triangle outline
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
graphics2D.Line(GetTrianglePoint(0, InnerRadius, colorAngle), GetTrianglePoint(1, InnerRadius, colorAngle), Color.Black);
|
||||
graphics2D.Line(GetTrianglePoint(1, InnerRadius, colorAngle), GetTrianglePoint(2, InnerRadius, colorAngle), Color.Black);
|
||||
graphics2D.Line(GetTrianglePoint(2, InnerRadius, colorAngle), GetTrianglePoint(0, InnerRadius, colorAngle), Color.Black);
|
||||
|
||||
// draw the color circle on the triangle
|
||||
var unitPosition = new Vector2(hsl.s, hsl.l);
|
||||
unitPosition.Y = agg_basics.Clamp(unitPosition.Y, .5 - (1 - unitPosition.X) / 2, .5 + (1 - unitPosition.X) / 2);
|
||||
// draw the color circle on the triangle
|
||||
var unitPosition = new Vector2(hsl.s, hsl.l);
|
||||
unitPosition.Y = Util.Clamp(unitPosition.Y, .5 - (1 - unitPosition.X) / 2, .5 + (1 - unitPosition.X) / 2);
|
||||
|
||||
var triangleColorCenter = TriangleToWidgetTransform().Transform(unitPosition);
|
||||
graphics2D.Circle(triangleColorCenter, RingWidth / 2 - 2, new Color(SelectedColor, 255));
|
||||
graphics2D.Ring(triangleColorCenter, RingWidth / 2 - 2, 2, Color.White);
|
||||
var triangleColorCenter = TriangleToWidgetTransform().Transform(unitPosition);
|
||||
graphics2D.Circle(triangleColorCenter, RingWidth / 2 - 2, new Color(SelectedColor, 255));
|
||||
graphics2D.Ring(triangleColorCenter, RingWidth / 2 - 2, 2, Color.White);
|
||||
|
||||
// draw the color circle on the ring
|
||||
var ringColorCenter = center + Vector2.Rotate(new Vector2(RingRadius, 0), colorAngle);
|
||||
graphics2D.Circle(ringColorCenter,
|
||||
RingWidth / 2 - 2,
|
||||
SelectedHueColor);
|
||||
graphics2D.Ring(ringColorCenter,
|
||||
RingWidth / 2 - 2,
|
||||
2,
|
||||
Color.White);
|
||||
// draw the color circle on the ring
|
||||
var ringColorCenter = center + Vector2.Rotate(new Vector2(RingRadius, 0), colorAngle);
|
||||
graphics2D.Circle(ringColorCenter,
|
||||
RingWidth / 2 - 2,
|
||||
SelectedHueColor);
|
||||
graphics2D.Ring(ringColorCenter,
|
||||
RingWidth / 2 - 2,
|
||||
2,
|
||||
Color.White);
|
||||
|
||||
base.OnDraw(graphics2D);
|
||||
}
|
||||
base.OnDraw(graphics2D);
|
||||
}
|
||||
|
||||
public override void OnMouseDown(MouseEventArgs mouseEvent)
|
||||
{
|
||||
downColor = SelectedColor;
|
||||
public override void OnMouseDown(MouseEventArgs mouseEvent)
|
||||
{
|
||||
downColor = SelectedColor;
|
||||
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
var direction = mouseEvent.Position - center;
|
||||
var startColor = SelectedColor;
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
var direction = mouseEvent.Position - center;
|
||||
var startColor = SelectedColor;
|
||||
|
||||
if (mouseEvent.Button == MouseButtons.Left)
|
||||
{
|
||||
if (direction.Length > RingRadius - RingWidth / 2
|
||||
&& direction.Length < RingRadius + RingWidth / 2)
|
||||
{
|
||||
downState = DownState.OnRing;
|
||||
if (mouseEvent.Button == MouseButtons.Left)
|
||||
{
|
||||
if (direction.Length > RingRadius - RingWidth / 2
|
||||
&& direction.Length < RingRadius + RingWidth / 2)
|
||||
{
|
||||
downState = DownState.OnRing;
|
||||
|
||||
var colorAngle = Math.Atan2(direction.Y, direction.X);
|
||||
if (colorAngle < 0)
|
||||
{
|
||||
colorAngle += MathHelper.Tau;
|
||||
}
|
||||
var colorAngle = Math.Atan2(direction.Y, direction.X);
|
||||
if (colorAngle < 0)
|
||||
{
|
||||
colorAngle += MathHelper.Tau;
|
||||
}
|
||||
|
||||
hsl.h = colorAngle / MathHelper.Tau;
|
||||
hsl.h = colorAngle / MathHelper.Tau;
|
||||
|
||||
if (hsl.s == 0)
|
||||
{
|
||||
|
|
@ -215,34 +215,34 @@ namespace MatterHackers.MatterControl.CustomWidgets.ColorPicker
|
|||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
var (inside, position) = WidgetToUnitTriangle(mouseEvent.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (inside, position) = WidgetToUnitTriangle(mouseEvent.Position);
|
||||
|
||||
if (inside)
|
||||
{
|
||||
downState = DownState.OnTriangle;
|
||||
SetSLFromUnitPosition(position);
|
||||
}
|
||||
if (inside)
|
||||
{
|
||||
downState = DownState.OnTriangle;
|
||||
SetSLFromUnitPosition(position);
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (startColor != SelectedColor)
|
||||
{
|
||||
IncrementalColorChanged?.Invoke(this, null);
|
||||
}
|
||||
if (startColor != SelectedColor)
|
||||
{
|
||||
IncrementalColorChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
base.OnMouseDown(mouseEvent);
|
||||
}
|
||||
base.OnMouseDown(mouseEvent);
|
||||
}
|
||||
|
||||
private void SetSLFromUnitPosition(Vector2 position)
|
||||
{
|
||||
hsl.s = position.X;
|
||||
hsl.l = position.Y;
|
||||
/*
|
||||
private void SetSLFromUnitPosition(Vector2 position)
|
||||
{
|
||||
hsl.s = position.X;
|
||||
hsl.l = position.Y;
|
||||
/*
|
||||
if (hsl.l > hsl.s)
|
||||
{
|
||||
|
||||
|
|
@ -251,183 +251,183 @@ namespace MatterHackers.MatterControl.CustomWidgets.ColorPicker
|
|||
agg_basics.Clamp(unitPosition.Y, .5 - (1 - unitPosition.X) / 2, .5 + (1 - unitPosition.X) / 2);
|
||||
unitTrianglePosition = position;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMouseMove(MouseEventArgs mouseEvent)
|
||||
{
|
||||
var startColor = SelectedColor;
|
||||
public override void OnMouseMove(MouseEventArgs mouseEvent)
|
||||
{
|
||||
var startColor = SelectedColor;
|
||||
|
||||
switch (downState)
|
||||
{
|
||||
case DownState.OnRing:
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
switch (downState)
|
||||
{
|
||||
case DownState.OnRing:
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
|
||||
var direction = mouseEvent.Position - center;
|
||||
var colorAngle = Math.Atan2(direction.Y, direction.X);
|
||||
if (colorAngle < 0)
|
||||
{
|
||||
colorAngle += MathHelper.Tau;
|
||||
}
|
||||
hsl.h = colorAngle / MathHelper.Tau;
|
||||
var direction = mouseEvent.Position - center;
|
||||
var colorAngle = Math.Atan2(direction.Y, direction.X);
|
||||
if (colorAngle < 0)
|
||||
{
|
||||
colorAngle += MathHelper.Tau;
|
||||
}
|
||||
hsl.h = colorAngle / MathHelper.Tau;
|
||||
|
||||
Invalidate();
|
||||
break;
|
||||
Invalidate();
|
||||
break;
|
||||
|
||||
case DownState.OnTriangle:
|
||||
SetSLFromUnitPosition(WidgetToUnitTriangle(mouseEvent.Position).position);
|
||||
Invalidate();
|
||||
break;
|
||||
}
|
||||
case DownState.OnTriangle:
|
||||
SetSLFromUnitPosition(WidgetToUnitTriangle(mouseEvent.Position).position);
|
||||
Invalidate();
|
||||
break;
|
||||
}
|
||||
|
||||
if (startColor != SelectedColor)
|
||||
{
|
||||
IncrementalColorChanged?.Invoke(this, null);
|
||||
}
|
||||
if (startColor != SelectedColor)
|
||||
{
|
||||
IncrementalColorChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
base.OnMouseMove(mouseEvent);
|
||||
}
|
||||
base.OnMouseMove(mouseEvent);
|
||||
}
|
||||
|
||||
public override void OnMouseUp(MouseEventArgs mouseEvent)
|
||||
{
|
||||
downState = DownState.None;
|
||||
public override void OnMouseUp(MouseEventArgs mouseEvent)
|
||||
{
|
||||
downState = DownState.None;
|
||||
|
||||
if (downColor != SelectedColor)
|
||||
{
|
||||
SelectedColorChanged?.Invoke(this, null);
|
||||
}
|
||||
if (downColor != SelectedColor)
|
||||
{
|
||||
SelectedColorChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
base.OnMouseUp(mouseEvent);
|
||||
}
|
||||
base.OnMouseUp(mouseEvent);
|
||||
}
|
||||
|
||||
private void DrawColorRing(Graphics2D graphics2D, double radius, double width)
|
||||
{
|
||||
if (graphics2D is Graphics2DOpenGL graphicsGL)
|
||||
{
|
||||
graphicsGL.PushOrthoProjection();
|
||||
private void DrawColorRing(Graphics2D graphics2D, double radius, double width)
|
||||
{
|
||||
if (graphics2D is Graphics2DOpenGL graphicsGL)
|
||||
{
|
||||
graphicsGL.PushOrthoProjection();
|
||||
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
|
||||
var outer = radius + width / 2;
|
||||
var inner = radius - width / 2;
|
||||
GL.Begin(BeginMode.TriangleStrip);
|
||||
var outer = radius + width / 2;
|
||||
var inner = radius - width / 2;
|
||||
GL.Begin(BeginMode.TriangleStrip);
|
||||
|
||||
for (int i = 0; i <= 360; i++)
|
||||
{
|
||||
var color = ColorF.FromHSL(i / 360.0, 1, .5);
|
||||
var angle = MathHelper.DegreesToRadians(i);
|
||||
for (int i = 0; i <= 360; i++)
|
||||
{
|
||||
var color = ColorF.FromHSL(i / 360.0, 1, .5);
|
||||
var angle = MathHelper.DegreesToRadians(i);
|
||||
|
||||
GL.Color4(color.Red0To255, color.Green0To255, color.Blue0To255, color.Alpha0To255);
|
||||
GL.Vertex2(GetAtAngle(angle, outer, true));
|
||||
GL.Vertex2(GetAtAngle(angle, inner, true));
|
||||
}
|
||||
GL.Color4(color.Red0To255, color.Green0To255, color.Blue0To255, color.Alpha0To255);
|
||||
GL.Vertex2(GetAtAngle(angle, outer, true));
|
||||
GL.Vertex2(GetAtAngle(angle, inner, true));
|
||||
}
|
||||
|
||||
GL.End();
|
||||
GL.End();
|
||||
|
||||
graphicsGL.PopOrthoProjection();
|
||||
}
|
||||
}
|
||||
graphicsGL.PopOrthoProjection();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColorTriangle(Graphics2D graphics2D, double radius, Color color)
|
||||
{
|
||||
if (graphics2D is Graphics2DOpenGL graphicsGL)
|
||||
{
|
||||
graphicsGL.PushOrthoProjection();
|
||||
private void DrawColorTriangle(Graphics2D graphics2D, double radius, Color color)
|
||||
{
|
||||
if (graphics2D is Graphics2DOpenGL graphicsGL)
|
||||
{
|
||||
graphicsGL.PushOrthoProjection();
|
||||
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
|
||||
GL.Begin(BeginMode.Triangles);
|
||||
GL.Color4(color.Red0To255, color.Green0To255, color.Blue0To255, color.Alpha0To255);
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
GL.Vertex2(GetTrianglePoint(0, radius, colorAngle, true));
|
||||
GL.Color4(Color.White);
|
||||
GL.Vertex2(GetTrianglePoint(1, radius, colorAngle, true));
|
||||
GL.Color4(Color.Black);
|
||||
GL.Vertex2(GetTrianglePoint(2, radius, colorAngle, true));
|
||||
GL.Begin(BeginMode.Triangles);
|
||||
GL.Color4(color.Red0To255, color.Green0To255, color.Blue0To255, color.Alpha0To255);
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
GL.Vertex2(GetTrianglePoint(0, radius, colorAngle, true));
|
||||
GL.Color4(Color.White);
|
||||
GL.Vertex2(GetTrianglePoint(1, radius, colorAngle, true));
|
||||
GL.Color4(Color.Black);
|
||||
GL.Vertex2(GetTrianglePoint(2, radius, colorAngle, true));
|
||||
|
||||
GL.End();
|
||||
GL.End();
|
||||
|
||||
graphicsGL.PopOrthoProjection();
|
||||
}
|
||||
}
|
||||
graphicsGL.PopOrthoProjection();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetAtAngle(double angle, double radius, bool screenSpace)
|
||||
{
|
||||
var start = new Vector2(radius, 0);
|
||||
private Vector2 GetAtAngle(double angle, double radius, bool screenSpace)
|
||||
{
|
||||
var start = new Vector2(radius, 0);
|
||||
|
||||
var position = default(Vector2);
|
||||
if (screenSpace)
|
||||
{
|
||||
position = this.TransformToScreenSpace(this.Position);
|
||||
}
|
||||
var position = default(Vector2);
|
||||
if (screenSpace)
|
||||
{
|
||||
position = this.TransformToScreenSpace(this.Position);
|
||||
}
|
||||
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
return position + center + Vector2.Rotate(start, angle);
|
||||
}
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
return position + center + Vector2.Rotate(start, angle);
|
||||
}
|
||||
|
||||
private Vector2 GetTrianglePoint(int index, double radius, double pontingAngle, bool screenSpace = false)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return GetAtAngle(pontingAngle, radius, screenSpace);
|
||||
private Vector2 GetTrianglePoint(int index, double radius, double pontingAngle, bool screenSpace = false)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return GetAtAngle(pontingAngle, radius, screenSpace);
|
||||
|
||||
case 1:
|
||||
return GetAtAngle(pontingAngle + MathHelper.DegreesToRadians(120), radius, screenSpace);
|
||||
case 1:
|
||||
return GetAtAngle(pontingAngle + MathHelper.DegreesToRadians(120), radius, screenSpace);
|
||||
|
||||
case 2:
|
||||
return GetAtAngle(pontingAngle + MathHelper.DegreesToRadians(240), radius, screenSpace);
|
||||
}
|
||||
case 2:
|
||||
return GetAtAngle(pontingAngle + MathHelper.DegreesToRadians(240), radius, screenSpace);
|
||||
}
|
||||
|
||||
return Vector2.Zero;
|
||||
}
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
private Affine TriangleToWidgetTransform()
|
||||
{
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
var leftSize = .5;
|
||||
var sizeToTop = Math.Sin(MathHelper.DegreesToRadians(60));
|
||||
private Affine TriangleToWidgetTransform()
|
||||
{
|
||||
var center = new Vector2(Width / 2, Height / 2);
|
||||
var leftSize = .5;
|
||||
var sizeToTop = Math.Sin(MathHelper.DegreesToRadians(60));
|
||||
|
||||
Affine total = Affine.NewIdentity();
|
||||
// scale to -1 to 1 coordinates
|
||||
total *= Affine.NewScaling(1 + leftSize, sizeToTop * 2);
|
||||
// center
|
||||
total *= Affine.NewTranslation(-leftSize, -sizeToTop);
|
||||
// rotate to correct color
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
total *= Affine.NewRotation(colorAngle);
|
||||
// scale to radius
|
||||
total *= Affine.NewScaling(InnerRadius);
|
||||
// move to center
|
||||
total *= Affine.NewTranslation(center);
|
||||
return total;
|
||||
}
|
||||
Affine total = Affine.NewIdentity();
|
||||
// scale to -1 to 1 coordinates
|
||||
total *= Affine.NewScaling(1 + leftSize, sizeToTop * 2);
|
||||
// center
|
||||
total *= Affine.NewTranslation(-leftSize, -sizeToTop);
|
||||
// rotate to correct color
|
||||
var colorAngle = hsl.h * MathHelper.Tau;
|
||||
total *= Affine.NewRotation(colorAngle);
|
||||
// scale to radius
|
||||
total *= Affine.NewScaling(InnerRadius);
|
||||
// move to center
|
||||
total *= Affine.NewTranslation(center);
|
||||
return total;
|
||||
}
|
||||
|
||||
private (bool inside, Vector2 position) WidgetToUnitTriangle(Vector2 widgetPosition)
|
||||
{
|
||||
var trianglePosition = TriangleToWidgetTransform()
|
||||
.InverseTransform(widgetPosition);
|
||||
private (bool inside, Vector2 position) WidgetToUnitTriangle(Vector2 widgetPosition)
|
||||
{
|
||||
var trianglePosition = TriangleToWidgetTransform()
|
||||
.InverseTransform(widgetPosition);
|
||||
|
||||
bool changed = ClampTrianglePosition(ref trianglePosition);
|
||||
bool changed = ClampTrianglePosition(ref trianglePosition);
|
||||
|
||||
return (!changed, trianglePosition);
|
||||
}
|
||||
return (!changed, trianglePosition);
|
||||
}
|
||||
|
||||
private static bool ClampTrianglePosition(ref Vector2 trianglePosition)
|
||||
{
|
||||
bool changed = false;
|
||||
trianglePosition.X = agg_basics.Clamp(trianglePosition.X, 0, 1, ref changed);
|
||||
trianglePosition.Y = agg_basics.Clamp(trianglePosition.Y, 0, 1, ref changed);
|
||||
private static bool ClampTrianglePosition(ref Vector2 trianglePosition)
|
||||
{
|
||||
bool changed = false;
|
||||
trianglePosition.X = Util.Clamp(trianglePosition.X, 0, 1, ref changed);
|
||||
trianglePosition.Y = Util.Clamp(trianglePosition.Y, 0, 1, ref changed);
|
||||
|
||||
trianglePosition.Y = agg_basics.Clamp(trianglePosition.Y,
|
||||
.5 - (1 - trianglePosition.X) / 2,
|
||||
.5 + (1 - trianglePosition.X) / 2,
|
||||
ref changed);
|
||||
trianglePosition.Y = Util.Clamp(trianglePosition.Y,
|
||||
.5 - (1 - trianglePosition.X) / 2,
|
||||
.5 + (1 - trianglePosition.X) / 2,
|
||||
ref changed);
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2019, John Lewin
|
||||
Copyright (c) 2023, John Lewin, Lars Brubaker
|
||||
|
||||
All rights reserved.
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ namespace MatterHackers.Agg.UI
|
|||
activeButtonID = InitialSelectionIndex;
|
||||
}
|
||||
|
||||
activeButtonID = agg_basics.Clamp(activeButtonID, 0, this.Operations.Count - 1);
|
||||
activeButtonID = Util.Clamp(activeButtonID, 0, this.Operations.Count - 1);
|
||||
|
||||
return this.Operations[activeButtonID];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2018, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -29,18 +29,18 @@ either expressed or implied, of the FreeBSD Project.
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the height of the geometry that shows the shape of a path object (it cannot be used in mesh operations).
|
||||
/// </summary>
|
||||
public static double PathPolygonsHeight => .2;
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the height of the geometry that shows the shape of a path object (it cannot be used in mesh operations).
|
||||
/// </summary>
|
||||
public static double PathPolygonsHeight => .2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alpha of the non z-buffered lines in the editor
|
||||
/// </summary>
|
||||
public static int LineAlpha => 80;
|
||||
/// <summary>
|
||||
/// Gets the alpha of the non z-buffered lines in the editor
|
||||
/// </summary>
|
||||
public static int LineAlpha => 80;
|
||||
|
||||
public static int Controls3DAlpha => 80;
|
||||
}
|
||||
public static int Controls3DAlpha => 80;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
{
|
||||
var applicationController = ApplicationController.Instance;
|
||||
|
||||
var primitives = new PrimitivesContainer();
|
||||
var primitives = new Primitives3DContainer();
|
||||
primitives.Load();
|
||||
|
||||
foreach (var item in primitives.Items.OfType<ILibraryObject3D>())
|
||||
|
|
|
|||
571
MatterControlLib/DesignTools/Expressions.cs
Normal file
571
MatterControlLib/DesignTools/Expressions.cs
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
Copyright (c) 2023, 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.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using org.mariuszgromada.math.mxparser;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using static MatterHackers.MatterControl.DesignTools.SheetObject3D;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public static class Expressions
|
||||
{
|
||||
public const BindingFlags OwnedPropertiesOnly = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
|
||||
|
||||
private static readonly Regex ConstantFinder = new Regex("(?<=\\[).+?(?=\\])", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Type[] ExpressionTypes =
|
||||
{
|
||||
typeof(StringOrExpression),
|
||||
typeof(DoubleOrExpression),
|
||||
typeof(IntOrExpression),
|
||||
};
|
||||
|
||||
private static Dictionary<string, Func<IObject3D, double>> constants = new Dictionary<string, Func<IObject3D, double>>()
|
||||
{
|
||||
// length
|
||||
["cm"] = (owner) => 10,
|
||||
["m"] = (owner) => 1000,
|
||||
["inch"] = (owner) => 25.4,
|
||||
["ft"] = (owner) => 304.8,
|
||||
// math constant
|
||||
["pi"] = (owner) => Math.PI,
|
||||
["tau"] = (owner) => Math.PI * 2,
|
||||
["e"] = (owner) => Math.E,
|
||||
// functions
|
||||
["rand"] = (owner) => rand.NextDouble(),
|
||||
// array function
|
||||
["index"] = (owner) => RetrieveArrayIndex(owner, 0),
|
||||
["index0"] = (owner) => RetrieveArrayIndex(owner, 0),
|
||||
["index1"] = (owner) => RetrieveArrayIndex(owner, 1),
|
||||
["index2"] = (owner) => RetrieveArrayIndex(owner, 2),
|
||||
};
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
public static T CastResult<T>(string value, string inputExpression)
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
// if parsing the equation resulted in NaN as the output
|
||||
if (value == "NaN")
|
||||
{
|
||||
// return the actual expression
|
||||
return (T)(object)inputExpression;
|
||||
}
|
||||
|
||||
// get the value of the cell
|
||||
return (T)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
if (double.TryParse(value, out double doubleValue)
|
||||
&& !double.IsNaN(doubleValue)
|
||||
&& !double.IsInfinity(doubleValue))
|
||||
{
|
||||
return (T)(object)doubleValue;
|
||||
}
|
||||
// else return an error
|
||||
return (T)(object).1;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
if (double.TryParse(value, out double doubleValue)
|
||||
&& !double.IsNaN(doubleValue)
|
||||
&& !double.IsInfinity(doubleValue))
|
||||
{
|
||||
return (T)(object)(int)Math.Round(doubleValue);
|
||||
}
|
||||
// else return an error
|
||||
return (T)(object)1;
|
||||
}
|
||||
|
||||
return (T)(object)default(T);
|
||||
}
|
||||
|
||||
public static T EvaluateExpression<T>(IObject3D owner, string inExpression)
|
||||
{
|
||||
var inputExpression = inExpression;
|
||||
|
||||
inputExpression = SearchSiblingProperties(owner, inputExpression);
|
||||
|
||||
inputExpression = ReplaceConstantsWithValues(owner, inputExpression);
|
||||
|
||||
// check if the expression is an equation (starts with "=")
|
||||
if (inputExpression.Length > 0 && inputExpression[0] == '=')
|
||||
{
|
||||
// look through all the parents
|
||||
var variableContainer = FindFirstVariableContainer(owner);
|
||||
if (variableContainer != null)
|
||||
{
|
||||
// try to manage the cell into the correct data type
|
||||
string value = variableContainer.EvaluateExpression(inputExpression);
|
||||
return CastResult<T>(value, inputExpression);
|
||||
}
|
||||
|
||||
// could not find a sheet, try to evaluate the expression directly
|
||||
var evaluator = new Expression(inputExpression.Substring(1).ToLower());
|
||||
if (evaluator.checkSyntax())
|
||||
{
|
||||
Debug.WriteLine(evaluator.getErrorMessage());
|
||||
}
|
||||
|
||||
return CastResult<T>(evaluator.calculate().ToString(), inputExpression);
|
||||
}
|
||||
else // not an equation so try to parse it directly
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)(object)inputExpression;
|
||||
}
|
||||
|
||||
double.TryParse(inputExpression, out var result);
|
||||
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
return (T)(object)result;
|
||||
}
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return (T)(object)(int)Math.Round(result);
|
||||
}
|
||||
|
||||
return (T)(object)0;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectOrExpression> GetActiveExpression(IObject3D item, string checkForString, bool startsWith)
|
||||
{
|
||||
foreach (var property in PublicPropertyEditor.GetEditablePropreties(item))
|
||||
{
|
||||
var propertyValue = property.Value;
|
||||
|
||||
if (propertyValue is DirectOrExpression directOrExpression)
|
||||
{
|
||||
if (startsWith)
|
||||
{
|
||||
if (directOrExpression.Expression.StartsWith(checkForString))
|
||||
{
|
||||
// WIP: check if the value has actually changed, this will update every object on any cell change
|
||||
yield return directOrExpression;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (directOrExpression.Expression.Contains(checkForString))
|
||||
{
|
||||
yield return directOrExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<int> GetComponentExpressions(ComponentObject3D component, string checkForString, bool startsWith)
|
||||
{
|
||||
for (var i = 0; i < component.SurfacedEditors.Count; i++)
|
||||
{
|
||||
var (cellId, cellData) = component.DecodeContent(i);
|
||||
|
||||
if (cellId != null)
|
||||
{
|
||||
if (startsWith)
|
||||
{
|
||||
if (cellData.StartsWith(checkForString))
|
||||
{
|
||||
// WIP: check if the value has actually changed, this will update every object on any cell change
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cellData.Contains(checkForString))
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<EditableProperty> GetExpressionPropreties(IObject3D item)
|
||||
{
|
||||
return item.GetType().GetProperties(OwnedPropertiesOnly)
|
||||
.Where(pi => ExpressionTypes.Contains(pi.PropertyType)
|
||||
&& pi.GetGetMethod() != null
|
||||
&& pi.GetSetMethod() != null)
|
||||
.Select(p => new EditableProperty(p, item));
|
||||
}
|
||||
|
||||
public static bool HasExpressionWithString(IObject3D itemToCheck, string checkForString, bool startsWith)
|
||||
{
|
||||
foreach (var item in itemToCheck.DescendantsAndSelf())
|
||||
{
|
||||
if (GetActiveExpression(item, checkForString, startsWith).Any()
|
||||
|| itemToCheck is ComponentObject3D component
|
||||
&& GetComponentExpressions(component, checkForString, startsWith).Any())
|
||||
{
|
||||
// three is one so return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there are any references from the item to the sheet.
|
||||
/// </summary>
|
||||
/// <param name="itemToCheck">The item to validate editable properties on</param>
|
||||
/// <param name="sheetToCheck">The sheet to check if this object references</param>
|
||||
/// <returns></returns>
|
||||
public static bool NeedRebuild(IObject3D itemToCheck, InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if (!invalidateArgs.InvalidateType.HasFlag(InvalidateType.SheetUpdated))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invalidateArgs.Source is SheetObject3D sheet)
|
||||
{
|
||||
// Check if the sheet is the first sheet parent of this item (if not it will not change it's data).
|
||||
if (FindFirstVariableContainer(itemToCheck) == sheet)
|
||||
{
|
||||
return HasExpressionWithString(itemToCheck, "=", true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int RetrieveArrayIndex(IObject3D item, int level)
|
||||
{
|
||||
var arrayObject = FindParentArray(item, level);
|
||||
|
||||
if (arrayObject != null)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var child in arrayObject.Children)
|
||||
{
|
||||
if (!(child is OperationSourceObject3D))
|
||||
{
|
||||
if (child.DescendantsAndSelf().Where(i => i == item).Any())
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static RunningInterval SendInvalidateInRebuildOrder(List<UpdateItem> updateItems,
|
||||
InvalidateType invalidateType,
|
||||
IObject3D sender = null)
|
||||
{
|
||||
// and send the invalidate
|
||||
RunningInterval runningInterval = null;
|
||||
void RebuildWhenUnlocked()
|
||||
{
|
||||
var count = updateItems.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
// get the last item from the list
|
||||
var lastIndex = count - 1;
|
||||
var lastUpdateItem = updateItems[lastIndex];
|
||||
// we start with everything locked, so unlock the last layer and tell it to rebuild
|
||||
if (lastUpdateItem.rebuildLock != null)
|
||||
{
|
||||
// release the lock and rebuild
|
||||
// and ask it to update
|
||||
var depthToBuild = lastUpdateItem.depth;
|
||||
for (int i = 0; i < updateItems.Count; i++)
|
||||
{
|
||||
var updateItem = updateItems[i];
|
||||
if (updateItem.depth == lastUpdateItem.depth)
|
||||
{
|
||||
updateItem.rebuildLock.Dispose();
|
||||
updateItem.rebuildLock = null;
|
||||
var updateSender = sender == null ? updateItem.item : sender;
|
||||
updateItem.item.Invalidate(new InvalidateArgs(updateSender, invalidateType));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (updateItems.Where(i =>
|
||||
{
|
||||
return i.depth == lastUpdateItem.depth && i.item.RebuildLocked;
|
||||
}).Any())
|
||||
{
|
||||
// wait for the current rebuild to end (the one we requested above)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// now that all the items at this level have rebuilt, remove them from out tracking
|
||||
for (int i = updateItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (updateItems[i].depth == lastUpdateItem.depth)
|
||||
{
|
||||
updateItems.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UiThread.ClearInterval(runningInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild depth first
|
||||
runningInterval = UiThread.SetInterval(RebuildWhenUnlocked, .01);
|
||||
|
||||
return runningInterval;
|
||||
}
|
||||
|
||||
public static List<UpdateItem> SortAndLockUpdateItems(IObject3D root, Func<IObject3D, bool> includeObject, bool checkForExpression)
|
||||
{
|
||||
var requiredUpdateItems = new Dictionary<IObject3D, UpdateItem>();
|
||||
foreach (var child in root.Descendants())
|
||||
{
|
||||
if (includeObject(child))
|
||||
{
|
||||
var parent = child;
|
||||
var depthToThis = 0;
|
||||
while (parent.Parent != root)
|
||||
{
|
||||
depthToThis++;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
AddItemsRequiringUpdateToDictionary(child, requiredUpdateItems, depthToThis, includeObject, checkForExpression);
|
||||
}
|
||||
}
|
||||
|
||||
var updateItems = requiredUpdateItems.Values.ToList();
|
||||
// sort them
|
||||
updateItems.Sort((a, b) => a.depth.CompareTo(b.depth));
|
||||
|
||||
// lock everything
|
||||
foreach (var depthItem in updateItems)
|
||||
{
|
||||
depthItem.rebuildLock = depthItem.item.RebuildLock();
|
||||
}
|
||||
|
||||
return updateItems;
|
||||
}
|
||||
|
||||
//private static bool HasValuesThatWillChange(IObject3D item)
|
||||
// {
|
||||
// // enumerate public properties on child
|
||||
// foreach (var property in GetExpressionPropreties(item))
|
||||
// {
|
||||
// var propertyValue = property.Value;
|
||||
|
||||
// if (propertyValue is IDirectOrExpression expression)
|
||||
// {
|
||||
// // return the value
|
||||
// var currentValue = item.GetType().GetProperty(property.Name).GetValue(child, null).ToString();
|
||||
// var newValue = EvaluateExpression<string>(item, propertyValue.ToString()).ToString();
|
||||
// inExpression = inExpression.Replace("[" + constant + "]", value);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
private static void AddItemsRequiringUpdateToDictionary(IObject3D inItem,
|
||||
Dictionary<IObject3D, UpdateItem> updatedItems,
|
||||
int inDepth,
|
||||
Func<IObject3D, bool> includeObject,
|
||||
bool checkForExpression)
|
||||
{
|
||||
// process depth first
|
||||
foreach (var child in inItem.Children)
|
||||
{
|
||||
AddItemsRequiringUpdateToDictionary(child, updatedItems, inDepth + 1, includeObject, checkForExpression);
|
||||
}
|
||||
|
||||
var depth2 = inDepth;
|
||||
if (includeObject(inItem)
|
||||
&& (!checkForExpression || HasExpressionWithString(inItem, "=", true)))
|
||||
{
|
||||
var itemToAdd = inItem;
|
||||
while (itemToAdd != null
|
||||
&& depth2 >= 0)
|
||||
{
|
||||
updatedItems[itemToAdd] = new UpdateItem()
|
||||
{
|
||||
depth = depth2,
|
||||
item = itemToAdd
|
||||
};
|
||||
depth2--;
|
||||
itemToAdd = itemToAdd?.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the sheet that the given item will reference
|
||||
/// </summary>
|
||||
/// <param name="item">The item to start the search from</param>
|
||||
/// <returns></returns>
|
||||
private static IVariableContainer FindFirstVariableContainer(IObject3D item)
|
||||
{
|
||||
// look through all the parents
|
||||
foreach (var parent in item.Parents())
|
||||
{
|
||||
// then each child of any give parent
|
||||
foreach (var sibling in parent.Children)
|
||||
{
|
||||
// if it is a sheet
|
||||
if (sibling != item
|
||||
&& sibling is IVariableContainer variableContainer)
|
||||
{
|
||||
return variableContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArrayObject3D FindParentArray(IObject3D item, int wantLevel)
|
||||
{
|
||||
int foundLevel = 0;
|
||||
// look through all the parents
|
||||
foreach (var parent in item.Parents())
|
||||
{
|
||||
// if it is a ArrayObject3D
|
||||
if (parent is ArrayObject3D arrayObject)
|
||||
{
|
||||
if (foundLevel == wantLevel)
|
||||
{
|
||||
return arrayObject;
|
||||
}
|
||||
|
||||
foundLevel++;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetDisplayName(PropertyInfo prop)
|
||||
{
|
||||
var nameAttribute = prop.GetCustomAttributes(true).OfType<DisplayNameAttribute>().FirstOrDefault();
|
||||
return nameAttribute?.DisplayName ?? prop.Name.SplitCamelCase();
|
||||
}
|
||||
|
||||
private static string ReplaceConstantsWithValues(IObject3D owner, string stringWithConstants)
|
||||
{
|
||||
string Replace(string inputString, string setting)
|
||||
{
|
||||
if (constants.ContainsKey(setting))
|
||||
{
|
||||
var value = constants[setting];
|
||||
|
||||
// braces then brackets replacement
|
||||
inputString = inputString.Replace("[" + setting + "]", value(owner).ToString());
|
||||
}
|
||||
|
||||
return inputString;
|
||||
}
|
||||
|
||||
MatchCollection matches = ConstantFinder.Matches(stringWithConstants);
|
||||
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
var replacementTerm = matches[i].Value;
|
||||
stringWithConstants = Replace(stringWithConstants, replacementTerm);
|
||||
}
|
||||
|
||||
return stringWithConstants;
|
||||
}
|
||||
|
||||
private static string SearchSiblingProperties(IObject3D owner, string inExpression)
|
||||
{
|
||||
var parent = owner.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
var matches = ConstantFinder.Matches(inExpression);
|
||||
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
var constant = matches[i].Value;
|
||||
// split inExpression on .
|
||||
var splitExpression = constant.Split('.');
|
||||
if (splitExpression.Length == 2)
|
||||
{
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
// skip if owner
|
||||
if (child != owner)
|
||||
{
|
||||
var itemName = splitExpression[0];
|
||||
var propertyName = splitExpression[1];
|
||||
// if child has the same name as itemName
|
||||
if (child.Name == itemName)
|
||||
{
|
||||
// enumerate public properties on child
|
||||
foreach (var property in child.GetType().GetProperties())
|
||||
{
|
||||
var displayName = GetDisplayName(property);
|
||||
// if property name matches propertyName
|
||||
if (displayName == propertyName)
|
||||
{
|
||||
// return the value
|
||||
var expression = child.GetType().GetProperty(property.Name).GetValue(child, null).ToString();
|
||||
var value = EvaluateExpression<double>(child, expression).ToString();
|
||||
inExpression = inExpression.Replace("[" + constant + "]", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,199 +1,215 @@
|
|||
/*
|
||||
Copyright (c) 2018, John Lewin
|
||||
*/
|
||||
Copyright (c) 2023, 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.Image;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace MatterHackers.MatterControl.Plugins.Lithophane
|
||||
{
|
||||
public static class Lithophane
|
||||
{
|
||||
class PixelInfo
|
||||
{
|
||||
public Vector3 Top { get; set; }
|
||||
public Vector3 Bottom { get; set; }
|
||||
}
|
||||
public static class Lithophane
|
||||
{
|
||||
public interface IImageData
|
||||
{
|
||||
int Height { get; }
|
||||
byte[] Pixels { get; }
|
||||
|
||||
public static Mesh Generate(IImageData resizedImage, double maxZ, double nozzleWidth, double pixelsPerMM, bool invert, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
// TODO: Move this to a user supplied value
|
||||
double baseThickness = nozzleWidth; // base thickness (in mm)
|
||||
double zRange = maxZ - baseThickness;
|
||||
int Width { get; }
|
||||
}
|
||||
|
||||
// Dimensions of image
|
||||
var width = resizedImage.Width;
|
||||
var height = resizedImage.Height;
|
||||
public static Mesh Generate(IImageData resizedImage, double maxZ, double nozzleWidth, double pixelsPerMM, bool invert, Action<double, string> reporter)
|
||||
{
|
||||
// TODO: Move this to a user supplied value
|
||||
double baseThickness = nozzleWidth; // base thickness (in mm)
|
||||
double zRange = maxZ - baseThickness;
|
||||
|
||||
var zScale = zRange / 255;
|
||||
// Dimensions of image
|
||||
var width = resizedImage.Width;
|
||||
var height = resizedImage.Height;
|
||||
|
||||
var pixelData = resizedImage.Pixels;
|
||||
var zScale = zRange / 255;
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
var pixelData = resizedImage.Pixels;
|
||||
|
||||
var mesh = new Mesh();
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
//var rescale = (double)onPlateWidth / imageData.Width;
|
||||
var rescale = 1;
|
||||
var mesh = new Mesh();
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
//var rescale = (double)onPlateWidth / imageData.Width;
|
||||
var rescale = 1;
|
||||
|
||||
// Build an array of PixelInfo objects from each pixel
|
||||
// Collapse from 4 bytes per pixel to one - makes subsequent processing more logical and has minimal cost
|
||||
var pixels = pixelData.Where((x, i) => i % 4 == 0)
|
||||
|
||||
// Interpolate the pixel color to zheight
|
||||
.Select(b => baseThickness + (invert ? 255 - b : b) * zScale)
|
||||
|
||||
// Build an array of PixelInfo objects from each pixel
|
||||
// Collapse from 4 bytes per pixel to one - makes subsequent processing more logical and has minimal cost
|
||||
var pixels = pixelData.Where((x, i) => i % 4 == 0)
|
||||
// Project to Vector3 for each pixel at the computed x/y/z
|
||||
.Select((z, i) => new Vector3(
|
||||
i % width * rescale,
|
||||
(i - i % width) / width * rescale * -1,
|
||||
z))
|
||||
// Project to PixelInfo, creating a mirrored Vector3 at z0, paired together and added to the mesh
|
||||
.Select(vec =>
|
||||
{
|
||||
var pixelInfo = new PixelInfo()
|
||||
{
|
||||
Top = vec,
|
||||
Bottom = new Vector3(vec.X, vec.Y, 0)
|
||||
};
|
||||
|
||||
// Interpolate the pixel color to zheight
|
||||
.Select(b => baseThickness + (invert ? 255 - b : b) * zScale)
|
||||
mesh.Vertices.Add(pixelInfo.Top);
|
||||
mesh.Vertices.Add(pixelInfo.Bottom);
|
||||
|
||||
// Project to Vector3 for each pixel at the computed x/y/z
|
||||
.Select((z, i) => new Vector3(
|
||||
i % width * rescale,
|
||||
(i - i % width) / width * rescale * -1,
|
||||
z))
|
||||
// Project to PixelInfo, creating a mirrored Vector3 at z0, paired together and added to the mesh
|
||||
.Select(vec =>
|
||||
{
|
||||
var pixelInfo = new PixelInfo()
|
||||
{
|
||||
Top = vec,
|
||||
Bottom = new Vector3(vec.X, vec.Y, 0)
|
||||
};
|
||||
return pixelInfo;
|
||||
}).ToArray();
|
||||
|
||||
mesh.Vertices.Add(pixelInfo.Top);
|
||||
mesh.Vertices.Add(pixelInfo.Bottom);
|
||||
Console.WriteLine("ElapsedTime - PixelInfo Linq Generation: {0}", stopwatch.ElapsedMilliseconds);
|
||||
stopwatch.Restart();
|
||||
|
||||
return pixelInfo;
|
||||
}).ToArray();
|
||||
// Select pixels along image edges
|
||||
var backRow = pixels.Take(width).Reverse().ToArray();
|
||||
var frontRow = pixels.Skip((height - 1) * width).Take(width).ToArray();
|
||||
var leftRow = pixels.Where((x, i) => i % width == 0).ToArray();
|
||||
var rightRow = pixels.Where((x, i) => (i + 1) % width == 0).Reverse().ToArray();
|
||||
|
||||
Console.WriteLine("ElapsedTime - PixelInfo Linq Generation: {0}", stopwatch.ElapsedMilliseconds);
|
||||
stopwatch.Restart();
|
||||
int k,
|
||||
nextJ,
|
||||
nextK;
|
||||
|
||||
// Select pixels along image edges
|
||||
var backRow = pixels.Take(width).Reverse().ToArray();
|
||||
var frontRow = pixels.Skip((height - 1) * width).Take(width).ToArray();
|
||||
var leftRow = pixels.Where((x, i) => i % width == 0).ToArray();
|
||||
var rightRow = pixels.Where((x, i) => (i + 1) % width == 0).Reverse().ToArray();
|
||||
var notificationInterval = 100;
|
||||
|
||||
int k,
|
||||
nextJ,
|
||||
nextK;
|
||||
var workCount = (resizedImage.Width - 1) * (resizedImage.Height - 1) +
|
||||
(height - 1) +
|
||||
(width - 1);
|
||||
|
||||
var notificationInterval = 100;
|
||||
double workIndex = 0;
|
||||
|
||||
var workCount = (resizedImage.Width - 1) * (resizedImage.Height - 1) +
|
||||
(height - 1) +
|
||||
(width - 1);
|
||||
// Vertical faces: process each row and column, creating the top and bottom faces as appropriate
|
||||
for (int i = 0; i < resizedImage.Height - 1; ++i)
|
||||
{
|
||||
var startAt = i * width;
|
||||
|
||||
double workIndex = 0;
|
||||
// Process each column
|
||||
for (int j = startAt; j < startAt + resizedImage.Width - 1; ++j)
|
||||
{
|
||||
k = j + 1;
|
||||
nextJ = j + resizedImage.Width;
|
||||
nextK = nextJ + 1;
|
||||
|
||||
// Vertical faces: process each row and column, creating the top and bottom faces as appropriate
|
||||
for (int i = 0; i < resizedImage.Height - 1; ++i)
|
||||
{
|
||||
var startAt = i * width;
|
||||
// Create north, then south face
|
||||
mesh.CreateFace(new[] { pixels[k].Top, pixels[j].Top, pixels[nextJ].Top, pixels[nextK].Top });
|
||||
mesh.CreateFace(new[] { pixels[j].Bottom, pixels[k].Bottom, pixels[nextK].Bottom, pixels[nextJ].Bottom });
|
||||
workIndex++;
|
||||
|
||||
// Process each column
|
||||
for (int j = startAt; j < startAt + resizedImage.Width - 1; ++j)
|
||||
{
|
||||
k = j + 1;
|
||||
nextJ = j + resizedImage.Width;
|
||||
nextK = nextJ + 1;
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
reporter?.Invoke(workIndex / workCount, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create north, then south face
|
||||
mesh.CreateFace(new [] { pixels[k].Top, pixels[j].Top, pixels[nextJ].Top, pixels[nextK].Top });
|
||||
mesh.CreateFace(new [] { pixels[j].Bottom, pixels[k].Bottom, pixels[nextK].Bottom, pixels[nextJ].Bottom });
|
||||
workIndex++;
|
||||
// Side faces: East/West
|
||||
for (int j = 0; j < height - 1; ++j)
|
||||
{
|
||||
//Next row
|
||||
k = j + 1;
|
||||
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
progressStatus.Progress0To1 = workIndex / workCount;
|
||||
reporter.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create east, then west face
|
||||
mesh.CreateFace(new[] { leftRow[k].Top, leftRow[j].Top, leftRow[j].Bottom, leftRow[k].Bottom });
|
||||
mesh.CreateFace(new[] { rightRow[k].Top, rightRow[j].Top, rightRow[j].Bottom, rightRow[k].Bottom });
|
||||
workIndex++;
|
||||
|
||||
// Side faces: East/West
|
||||
for (int j = 0; j < height - 1; ++j)
|
||||
{
|
||||
//Next row
|
||||
k = j + 1;
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
reporter?.Invoke(workIndex / workCount, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Create east, then west face
|
||||
mesh.CreateFace(new [] { leftRow[k].Top, leftRow[j].Top, leftRow[j].Bottom, leftRow[k].Bottom });
|
||||
mesh.CreateFace(new [] { rightRow[k].Top, rightRow[j].Top, rightRow[j].Bottom, rightRow[k].Bottom });
|
||||
workIndex++;
|
||||
// Side faces: North/South
|
||||
for (int j = 0; j < width - 1; ++j)
|
||||
{
|
||||
// Next row
|
||||
k = j + 1;
|
||||
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
progressStatus.Progress0To1 = workIndex / workCount;
|
||||
reporter.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
// Create north, then south face
|
||||
mesh.CreateFace(new[] { frontRow[k].Top, frontRow[j].Top, frontRow[j].Bottom, frontRow[k].Bottom });
|
||||
mesh.CreateFace(new[] { backRow[k].Top, backRow[j].Top, backRow[j].Bottom, backRow[k].Bottom });
|
||||
workIndex++;
|
||||
|
||||
// Side faces: North/South
|
||||
for (int j = 0; j < width - 1; ++j)
|
||||
{
|
||||
// Next row
|
||||
k = j + 1;
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
reporter?.Invoke(workIndex / workCount, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Create north, then south face
|
||||
mesh.CreateFace(new [] { frontRow[k].Top, frontRow[j].Top, frontRow[j].Bottom, frontRow[k].Bottom });
|
||||
mesh.CreateFace(new [] { backRow[k].Top, backRow[j].Top, backRow[j].Bottom, backRow[k].Bottom });
|
||||
workIndex++;
|
||||
Console.WriteLine("ElapsedTime - Face Generation: {0}", stopwatch.ElapsedMilliseconds);
|
||||
|
||||
if (workIndex % notificationInterval == 0)
|
||||
{
|
||||
progressStatus.Progress0To1 = workIndex / workCount;
|
||||
reporter.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Console.WriteLine("ElapsedTime - Face Generation: {0}", stopwatch.ElapsedMilliseconds);
|
||||
public class ImageBufferImageData : IImageData
|
||||
{
|
||||
private ImageBuffer resizedImage;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
public ImageBufferImageData(ImageBuffer image, double pixelWidth)
|
||||
{
|
||||
resizedImage = this.ToResizedGrayscale(image, pixelWidth).MirrorY();
|
||||
}
|
||||
|
||||
public interface IImageData
|
||||
{
|
||||
byte[] Pixels { get; }
|
||||
public int Height => resizedImage.Height;
|
||||
public byte[] Pixels => resizedImage.GetBuffer();
|
||||
public int Width => resizedImage.Width;
|
||||
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
}
|
||||
private ImageBuffer ToResizedGrayscale(ImageBuffer image, double onPlateWidth = 0)
|
||||
{
|
||||
var ratio = onPlateWidth / image.Width;
|
||||
|
||||
public class ImageBufferImageData : IImageData
|
||||
{
|
||||
ImageBuffer resizedImage;
|
||||
var resizedImage = image.CreateScaledImage(ratio);
|
||||
|
||||
public ImageBufferImageData(ImageBuffer image, double pixelWidth)
|
||||
{
|
||||
resizedImage = this.ToResizedGrayscale(image, pixelWidth).MirrorY();
|
||||
}
|
||||
var grayImage = resizedImage.ToGrayscale();
|
||||
|
||||
public int Width => resizedImage.Width;
|
||||
public int Height => resizedImage.Height;
|
||||
// Render grayscale pixels onto resized image with larger pixel format needed by caller
|
||||
resizedImage.NewGraphics2D().Render(grayImage, 0, 0);
|
||||
|
||||
private ImageBuffer ToResizedGrayscale(ImageBuffer image, double onPlateWidth = 0)
|
||||
{
|
||||
var ratio = onPlateWidth / image.Width;
|
||||
return resizedImage;
|
||||
}
|
||||
}
|
||||
|
||||
var resizedImage = image.CreateScaledImage(ratio);
|
||||
|
||||
var grayImage = resizedImage.ToGrayscale();
|
||||
|
||||
// Render grayscale pixels onto resized image with larger pixel format needed by caller
|
||||
resizedImage.NewGraphics2D().Render(grayImage, 0, 0);
|
||||
|
||||
return resizedImage;
|
||||
}
|
||||
|
||||
public byte[] Pixels => resizedImage.GetBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
private class PixelInfo
|
||||
{
|
||||
public Vector3 Bottom { get; set; }
|
||||
public Vector3 Top { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,131 +36,125 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.PolygonMesh.Csg;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[Obsolete("Use CombineObject3D_2 instead", false)]
|
||||
public class CombineObject3D : MeshWrapperObject3D
|
||||
{
|
||||
public CombineObject3D()
|
||||
{
|
||||
Name = "Combine";
|
||||
}
|
||||
[Obsolete("Use CombineObject3D_2 instead", false)]
|
||||
public class CombineObject3D : MeshWrapperObject3D
|
||||
{
|
||||
public CombineObject3D()
|
||||
{
|
||||
Name = "Combine";
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
// spin up a task to remove holes from the objects in the group
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Combine".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
// spin up a task to remove holes from the objects in the group
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Combine".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
reporter?.Invoke(0, null);
|
||||
|
||||
try
|
||||
{
|
||||
Combine(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
Combine(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
return base.Rebuild();
|
||||
});
|
||||
}
|
||||
return base.Rebuild();
|
||||
});
|
||||
}
|
||||
|
||||
public void Combine()
|
||||
{
|
||||
Combine(CancellationToken.None, null);
|
||||
}
|
||||
public void Combine()
|
||||
{
|
||||
Combine(CancellationToken.None, null);
|
||||
}
|
||||
|
||||
public void Combine(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
public void Combine(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
|
||||
var participants = this.Descendants().Where(o => o.OwnerID == this.ID).ToList();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var participants = this.Descendants().Where(o => o.OwnerID == this.ID).ToList();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var first = participants.First();
|
||||
var first = participants.First();
|
||||
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
ProgressStatus progressStatus = new ProgressStatus();
|
||||
foreach (var item in participants)
|
||||
{
|
||||
if (item != first)
|
||||
{
|
||||
var result = BooleanProcessing.Do(item.Mesh,
|
||||
item.WorldMatrix(),
|
||||
first.Mesh,
|
||||
first.WorldMatrix(),
|
||||
CsgModes.Union,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
foreach (var item in participants)
|
||||
{
|
||||
if (item != first)
|
||||
{
|
||||
var result = BooleanProcessing.Do(item.Mesh,
|
||||
item.WorldMatrix(),
|
||||
first.Mesh,
|
||||
first.WorldMatrix(),
|
||||
CsgModes.Union,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
var inverse = first.WorldMatrix();
|
||||
inverse.Invert();
|
||||
result.Transform(inverse);
|
||||
using (first.RebuildLock())
|
||||
{
|
||||
first.Mesh = result;
|
||||
}
|
||||
var inverse = first.WorldMatrix();
|
||||
inverse.Invert();
|
||||
result.Transform(inverse);
|
||||
using (first.RebuildLock())
|
||||
{
|
||||
first.Mesh = result;
|
||||
}
|
||||
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter?.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ratioCompleted += amountPerOperation;
|
||||
reporter?.Invoke(ratioCompleted, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,123 +32,117 @@ either expressed or implied, of the FreeBSD Project.
|
|||
/************************ USE NEWER VERSION **************************/
|
||||
/*********************************************************************/
|
||||
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.PolygonMesh.Csg;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.PolygonMesh.Csg;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[Obsolete("Use IntersectionObject3D_2 instead", false)]
|
||||
public class IntersectionObject3D : MeshWrapperObject3D
|
||||
{
|
||||
public IntersectionObject3D()
|
||||
{
|
||||
Name = "Intersection";
|
||||
}
|
||||
[Obsolete("Use IntersectionObject3D_2 instead", false)]
|
||||
public class IntersectionObject3D : MeshWrapperObject3D
|
||||
{
|
||||
public IntersectionObject3D()
|
||||
{
|
||||
Name = "Intersection";
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute("Intersection".Localize(), null, (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
return ApplicationController.Instance.Tasks.Execute("Intersection".Localize(), null, (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
reporter?.Invoke(0, null);
|
||||
|
||||
try
|
||||
{
|
||||
Intersect(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
Intersect(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
return base.Rebuild();
|
||||
});
|
||||
}
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
return base.Rebuild();
|
||||
});
|
||||
}
|
||||
|
||||
private void Intersect(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
private void Intersect(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
|
||||
var participants = this.DescendantsAndSelf().Where((obj) => obj.OwnerID == this.ID);
|
||||
var participants = this.DescendantsAndSelf().Where((obj) => obj.OwnerID == this.ID);
|
||||
|
||||
if (participants.Count() > 1)
|
||||
{
|
||||
var first = participants.First();
|
||||
if (participants.Count() > 1)
|
||||
{
|
||||
var first = participants.First();
|
||||
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
ProgressStatus progressStatus = new ProgressStatus();
|
||||
foreach (var remove in participants)
|
||||
{
|
||||
if (remove != first)
|
||||
{
|
||||
var result = BooleanProcessing.Do(remove.Mesh,
|
||||
remove.WorldMatrix(),
|
||||
first.Mesh,
|
||||
first.WorldMatrix(),
|
||||
CsgModes.Intersect,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
foreach (var remove in participants)
|
||||
{
|
||||
if (remove != first)
|
||||
{
|
||||
var result = BooleanProcessing.Do(remove.Mesh,
|
||||
remove.WorldMatrix(),
|
||||
first.Mesh,
|
||||
first.WorldMatrix(),
|
||||
CsgModes.Intersect,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
var inverse = first.WorldMatrix();
|
||||
inverse.Invert();
|
||||
result.Transform(inverse);
|
||||
using (first.RebuildLock())
|
||||
{
|
||||
first.Mesh = result;
|
||||
}
|
||||
remove.Visible = false;
|
||||
var inverse = first.WorldMatrix();
|
||||
inverse.Invert();
|
||||
result.Transform(inverse);
|
||||
using (first.RebuildLock())
|
||||
{
|
||||
first.Mesh = result;
|
||||
}
|
||||
remove.Visible = false;
|
||||
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ratioCompleted += amountPerOperation;
|
||||
reporter?.Invoke(ratioCompleted, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
NameOverriden = false;
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
NameOverriden = false;
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2018, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -32,94 +32,94 @@ using MatterHackers.DataConverters3D;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
{
|
||||
public abstract class ArrayObject3D : OperationSourceContainerObject3D
|
||||
{
|
||||
public abstract IntOrExpression Count { get; set; }
|
||||
public abstract class ArrayObject3D : OperationSourceContainerObject3D
|
||||
{
|
||||
public abstract IntOrExpression Count { get; set; }
|
||||
|
||||
public override void Apply(Agg.UI.UndoBuffer undoBuffer)
|
||||
{
|
||||
var indexExpansions = new (string key, int index)[]
|
||||
{
|
||||
("[index]", 0),
|
||||
("[index0]", 0),
|
||||
("[index1]", 1),
|
||||
("[index2]", 2),
|
||||
};
|
||||
var indexExpansions = new (string key, int index)[]
|
||||
{
|
||||
("[index]", 0),
|
||||
("[index0]", 0),
|
||||
("[index1]", 1),
|
||||
("[index2]", 2),
|
||||
};
|
||||
|
||||
// convert [index] expressions to their constant values
|
||||
foreach (var item in this.Descendants((item) => !(item is ArrayObject3D)))
|
||||
{
|
||||
foreach (var expansion in indexExpansions)
|
||||
{
|
||||
foreach (var expression in SheetObject3D.GetActiveExpressions(item, expansion.key, false))
|
||||
{
|
||||
var expressionValue = expression.Expression;
|
||||
expression.Expression = expressionValue.Replace(expansion.key, SheetObject3D.RetrieveArrayIndex(item, expansion.index).ToString());
|
||||
}
|
||||
// convert [index] expressions to their constant values
|
||||
foreach (var item in this.Descendants((item) => !(item is ArrayObject3D)))
|
||||
{
|
||||
foreach (var expansion in indexExpansions)
|
||||
{
|
||||
foreach (var expression in Expressions.GetActiveExpression(item, expansion.key, false))
|
||||
{
|
||||
var expressionValue = expression.Expression;
|
||||
expression.Expression = expressionValue.Replace(expansion.key, Expressions.RetrieveArrayIndex(item, expansion.index).ToString());
|
||||
}
|
||||
|
||||
// Also convert index expressions in ComponentObjects to their constants
|
||||
if (item is ComponentObject3D component)
|
||||
{
|
||||
for (int i = 0; i < component.SurfacedEditors.Count; i++)
|
||||
{
|
||||
var (cellId, cellData) = component.DecodeContent(i);
|
||||
// Also convert index expressions in ComponentObjects to their constants
|
||||
if (item is ComponentObject3D component)
|
||||
{
|
||||
for (int i = 0; i < component.SurfacedEditors.Count; i++)
|
||||
{
|
||||
var (cellId, cellData) = component.DecodeContent(i);
|
||||
|
||||
if (cellId != null)
|
||||
{
|
||||
var newValue = cellData.Replace(expansion.key, SheetObject3D.RetrieveArrayIndex(component, expansion.index).ToString());
|
||||
component.SurfacedEditors[i] = "!" + cellId + "," + newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cellId != null)
|
||||
{
|
||||
var newValue = cellData.Replace(expansion.key, Expressions.RetrieveArrayIndex(component, expansion.index).ToString());
|
||||
component.SurfacedEditors[i] = "!" + cellId + "," + newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then call base apply
|
||||
base.Apply(undoBuffer);
|
||||
// then call base apply
|
||||
base.Apply(undoBuffer);
|
||||
}
|
||||
|
||||
internal void ProcessIndexExpressions()
|
||||
{
|
||||
var updateItems = SheetObject3D.SortAndLockUpdateItems(this, (item) =>
|
||||
{
|
||||
if (!SheetObject3D.HasExpressionWithString(item, "=", true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// WIP
|
||||
if (item.Parent == this)
|
||||
{
|
||||
// only process our children that are not the source object
|
||||
return !(item is OperationSourceObject3D);
|
||||
}
|
||||
else if (item.Parent is OperationSourceContainerObject3D)
|
||||
{
|
||||
// If we find another source container
|
||||
// Only process its children that are the source container (they will be replicated and modified correctly by the source container)
|
||||
return item is OperationSourceObject3D;
|
||||
}
|
||||
else if (item.Parent is OperationSourceObject3D operationSourceObject3D
|
||||
&& operationSourceObject3D.Parent == this)
|
||||
{
|
||||
// we don't need to rebuild our source object
|
||||
return false;
|
||||
}
|
||||
else if (item.Parent is ComponentObject3D)
|
||||
var updateItems = Expressions.SortAndLockUpdateItems(this, (item) =>
|
||||
{
|
||||
if (!Expressions.HasExpressionWithString(item, "=", true))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// process everything else
|
||||
return true;
|
||||
}, true);
|
||||
// WIP
|
||||
if (item.Parent == this)
|
||||
{
|
||||
// only process our children that are not the source object
|
||||
return !(item is OperationSourceObject3D);
|
||||
}
|
||||
else if (item.Parent is OperationSourceContainerObject3D)
|
||||
{
|
||||
// If we find another source container
|
||||
// Only process its children that are the source container (they will be replicated and modified correctly by the source container)
|
||||
return item is OperationSourceObject3D;
|
||||
}
|
||||
else if (item.Parent is OperationSourceObject3D operationSourceObject3D
|
||||
&& operationSourceObject3D.Parent == this)
|
||||
{
|
||||
// we don't need to rebuild our source object
|
||||
return false;
|
||||
}
|
||||
else if (item.Parent is ComponentObject3D)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var runningInterval = SheetObject3D.SendInvalidateInRebuildOrder(updateItems, InvalidateType.Properties);
|
||||
// process everything else
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
while (runningInterval.Active)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
var runningInterval = Expressions.SendInvalidateInRebuildOrder(updateItems, InvalidateType.Properties);
|
||||
|
||||
while (runningInterval.Active)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,215 +51,209 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
[Obsolete("Use CurveObject3D_3 instead", false)]
|
||||
public class CurveObject3D_2 : OperationSourceContainerObject3D, IEditorDraw
|
||||
{
|
||||
public CurveObject3D_2()
|
||||
{
|
||||
Name = "Curve".Localize();
|
||||
}
|
||||
[Obsolete("Use CurveObject3D_3 instead", false)]
|
||||
public class CurveObject3D_2 : OperationSourceContainerObject3D, IEditorDraw
|
||||
{
|
||||
public CurveObject3D_2()
|
||||
{
|
||||
Name = "Curve".Localize();
|
||||
}
|
||||
|
||||
[DisplayName("Bend Up")]
|
||||
public bool BendCcw { get; set; } = true;
|
||||
[DisplayName("Bend Up")]
|
||||
public bool BendCcw { get; set; } = true;
|
||||
|
||||
public double Diameter { get; set; } = double.MaxValue;
|
||||
public double Diameter { get; set; } = double.MaxValue;
|
||||
|
||||
[Slider(3, 360)]
|
||||
[Description("Ensures the rotated part has a minimum number of sides per complete rotation")]
|
||||
public double MinSidesPerRotation { get; set; } = 30;
|
||||
[Slider(3, 360)]
|
||||
[Description("Ensures the rotated part has a minimum number of sides per complete rotation")]
|
||||
public double MinSidesPerRotation { get; set; } = 30;
|
||||
|
||||
[Slider(0, 100)]
|
||||
[Description("Where to start the bend as a percent of the width of the part")]
|
||||
public double StartPercent { get; set; } = 50;
|
||||
[Description("Split the mesh so it has enough geometry to create a smooth curve")]
|
||||
public bool SplitMesh { get; set; } = true;
|
||||
|
||||
[Description("Split the mesh so it has enough geometry to create a smooth curve")]
|
||||
public bool SplitMesh { get; set; } = true;
|
||||
[Slider(0, 100)]
|
||||
[Description("Where to start the bend as a percent of the width of the part")]
|
||||
public double StartPercent { get; set; } = 50;
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
|
||||
struct DrawInfo
|
||||
{
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
public double distance;
|
||||
public Vector3 center;
|
||||
}
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = Diameter / 2 + sourceAabb.YSize / 2;
|
||||
var center = sourceAabb.Center + new Vector3(0, BendCcw ? distance : -distance, 0);
|
||||
center.X -= sourceAabb.XSize / 2 - (StartPercent / 100.0) * sourceAabb.XSize;
|
||||
// render the split lines
|
||||
var radius = Diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = drawInfo.sourceAabb.XSize * (StartPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, this.MinSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
|
||||
return new DrawInfo
|
||||
{
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var radius = Diameter / 2;
|
||||
var halfHeight = drawInfo.sourceAabb.ZSize / 2;
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(radius, radius, halfHeight), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
// render the split lines
|
||||
var radius = Diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = drawInfo.sourceAabb.XSize * (StartPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, this.MinSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
bool valuesChanged = false;
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
// ensure we have good values
|
||||
StartPercent = Util.Clamp(StartPercent, 0, 100, ref valuesChanged);
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var radius = Diameter / 2;
|
||||
var halfHeight = drawInfo.sourceAabb.ZSize / 2;
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(radius, radius, halfHeight), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
if (Diameter < 1 || Diameter > 100000)
|
||||
{
|
||||
if (Diameter == double.MaxValue)
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
// uninitialized set to a reasonable value
|
||||
Diameter = (int)aabb.XSize;
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
Diameter = Math.Min(100000, Math.Max(1, Diameter));
|
||||
valuesChanged = true;
|
||||
}
|
||||
|
||||
bool valuesChanged = false;
|
||||
MinSidesPerRotation = Util.Clamp(MinSidesPerRotation, 3, 360, ref valuesChanged);
|
||||
|
||||
// ensure we have good values
|
||||
StartPercent = agg_basics.Clamp(StartPercent, 0, 100, ref valuesChanged);
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
if (Diameter < 1 || Diameter > 100000)
|
||||
{
|
||||
if (Diameter == double.MaxValue)
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
// uninitialized set to a reasonable value
|
||||
Diameter = (int)aabb.XSize;
|
||||
}
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Curve".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
|
||||
Diameter = Math.Min(100000, Math.Max(1, Diameter));
|
||||
valuesChanged = true;
|
||||
}
|
||||
var radius = Diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
double numRotations = sourceAabb.XSize / circumference;
|
||||
double numberOfCuts = numRotations * MinSidesPerRotation;
|
||||
double cutSize = sourceAabb.XSize / numberOfCuts;
|
||||
double cutPosition = sourceAabb.MinXYZ.X + cutSize;
|
||||
var cuts = new List<double>();
|
||||
for (int i = 0; i < numberOfCuts; i++)
|
||||
{
|
||||
cuts.Add(cutPosition);
|
||||
cutPosition += cutSize;
|
||||
}
|
||||
|
||||
MinSidesPerRotation = agg_basics.Clamp(MinSidesPerRotation, 3, 360, ref valuesChanged);
|
||||
var rotationCenter = new Vector3(sourceAabb.MinXYZ.X + (sourceAabb.MaxXYZ.X - sourceAabb.MinXYZ.X) * (StartPercent / 100),
|
||||
BendCcw ? sourceAabb.MaxXYZ.Y + radius : sourceAabb.MinXYZ.Y - radius,
|
||||
sourceAabb.Center.Z);
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var curvedChildren = new List<IObject3D>();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Curve".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
{
|
||||
var originalMesh = sourceItem.Mesh;
|
||||
reporter?.Invoke(0, "Copy Mesh".Localize());
|
||||
var transformedMesh = originalMesh.Copy(CancellationToken.None);
|
||||
var itemMatrix = sourceItem.WorldMatrix(SourceContainer);
|
||||
|
||||
var radius = Diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
double numRotations = sourceAabb.XSize / circumference;
|
||||
double numberOfCuts = numRotations * MinSidesPerRotation;
|
||||
double cutSize = sourceAabb.XSize / numberOfCuts;
|
||||
double cutPosition = sourceAabb.MinXYZ.X + cutSize;
|
||||
var cuts = new List<double>();
|
||||
for (int i = 0; i < numberOfCuts; i++)
|
||||
{
|
||||
cuts.Add(cutPosition);
|
||||
cutPosition += cutSize;
|
||||
}
|
||||
// transform into this space
|
||||
transformedMesh.Transform(itemMatrix);
|
||||
|
||||
var rotationCenter = new Vector3(sourceAabb.MinXYZ.X + (sourceAabb.MaxXYZ.X - sourceAabb.MinXYZ.X) * (StartPercent / 100),
|
||||
BendCcw ? sourceAabb.MaxXYZ.Y + radius : sourceAabb.MinXYZ.Y - radius,
|
||||
sourceAabb.Center.Z);
|
||||
if (SplitMesh)
|
||||
{
|
||||
reporter?.Invoke(0, "Split Mesh".Localize());
|
||||
|
||||
var curvedChildren = new List<IObject3D>();
|
||||
// split the mesh along the x axis
|
||||
transformedMesh.SplitOnPlanes(Vector3.UnitX, cuts, cutSize / 8);
|
||||
}
|
||||
|
||||
var status = new ProgressStatus();
|
||||
for (int i = 0; i < transformedMesh.Vertices.Count; i++)
|
||||
{
|
||||
var position = transformedMesh.Vertices[i];
|
||||
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
{
|
||||
var originalMesh = sourceItem.Mesh;
|
||||
status.Status = "Copy Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
var transformedMesh = originalMesh.Copy(CancellationToken.None);
|
||||
var itemMatrix = sourceItem.WorldMatrix(SourceContainer);
|
||||
var angleToRotate = ((position.X - rotationCenter.X) / circumference) * MathHelper.Tau - MathHelper.Tau / 4;
|
||||
var distanceFromCenter = rotationCenter.Y - position.Y;
|
||||
if (!BendCcw)
|
||||
{
|
||||
angleToRotate = -angleToRotate;
|
||||
distanceFromCenter = -distanceFromCenter;
|
||||
}
|
||||
|
||||
// transform into this space
|
||||
transformedMesh.Transform(itemMatrix);
|
||||
var rotatePosition = new Vector3Float(Math.Cos(angleToRotate), Math.Sin(angleToRotate), 0) * distanceFromCenter;
|
||||
rotatePosition.Z = position.Z;
|
||||
transformedMesh.Vertices[i] = rotatePosition + new Vector3Float(rotationCenter.X, radius + sourceAabb.MaxXYZ.Y, 0);
|
||||
}
|
||||
|
||||
if (SplitMesh)
|
||||
{
|
||||
status.Status = "Split Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
// transform back into item local space
|
||||
transformedMesh.Transform(Matrix4X4.CreateTranslation(-rotationCenter) * itemMatrix.Inverted);
|
||||
|
||||
// split the mesh along the x axis
|
||||
transformedMesh.SplitOnPlanes(Vector3.UnitX, cuts, cutSize / 8);
|
||||
}
|
||||
if (SplitMesh)
|
||||
{
|
||||
reporter?.Invoke(0, "Merge Vertices".Localize());
|
||||
|
||||
for (int i = 0; i < transformedMesh.Vertices.Count; i++)
|
||||
{
|
||||
var position = transformedMesh.Vertices[i];
|
||||
transformedMesh.MergeVertices(.1);
|
||||
}
|
||||
|
||||
var angleToRotate = ((position.X - rotationCenter.X) / circumference) * MathHelper.Tau - MathHelper.Tau / 4;
|
||||
var distanceFromCenter = rotationCenter.Y - position.Y;
|
||||
if (!BendCcw)
|
||||
{
|
||||
angleToRotate = -angleToRotate;
|
||||
distanceFromCenter = -distanceFromCenter;
|
||||
}
|
||||
transformedMesh.CalculateNormals();
|
||||
|
||||
var rotatePosition = new Vector3Float(Math.Cos(angleToRotate), Math.Sin(angleToRotate), 0) * distanceFromCenter;
|
||||
rotatePosition.Z = position.Z;
|
||||
transformedMesh.Vertices[i] = rotatePosition + new Vector3Float(rotationCenter.X, radius + sourceAabb.MaxXYZ.Y, 0);
|
||||
}
|
||||
var curvedChild = new Object3D()
|
||||
{
|
||||
Mesh = transformedMesh
|
||||
};
|
||||
curvedChild.CopyWorldProperties(sourceItem, SourceContainer, Object3DPropertyFlags.All, false);
|
||||
curvedChild.Visible = true;
|
||||
curvedChild.Translate(new Vector3(rotationCenter));
|
||||
if (!BendCcw)
|
||||
{
|
||||
curvedChild.Translate(0, -sourceAabb.YSize - Diameter, 0);
|
||||
}
|
||||
|
||||
// transform back into item local space
|
||||
transformedMesh.Transform(Matrix4X4.CreateTranslation(-rotationCenter) * itemMatrix.Inverted);
|
||||
curvedChildren.Add(curvedChild);
|
||||
}
|
||||
|
||||
if (SplitMesh)
|
||||
{
|
||||
status.Status = "Merge Vertices".Localize();
|
||||
reporter.Report(status);
|
||||
RemoveAllButSource();
|
||||
this.SourceContainer.Visible = false;
|
||||
|
||||
transformedMesh.MergeVertices(.1);
|
||||
}
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.AddRange(curvedChildren);
|
||||
});
|
||||
|
||||
transformedMesh.CalculateNormals();
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
var curvedChild = new Object3D()
|
||||
{
|
||||
Mesh = transformedMesh
|
||||
};
|
||||
curvedChild.CopyWorldProperties(sourceItem, SourceContainer, Object3DPropertyFlags.All, false);
|
||||
curvedChild.Visible = true;
|
||||
curvedChild.Translate(new Vector3(rotationCenter));
|
||||
if (!BendCcw)
|
||||
{
|
||||
curvedChild.Translate(0, -sourceAabb.YSize - Diameter, 0);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
curvedChildren.Add(curvedChild);
|
||||
}
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = Diameter / 2 + sourceAabb.YSize / 2;
|
||||
var center = sourceAabb.Center + new Vector3(0, BendCcw ? distance : -distance, 0);
|
||||
center.X -= sourceAabb.XSize / 2 - (StartPercent / 100.0) * sourceAabb.XSize;
|
||||
|
||||
RemoveAllButSource();
|
||||
this.SourceContainer.Visible = false;
|
||||
return new DrawInfo
|
||||
{
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.AddRange(curvedChildren);
|
||||
});
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
struct DrawInfo
|
||||
{
|
||||
public Vector3 center;
|
||||
public double distance;
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2018, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -48,343 +48,338 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public class CurveObject3D_3 : OperationSourceContainerObject3D, IPropertyGridModifier, IEditorDraw
|
||||
{
|
||||
public CurveObject3D_3()
|
||||
{
|
||||
Name = "Curve".Localize();
|
||||
}
|
||||
public class CurveObject3D_3 : OperationSourceContainerObject3D, IPropertyGridModifier, IEditorDraw
|
||||
{
|
||||
public CurveObject3D_3()
|
||||
{
|
||||
Name = "Curve".Localize();
|
||||
}
|
||||
|
||||
public enum BendDirections
|
||||
{
|
||||
Bend_Up,
|
||||
Bend_Down,
|
||||
}
|
||||
public enum BendDirections
|
||||
{
|
||||
Bend_Up,
|
||||
Bend_Down,
|
||||
}
|
||||
|
||||
public enum BendTypes
|
||||
{
|
||||
[Description("Bend the part by an angle")]
|
||||
Angle,
|
||||
[Description("Bend the part around a specified diameter")]
|
||||
Diameter,
|
||||
}
|
||||
public enum BendTypes
|
||||
{
|
||||
[Description("Bend the part by an angle")]
|
||||
Angle,
|
||||
[Description("Bend the part around a specified diameter")]
|
||||
Diameter,
|
||||
}
|
||||
|
||||
[HideFromEditor]
|
||||
public Vector3 PostCurveOffset { get; set; } = new Vector3();
|
||||
[HideFromEditor]
|
||||
public Vector3 PostCurveOffset { get; set; } = new Vector3();
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Tabs)]
|
||||
public BendTypes BendType { get; set; } = BendTypes.Angle;
|
||||
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Description("Set the radius that the bend will wrap around")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/PpQKIIOqD-49UMhgM_HCvig9Mw_UtUwO08UoRVSLJlCv9h5cGBLMvaXbtORrVQrWYPcKZ4_DfrDoKfcu2TuyYVQOl3AeZNoYflgnijc")]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, snapDistance: 1)]
|
||||
public DoubleOrExpression Diameter { get; set; } = double.MaxValue;
|
||||
|
||||
[MaxDecimalPlaces(1)]
|
||||
[Description("Set the angle of the curvature")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/TYe-CZfwJMKvP2JWBQihkvHD1PyB_nvyf0h3DhvyJu1RBjQWgqeOEsSH3sYcwA4alJjJmziueYGCbB_mic_QoYKuhKrmipkV2eG4_A")]
|
||||
[Slider(1, 360, snapDistance: 1)]
|
||||
public DoubleOrExpression Angle { get; set; } = 90;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
[Description("The part will bend around the z axis either up or down")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/h-s2FyBKO5etYDr_9YSLtGmGmQTcmSGMu4p0mRqX4_7Z62Ndn2QRLoFICC6X9scbhr1EP29RiYRj4EmhLMUwiNTAG-PIiFbzI_jAses")]
|
||||
public BendDirections BendDirection { get; set; } = BendDirections.Bend_Up;
|
||||
|
||||
[Slider(0, 100, snapDistance: 1)]
|
||||
[Description("Where to start the bend as a percent from the left side")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/eOeWjr98uz_E924PnNaXrasepv15nWEuvhqH-jbaQyvrOVdX5MHXF00HdZQGC8NLpJc9ok1sToMtyPx1wnnDgFwTTGA5MjoMFu612AY1")]
|
||||
public DoubleOrExpression StartPercent { get; set; } = 50;
|
||||
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/arAJFTHAOPKn9BQtm1xEyct4LuA2jUAxW11q4cdQPz_JfoCTjS1rxtVTUdE1ND0Q_eigUa27Yc28U08zY2LDiQgS7kKkXKY_FY838p-5")]
|
||||
[Description("Split the mesh so it has enough geometry to create a smooth curve")]
|
||||
public bool SplitMesh { get; set; } = true;
|
||||
|
||||
[Slider(3, 360, Easing.EaseType.Cubic, snapDistance: 1)]
|
||||
[Description("Ensures the rotated part has a minimum number of sides per complete rotation")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/p9MyKu3AFP55PnobUKZQPqf6iAx11GzXyX-25f1ddrUnfCt8KFGd1YtHOR5HqfO0mhlX2ZVciZV4Yn0Kzfm43SErOS_xzgsESTu9scux")]
|
||||
public DoubleOrExpression MinSidesPerRotation { get; set; } = 30;
|
||||
|
||||
struct DrawInfo
|
||||
{
|
||||
public double diameter;
|
||||
public double startPercent;
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
public double distance;
|
||||
public Vector3 center;
|
||||
}
|
||||
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var startPercent = StartPercent.Value(this);
|
||||
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = diameter / 2 + sourceAabb.YSize / 2;
|
||||
var center = sourceAabb.Center + new Vector3(0, BendDirection == BendDirections.Bend_Up ? distance : -distance, 0);
|
||||
center.X -= sourceAabb.XSize / 2 - (startPercent / 100.0) * sourceAabb.XSize;
|
||||
center = Vector3.Zero;//.Transform(Matrix.Inverted);
|
||||
|
||||
return new DrawInfo
|
||||
{
|
||||
diameter = diameter,
|
||||
startPercent = startPercent,
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var minSidesPerRotation = MinSidesPerRotation.Value(this);
|
||||
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
|
||||
// render the split lines
|
||||
var radius = drawInfo.diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = drawInfo.sourceAabb.XSize * (drawInfo.startPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, minSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(drawInfo.diameter, drawInfo.diameter, drawInfo.sourceAabb.ZSize), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
private double DiameterFromAngle()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var angle = Angle.Value(this);
|
||||
|
||||
var aabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var angleR = MathHelper.DegreesToRadians(angle);
|
||||
var ratio = angleR / MathHelper.Tau;
|
||||
var newDiameter = (aabb.XSize / ratio) / Math.PI;
|
||||
if (Math.Abs(diameter - newDiameter) > .0001)
|
||||
{
|
||||
Diameter = newDiameter;
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
}
|
||||
|
||||
return diameter;
|
||||
}
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Tabs)]
|
||||
public BendTypes BendType { get; set; } = BendTypes.Angle;
|
||||
|
||||
|
||||
private void AngleFromDiameter()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var angle = Angle.Value(this);
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Description("Set the radius that the bend will wrap around")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/PpQKIIOqD-49UMhgM_HCvig9Mw_UtUwO08UoRVSLJlCv9h5cGBLMvaXbtORrVQrWYPcKZ4_DfrDoKfcu2TuyYVQOl3AeZNoYflgnijc")]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, snapDistance: 1)]
|
||||
public DoubleOrExpression Diameter { get; set; } = double.MaxValue;
|
||||
|
||||
var aabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var ratio = aabb.XSize / (MathHelper.Tau * diameter / 2);
|
||||
var angleR = MathHelper.Tau * ratio;
|
||||
var newAngle = MathHelper.RadiansToDegrees(angleR);
|
||||
if (Math.Abs(angle - newAngle) > .00001)
|
||||
{
|
||||
Angle = MathHelper.RadiansToDegrees(angleR);
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
}
|
||||
}
|
||||
[MaxDecimalPlaces(1)]
|
||||
[Description("Set the angle of the curvature")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/TYe-CZfwJMKvP2JWBQihkvHD1PyB_nvyf0h3DhvyJu1RBjQWgqeOEsSH3sYcwA4alJjJmziueYGCbB_mic_QoYKuhKrmipkV2eG4_A")]
|
||||
[Slider(1, 360, snapDistance: 1)]
|
||||
public DoubleOrExpression Angle { get; set; } = 90;
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
[Description("The part will bend around the z axis either up or down")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/h-s2FyBKO5etYDr_9YSLtGmGmQTcmSGMu4p0mRqX4_7Z62Ndn2QRLoFICC6X9scbhr1EP29RiYRj4EmhLMUwiNTAG-PIiFbzI_jAses")]
|
||||
public BendDirections BendDirection { get; set; } = BendDirections.Bend_Up;
|
||||
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
[Slider(0, 100, snapDistance: 1)]
|
||||
[Description("Where to start the bend as a percent from the left side")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/eOeWjr98uz_E924PnNaXrasepv15nWEuvhqH-jbaQyvrOVdX5MHXF00HdZQGC8NLpJc9ok1sToMtyPx1wnnDgFwTTGA5MjoMFu612AY1")]
|
||||
public DoubleOrExpression StartPercent { get; set; } = 50;
|
||||
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/arAJFTHAOPKn9BQtm1xEyct4LuA2jUAxW11q4cdQPz_JfoCTjS1rxtVTUdE1ND0Q_eigUa27Yc28U08zY2LDiQgS7kKkXKY_FY838p-5")]
|
||||
[Description("Split the mesh so it has enough geometry to create a smooth curve")]
|
||||
public bool SplitMesh { get; set; } = true;
|
||||
|
||||
[Slider(3, 360, Easing.EaseType.Cubic, snapDistance: 1)]
|
||||
[Description("Ensures the rotated part has a minimum number of sides per complete rotation")]
|
||||
[DescriptionImage("https://lh3.googleusercontent.com/p9MyKu3AFP55PnobUKZQPqf6iAx11GzXyX-25f1ddrUnfCt8KFGd1YtHOR5HqfO0mhlX2ZVciZV4Yn0Kzfm43SErOS_xzgsESTu9scux")]
|
||||
public DoubleOrExpression MinSidesPerRotation { get; set; } = 30;
|
||||
|
||||
struct DrawInfo
|
||||
{
|
||||
public double diameter;
|
||||
public double startPercent;
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
public double distance;
|
||||
public Vector3 center;
|
||||
}
|
||||
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var startPercent = StartPercent.Value(this);
|
||||
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = diameter / 2 + sourceAabb.YSize / 2;
|
||||
var center = sourceAabb.Center + new Vector3(0, BendDirection == BendDirections.Bend_Up ? distance : -distance, 0);
|
||||
center.X -= sourceAabb.XSize / 2 - (startPercent / 100.0) * sourceAabb.XSize;
|
||||
center = Vector3.Zero;//.Transform(Matrix.Inverted);
|
||||
|
||||
return new DrawInfo
|
||||
{
|
||||
diameter = diameter,
|
||||
startPercent = startPercent,
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var minSidesPerRotation = MinSidesPerRotation.Value(this);
|
||||
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
|
||||
// render the split lines
|
||||
var radius = drawInfo.diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = drawInfo.sourceAabb.XSize * (drawInfo.startPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, minSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(drawInfo.diameter, drawInfo.diameter, drawInfo.sourceAabb.ZSize), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
private double DiameterFromAngle()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var angle = Angle.Value(this);
|
||||
|
||||
var aabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var angleR = MathHelper.DegreesToRadians(angle);
|
||||
var ratio = angleR / MathHelper.Tau;
|
||||
var newDiameter = (aabb.XSize / ratio) / Math.PI;
|
||||
if (Math.Abs(diameter - newDiameter) > .0001)
|
||||
{
|
||||
Diameter = newDiameter;
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
}
|
||||
|
||||
return diameter;
|
||||
}
|
||||
|
||||
|
||||
private void AngleFromDiameter()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var angle = Angle.Value(this);
|
||||
|
||||
var aabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var ratio = aabb.XSize / (MathHelper.Tau * diameter / 2);
|
||||
var angleR = MathHelper.Tau * ratio;
|
||||
var newAngle = MathHelper.RadiansToDegrees(angleR);
|
||||
if (Math.Abs(angle - newAngle) > .00001)
|
||||
{
|
||||
Angle = MathHelper.RadiansToDegrees(angleR);
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Cancel(UndoBuffer undoBuffer)
|
||||
{
|
||||
this.Matrix *= Matrix4X4.CreateTranslation(-PostCurveOffset);
|
||||
base.Cancel(undoBuffer);
|
||||
this.Matrix *= Matrix4X4.CreateTranslation(-PostCurveOffset);
|
||||
base.Cancel(undoBuffer);
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
bool valuesChanged = false;
|
||||
bool valuesChanged = false;
|
||||
|
||||
// ensure we have good values
|
||||
var startPercent = StartPercent.ClampIfNotCalculated(this, 0, 100, ref valuesChanged);
|
||||
// ensure we have good values
|
||||
var startPercent = StartPercent.ClampIfNotCalculated(this, 0, 100, ref valuesChanged);
|
||||
|
||||
var diameter = Diameter.Value(this);
|
||||
if (diameter == double.MaxValue
|
||||
|| diameter == 0)
|
||||
{
|
||||
diameter = DiameterFromAngle();
|
||||
}
|
||||
var diameter = Diameter.Value(this);
|
||||
if (diameter == double.MaxValue
|
||||
|| diameter == 0)
|
||||
{
|
||||
diameter = DiameterFromAngle();
|
||||
}
|
||||
|
||||
// keep the unused type synced so we don't change the bend when clicking the tabs
|
||||
if (BendType == BendTypes.Diameter)
|
||||
{
|
||||
AngleFromDiameter();
|
||||
}
|
||||
else
|
||||
{
|
||||
diameter = DiameterFromAngle();
|
||||
}
|
||||
// keep the unused type synced so we don't change the bend when clicking the tabs
|
||||
if (BendType == BendTypes.Diameter)
|
||||
{
|
||||
AngleFromDiameter();
|
||||
}
|
||||
else
|
||||
{
|
||||
diameter = DiameterFromAngle();
|
||||
}
|
||||
|
||||
diameter = Diameter.ClampIfNotCalculated(this, .1, 100000, ref valuesChanged);
|
||||
diameter = Diameter.ClampIfNotCalculated(this, .1, 100000, ref valuesChanged);
|
||||
|
||||
var minSidesPerRotation = MinSidesPerRotation.ClampIfNotCalculated(this, 3, 360, ref valuesChanged);
|
||||
var minSidesPerRotation = MinSidesPerRotation.ClampIfNotCalculated(this, 3, 360, ref valuesChanged);
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Curve".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource;
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Curve".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource;
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
|
||||
// If this is the first build (the only child is the source container), fix the aabb.
|
||||
var firstBuild = this.Children.Count == 1;
|
||||
var initialAabb = this.GetAxisAlignedBoundingBox();
|
||||
// If this is the first build (the only child is the source container), fix the aabb.
|
||||
var firstBuild = this.Children.Count == 1;
|
||||
var initialAabb = this.GetAxisAlignedBoundingBox();
|
||||
|
||||
var radius = diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
double numRotations = sourceAabb.XSize / circumference;
|
||||
double numberOfCuts = numRotations * minSidesPerRotation;
|
||||
double cutSize = sourceAabb.XSize / numberOfCuts;
|
||||
double cutPosition = sourceAabb.MinXYZ.X + cutSize;
|
||||
var cuts = new List<double>();
|
||||
for (int i = 0; i < numberOfCuts; i++)
|
||||
{
|
||||
cuts.Add(cutPosition);
|
||||
cutPosition += cutSize;
|
||||
}
|
||||
var radius = diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
double numRotations = sourceAabb.XSize / circumference;
|
||||
double numberOfCuts = numRotations * minSidesPerRotation;
|
||||
double cutSize = sourceAabb.XSize / numberOfCuts;
|
||||
double cutPosition = sourceAabb.MinXYZ.X + cutSize;
|
||||
var cuts = new List<double>();
|
||||
for (int i = 0; i < numberOfCuts; i++)
|
||||
{
|
||||
cuts.Add(cutPosition);
|
||||
cutPosition += cutSize;
|
||||
}
|
||||
|
||||
var rotationCenter = new Vector3(sourceAabb.MinXYZ.X + (sourceAabb.MaxXYZ.X - sourceAabb.MinXYZ.X) * (startPercent / 100),
|
||||
BendDirection == BendDirections.Bend_Up ? sourceAabb.MaxXYZ.Y + radius : sourceAabb.MinXYZ.Y - radius,
|
||||
sourceAabb.Center.Z);
|
||||
var rotationCenter = new Vector3(sourceAabb.MinXYZ.X + (sourceAabb.MaxXYZ.X - sourceAabb.MinXYZ.X) * (startPercent / 100),
|
||||
BendDirection == BendDirections.Bend_Up ? sourceAabb.MaxXYZ.Y + radius : sourceAabb.MinXYZ.Y - radius,
|
||||
sourceAabb.Center.Z);
|
||||
|
||||
var curvedChildren = new List<IObject3D>();
|
||||
var curvedChildren = new List<IObject3D>();
|
||||
|
||||
var status = new ProgressStatus();
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
{
|
||||
var originalMesh = sourceItem.Mesh;
|
||||
reporter?.Invoke(0, "Copy Mesh".Localize());
|
||||
var transformedMesh = originalMesh.Copy(CancellationToken.None);
|
||||
var itemMatrix = sourceItem.WorldMatrix(SourceContainer);
|
||||
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
{
|
||||
var originalMesh = sourceItem.Mesh;
|
||||
status.Status = "Copy Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
var transformedMesh = originalMesh.Copy(CancellationToken.None);
|
||||
var itemMatrix = sourceItem.WorldMatrix(SourceContainer);
|
||||
// transform into this space
|
||||
transformedMesh.Transform(itemMatrix);
|
||||
|
||||
// transform into this space
|
||||
transformedMesh.Transform(itemMatrix);
|
||||
if (SplitMesh)
|
||||
{
|
||||
reporter?.Invoke(0, "Split Mesh".Localize());
|
||||
|
||||
if (SplitMesh)
|
||||
{
|
||||
status.Status = "Split Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
// split the mesh along the x axis
|
||||
transformedMesh.SplitOnPlanes(Vector3.UnitX, cuts, cutSize / 8);
|
||||
}
|
||||
|
||||
// split the mesh along the x axis
|
||||
transformedMesh.SplitOnPlanes(Vector3.UnitX, cuts, cutSize / 8);
|
||||
}
|
||||
for (int i = 0; i < transformedMesh.Vertices.Count; i++)
|
||||
{
|
||||
var position = transformedMesh.Vertices[i];
|
||||
|
||||
for (int i = 0; i < transformedMesh.Vertices.Count; i++)
|
||||
{
|
||||
var position = transformedMesh.Vertices[i];
|
||||
var angleToRotate = ((position.X - rotationCenter.X) / circumference) * MathHelper.Tau - MathHelper.Tau / 4;
|
||||
var distanceFromCenter = rotationCenter.Y - position.Y;
|
||||
if (BendDirection == BendDirections.Bend_Down)
|
||||
{
|
||||
angleToRotate = -angleToRotate;
|
||||
distanceFromCenter = -distanceFromCenter;
|
||||
}
|
||||
|
||||
var angleToRotate = ((position.X - rotationCenter.X) / circumference) * MathHelper.Tau - MathHelper.Tau / 4;
|
||||
var distanceFromCenter = rotationCenter.Y - position.Y;
|
||||
if (BendDirection == BendDirections.Bend_Down)
|
||||
{
|
||||
angleToRotate = -angleToRotate;
|
||||
distanceFromCenter = -distanceFromCenter;
|
||||
}
|
||||
var rotatePosition = new Vector3Float(Math.Cos(angleToRotate), Math.Sin(angleToRotate), 0) * distanceFromCenter;
|
||||
rotatePosition.Z = position.Z;
|
||||
transformedMesh.Vertices[i] = rotatePosition + new Vector3Float(rotationCenter.X, radius + sourceAabb.MaxXYZ.Y, 0);
|
||||
}
|
||||
|
||||
var rotatePosition = new Vector3Float(Math.Cos(angleToRotate), Math.Sin(angleToRotate), 0) * distanceFromCenter;
|
||||
rotatePosition.Z = position.Z;
|
||||
transformedMesh.Vertices[i] = rotatePosition + new Vector3Float(rotationCenter.X, radius + sourceAabb.MaxXYZ.Y, 0);
|
||||
}
|
||||
// transform back into item local space
|
||||
transformedMesh.Transform(Matrix4X4.CreateTranslation(-rotationCenter) * itemMatrix.Inverted);
|
||||
|
||||
// transform back into item local space
|
||||
transformedMesh.Transform(Matrix4X4.CreateTranslation(-rotationCenter) * itemMatrix.Inverted);
|
||||
if (SplitMesh)
|
||||
{
|
||||
reporter?.Invoke(0, "Merge Vertices".Localize());
|
||||
|
||||
if (SplitMesh)
|
||||
{
|
||||
status.Status = "Merge Vertices".Localize();
|
||||
reporter.Report(status);
|
||||
transformedMesh.MergeVertices(.1);
|
||||
}
|
||||
|
||||
transformedMesh.MergeVertices(.1);
|
||||
}
|
||||
transformedMesh.CalculateNormals();
|
||||
|
||||
transformedMesh.CalculateNormals();
|
||||
var curvedChild = new Object3D()
|
||||
{
|
||||
Mesh = transformedMesh
|
||||
};
|
||||
|
||||
var curvedChild = new Object3D()
|
||||
{
|
||||
Mesh = transformedMesh
|
||||
};
|
||||
curvedChild.CopyWorldProperties(sourceItem, SourceContainer, Object3DPropertyFlags.All, false);
|
||||
|
||||
curvedChild.CopyWorldProperties(sourceItem, SourceContainer, Object3DPropertyFlags.All, false);
|
||||
if (BendDirection == BendDirections.Bend_Down)
|
||||
{
|
||||
curvedChild.Translate(0, -sourceAabb.YSize - diameter, 0);
|
||||
}
|
||||
|
||||
if (BendDirection == BendDirections.Bend_Down)
|
||||
{
|
||||
curvedChild.Translate(0, -sourceAabb.YSize - diameter, 0);
|
||||
}
|
||||
curvedChildren.Add(curvedChild);
|
||||
}
|
||||
|
||||
curvedChildren.Add(curvedChild);
|
||||
}
|
||||
RemoveAllButSource();
|
||||
this.SourceContainer.Visible = false;
|
||||
|
||||
RemoveAllButSource();
|
||||
this.SourceContainer.Visible = false;
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.AddRange(curvedChildren);
|
||||
});
|
||||
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.AddRange(curvedChildren);
|
||||
});
|
||||
if (firstBuild)
|
||||
{
|
||||
var postAabb = this.GetAxisAlignedBoundingBox();
|
||||
PostCurveOffset = new Vector3(initialAabb.Center.X - postAabb.Center.X,
|
||||
initialAabb.MinXYZ.Y - postAabb.MinXYZ.Y,
|
||||
initialAabb.MinXYZ.Z - postAabb.MinXYZ.Z);
|
||||
this.Matrix *= Matrix4X4.CreateTranslation(PostCurveOffset);
|
||||
}
|
||||
|
||||
if (firstBuild)
|
||||
{
|
||||
var postAabb = this.GetAxisAlignedBoundingBox();
|
||||
PostCurveOffset = new Vector3(initialAabb.Center.X - postAabb.Center.X,
|
||||
initialAabb.MinXYZ.Y - postAabb.MinXYZ.Y,
|
||||
initialAabb.MinXYZ.Z - postAabb.MinXYZ.Z);
|
||||
this.Matrix *= Matrix4X4.CreateTranslation(PostCurveOffset);
|
||||
}
|
||||
ApplyHoles(reporter, cancellationToken.Token);
|
||||
|
||||
ApplyHoles(reporter, cancellationToken.Token);
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
private Dictionary<string, bool> changeSet = new Dictionary<string, bool>();
|
||||
|
||||
private Dictionary<string, bool> changeSet = new Dictionary<string, bool>();
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(Diameter), () => BendType == BendTypes.Diameter);
|
||||
change.SetRowVisible(nameof(Angle), () => BendType == BendTypes.Angle);
|
||||
change.SetRowVisible(nameof(MinSidesPerRotation), () => SplitMesh);
|
||||
}
|
||||
}
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(Diameter), () => BendType == BendTypes.Diameter);
|
||||
change.SetRowVisible(nameof(Angle), () => BendType == BendTypes.Angle);
|
||||
change.SetRowVisible(nameof(MinSidesPerRotation), () => SplitMesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,12 +135,12 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
// check if we have be initialized
|
||||
if (Mode == ReductionMode.Polygon_Count)
|
||||
{
|
||||
TargetCount = agg_basics.Clamp(TargetCount, 4, SourcePolygonCount, ref valuesChanged);
|
||||
TargetCount = Agg.Util.Clamp(TargetCount, 4, SourcePolygonCount, ref valuesChanged);
|
||||
TargetPercent = TargetCount / (double)SourcePolygonCount * 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetPercent = agg_basics.Clamp(TargetPercent, 0, 100, ref valuesChanged);
|
||||
TargetPercent = Agg.Util.Clamp(TargetPercent, 0, 100, ref valuesChanged);
|
||||
TargetCount = (int)(SourcePolygonCount * TargetPercent / 100);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
var offset = e.Position.X - leftX;
|
||||
var newStart = RangeStart + offset / histogramBackground.Width;
|
||||
newStart = agg_basics.Clamp(newStart, 0, RangeEnd);
|
||||
newStart = Util.Clamp(newStart, 0, RangeEnd);
|
||||
if (RangeStart != newStart)
|
||||
{
|
||||
RangeStart = newStart;
|
||||
|
|
@ -459,7 +459,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
var offset = e.Position.X - rightX;
|
||||
var newEnd = RangeEnd + offset / histogramBackground.Width;
|
||||
newEnd = agg_basics.Clamp(newEnd, RangeStart, 1);
|
||||
newEnd = Util.Clamp(newEnd, RangeStart, 1);
|
||||
if (RangeEnd != newEnd)
|
||||
{
|
||||
RangeEnd = newEnd;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -54,329 +54,326 @@ using Polygons = System.Collections.Generic.List<System.Collections.Generic.List
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
[Obsolete("Use ImageToPathObject3D_2 instead", false)]
|
||||
public class ImageToPathObject3D : Object3D, IEditorDraw, IObject3DControlsProvider
|
||||
{
|
||||
private ThresholdFunctions _featureDetector = ThresholdFunctions.Silhouette;
|
||||
[Obsolete("Use ImageToPathObject3D_2 instead", false)]
|
||||
public class ImageToPathObject3D : Object3D, IEditorDraw, IObject3DControlsProvider
|
||||
{
|
||||
private ThresholdFunctions _featureDetector = ThresholdFunctions.Silhouette;
|
||||
|
||||
private ImageBuffer _histogramRawCache = null;
|
||||
private ImageBuffer _histogramDisplayCache = null;
|
||||
private ImageBuffer _histogramRawCache = null;
|
||||
private ImageBuffer _histogramDisplayCache = null;
|
||||
|
||||
public ImageToPathObject3D()
|
||||
{
|
||||
Name = "Image to Path".Localize();
|
||||
}
|
||||
public ImageToPathObject3D()
|
||||
{
|
||||
Name = "Image to Path".Localize();
|
||||
}
|
||||
|
||||
public enum ThresholdFunctions
|
||||
{
|
||||
Silhouette,
|
||||
Intensity,
|
||||
Alpha,
|
||||
Hue
|
||||
}
|
||||
public enum ThresholdFunctions
|
||||
{
|
||||
Silhouette,
|
||||
Intensity,
|
||||
Alpha,
|
||||
Hue
|
||||
}
|
||||
|
||||
[EnumRename("Alpha", "Transparency")]
|
||||
public ThresholdFunctions FeatureDetector
|
||||
{
|
||||
get
|
||||
{
|
||||
return _featureDetector;
|
||||
}
|
||||
[EnumRename("Alpha", "Transparency")]
|
||||
public ThresholdFunctions FeatureDetector
|
||||
{
|
||||
get
|
||||
{
|
||||
return _featureDetector;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _featureDetector)
|
||||
{
|
||||
_histogramRawCache = null;
|
||||
_featureDetector = value;
|
||||
// make sure we create it
|
||||
var _ = this.Histogram;
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _featureDetector)
|
||||
{
|
||||
_histogramRawCache = null;
|
||||
_featureDetector = value;
|
||||
// make sure we create it
|
||||
var _ = this.Histogram;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ImageBuffer Histogram
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_histogramRawCache == null)
|
||||
{
|
||||
_histogramRawCache = new ImageBuffer(256, 100);
|
||||
var image = Image;
|
||||
if (image != null)
|
||||
{
|
||||
var counts = new int[_histogramRawCache.Width];
|
||||
var function = ThresholdFunction;
|
||||
[JsonIgnore]
|
||||
public ImageBuffer Histogram
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_histogramRawCache == null)
|
||||
{
|
||||
_histogramRawCache = new ImageBuffer(256, 100);
|
||||
var image = Image;
|
||||
if (image != null)
|
||||
{
|
||||
var counts = new int[_histogramRawCache.Width];
|
||||
var function = ThresholdFunction;
|
||||
|
||||
byte[] buffer = image.GetBuffer();
|
||||
int strideInBytes = image.StrideInBytes();
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
int imageBufferOffset = image.GetBufferOffsetY(y);
|
||||
int thresholdBufferOffset = y * image.Width;
|
||||
byte[] buffer = image.GetBuffer();
|
||||
int strideInBytes = image.StrideInBytes();
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
int imageBufferOffset = image.GetBufferOffsetY(y);
|
||||
int thresholdBufferOffset = y * image.Width;
|
||||
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
int imageBufferOffsetWithX = imageBufferOffset + x * 4;
|
||||
var color = GetRGBA(buffer, imageBufferOffsetWithX);
|
||||
counts[(int)(function.Transform(color) * (_histogramRawCache.Width - 1))]++;
|
||||
}
|
||||
}
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
int imageBufferOffsetWithX = imageBufferOffset + x * 4;
|
||||
var color = GetRGBA(buffer, imageBufferOffsetWithX);
|
||||
counts[(int)(function.Transform(color) * (_histogramRawCache.Width - 1))]++;
|
||||
}
|
||||
}
|
||||
|
||||
double max = counts.Select((value, index) => new { value, index })
|
||||
.OrderByDescending(vi => vi.value)
|
||||
.First().value;
|
||||
var graphics2D2 = _histogramRawCache.NewGraphics2D();
|
||||
graphics2D2.Clear(Color.White);
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
graphics2D2.Line(i, 0, i, Easing.Exponential.Out(counts[i] / max) * _histogramRawCache.Height, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
double max = counts.Select((value, index) => new { value, index })
|
||||
.OrderByDescending(vi => vi.value)
|
||||
.First().value;
|
||||
var graphics2D2 = _histogramRawCache.NewGraphics2D();
|
||||
graphics2D2.Clear(Color.White);
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
graphics2D2.Line(i, 0, i, Easing.Exponential.Out(counts[i] / max) * _histogramRawCache.Height, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_histogramDisplayCache == null)
|
||||
{
|
||||
_histogramDisplayCache = new ImageBuffer(_histogramRawCache);
|
||||
}
|
||||
if (_histogramDisplayCache == null)
|
||||
{
|
||||
_histogramDisplayCache = new ImageBuffer(_histogramRawCache);
|
||||
}
|
||||
|
||||
UpdateHistogramDisplay();
|
||||
UpdateHistogramDisplay();
|
||||
|
||||
return _histogramDisplayCache;
|
||||
}
|
||||
return _histogramDisplayCache;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHistogramDisplay()
|
||||
{
|
||||
if (_histogramRawCache != null
|
||||
&& _histogramDisplayCache != null)
|
||||
{
|
||||
var graphics2D = _histogramDisplayCache.NewGraphics2D();
|
||||
graphics2D.Clear(Color.Transparent);
|
||||
_histogramDisplayCache.CopyFrom(_histogramRawCache);
|
||||
var rangeStart = RangeStart.Value(this);
|
||||
var rangeEnd = RangeEnd.Value(this);
|
||||
graphics2D.FillRectangle(0, 0, rangeStart * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.Red, 100));
|
||||
graphics2D.FillRectangle(rangeEnd * _histogramDisplayCache.Width, 0, 255, _histogramDisplayCache.Height, new Color(Color.Red, 100));
|
||||
graphics2D.Line(rangeStart * _histogramDisplayCache.Width, 0, rangeStart * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.LightGray, 200));
|
||||
graphics2D.Line(rangeEnd * _histogramDisplayCache.Width, 0, rangeEnd * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.LightGray, 200));
|
||||
}
|
||||
}
|
||||
private void UpdateHistogramDisplay()
|
||||
{
|
||||
if (_histogramRawCache != null
|
||||
&& _histogramDisplayCache != null)
|
||||
{
|
||||
var graphics2D = _histogramDisplayCache.NewGraphics2D();
|
||||
graphics2D.Clear(Color.Transparent);
|
||||
_histogramDisplayCache.CopyFrom(_histogramRawCache);
|
||||
var rangeStart = RangeStart.Value(this);
|
||||
var rangeEnd = RangeEnd.Value(this);
|
||||
graphics2D.FillRectangle(0, 0, rangeStart * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.Red, 100));
|
||||
graphics2D.FillRectangle(rangeEnd * _histogramDisplayCache.Width, 0, 255, _histogramDisplayCache.Height, new Color(Color.Red, 100));
|
||||
graphics2D.Line(rangeStart * _histogramDisplayCache.Width, 0, rangeStart * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.LightGray, 200));
|
||||
graphics2D.Line(rangeEnd * _histogramDisplayCache.Width, 0, rangeEnd * _histogramDisplayCache.Width, _histogramDisplayCache.Height, new Color(Color.LightGray, 200));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private ImageBuffer Image => this.Descendants<ImageObject3D>().FirstOrDefault()?.Image;
|
||||
[JsonIgnore]
|
||||
private ImageBuffer Image => this.Descendants<ImageObject3D>().FirstOrDefault()?.Image;
|
||||
|
||||
[Slider(0, 1)]
|
||||
public DoubleOrExpression RangeStart { get; set; } = .1;
|
||||
[Slider(0, 1)]
|
||||
public DoubleOrExpression RangeStart { get; set; } = .1;
|
||||
|
||||
[Slider(0, 1)]
|
||||
public DoubleOrExpression RangeEnd { get; set; } = 1;
|
||||
[Slider(0, 1)]
|
||||
public DoubleOrExpression RangeEnd { get; set; } = 1;
|
||||
|
||||
private IThresholdFunction ThresholdFunction
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (FeatureDetector)
|
||||
{
|
||||
case ThresholdFunctions.Silhouette:
|
||||
return new SilhouetteThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
private IThresholdFunction ThresholdFunction
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (FeatureDetector)
|
||||
{
|
||||
case ThresholdFunctions.Silhouette:
|
||||
return new SilhouetteThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
|
||||
case ThresholdFunctions.Intensity:
|
||||
return new MapOnMaxIntensity(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
case ThresholdFunctions.Intensity:
|
||||
return new MapOnMaxIntensity(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
|
||||
case ThresholdFunctions.Alpha:
|
||||
return new AlphaThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
case ThresholdFunctions.Alpha:
|
||||
return new AlphaThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
|
||||
case ThresholdFunctions.Hue:
|
||||
return new HueThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
}
|
||||
case ThresholdFunctions.Hue:
|
||||
return new HueThresholdFunction(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
}
|
||||
|
||||
return new MapOnMaxIntensity(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
}
|
||||
}
|
||||
return new MapOnMaxIntensity(RangeStart.Value(this), RangeEnd.Value(this));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
|
||||
}
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public override bool CanApply => true;
|
||||
public override bool CanApply => true;
|
||||
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
this.FlattenToPathObject(undoBuffer);
|
||||
}
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
this.FlattenToPathObject(undoBuffer);
|
||||
}
|
||||
|
||||
public void GenerateMarchingSquaresAndLines(Action<double, string> progressReporter, ImageBuffer image, IThresholdFunction thresholdFunction)
|
||||
{
|
||||
if (image != null)
|
||||
{
|
||||
// Regenerate outline
|
||||
var marchingSquaresData = new MarchingSquaresByte(
|
||||
image,
|
||||
thresholdFunction.ZeroColor,
|
||||
thresholdFunction.Threshold,
|
||||
0);
|
||||
public void GenerateMarchingSquaresAndLines(Action<double, string> progressReporter, ImageBuffer image, IThresholdFunction thresholdFunction)
|
||||
{
|
||||
if (image != null)
|
||||
{
|
||||
// Regenerate outline
|
||||
var marchingSquaresData = new MarchingSquaresByte(
|
||||
image,
|
||||
thresholdFunction.ZeroColor,
|
||||
thresholdFunction.Threshold,
|
||||
0);
|
||||
|
||||
progressReporter?.Invoke(0, "Creating Outline");
|
||||
progressReporter?.Invoke(0, "Creating Outline");
|
||||
|
||||
marchingSquaresData.CreateLineSegments();
|
||||
progressReporter?.Invoke(.1, null);
|
||||
marchingSquaresData.CreateLineSegments();
|
||||
progressReporter?.Invoke(.1, null);
|
||||
|
||||
int pixelsToIntPointsScale = 1000;
|
||||
var lineLoops = marchingSquaresData.CreateLineLoops(pixelsToIntPointsScale);
|
||||
int pixelsToIntPointsScale = 1000;
|
||||
var lineLoops = marchingSquaresData.CreateLineLoops(pixelsToIntPointsScale);
|
||||
|
||||
progressReporter?.Invoke(.15, null);
|
||||
progressReporter?.Invoke(.15, null);
|
||||
|
||||
var min = new IntPoint(-10, -10);
|
||||
var max = new IntPoint(10 + image.Width * pixelsToIntPointsScale, 10 + image.Height * pixelsToIntPointsScale);
|
||||
var min = new IntPoint(-10, -10);
|
||||
var max = new IntPoint(10 + image.Width * pixelsToIntPointsScale, 10 + image.Height * pixelsToIntPointsScale);
|
||||
|
||||
var boundingPoly = new Polygon();
|
||||
boundingPoly.Add(min);
|
||||
boundingPoly.Add(new IntPoint(min.X, max.Y));
|
||||
boundingPoly.Add(max);
|
||||
boundingPoly.Add(new IntPoint(max.X, min.Y));
|
||||
var boundingPoly = new Polygon();
|
||||
boundingPoly.Add(min);
|
||||
boundingPoly.Add(new IntPoint(min.X, max.Y));
|
||||
boundingPoly.Add(max);
|
||||
boundingPoly.Add(new IntPoint(max.X, min.Y));
|
||||
|
||||
// now clip the polygons to get the inside and outside polys
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(lineLoops, PolyType.ptSubject, true);
|
||||
clipper.AddPath(boundingPoly, PolyType.ptClip, true);
|
||||
// now clip the polygons to get the inside and outside polys
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(lineLoops, PolyType.ptSubject, true);
|
||||
clipper.AddPath(boundingPoly, PolyType.ptClip, true);
|
||||
|
||||
var polygonShape = new Polygons();
|
||||
progressReporter?.Invoke(.3, null);
|
||||
var polygonShape = new Polygons();
|
||||
progressReporter?.Invoke(.3, null);
|
||||
|
||||
clipper.Execute(ClipType.ctIntersection, polygonShape);
|
||||
clipper.Execute(ClipType.ctIntersection, polygonShape);
|
||||
|
||||
progressReporter?.Invoke(.55, null);
|
||||
progressReporter?.Invoke(.55, null);
|
||||
|
||||
polygonShape = Clipper.CleanPolygons(polygonShape, 100);
|
||||
polygonShape = Clipper.CleanPolygons(polygonShape, 100);
|
||||
|
||||
progressReporter?.Invoke(.75, null);
|
||||
progressReporter?.Invoke(.75, null);
|
||||
|
||||
VertexStorage rawVectorShape = polygonShape.PolygonToPathStorage();
|
||||
VertexStorage rawVectorShape = polygonShape.PolygonToPathStorage();
|
||||
|
||||
var aabb = this.VisibleMeshes().FirstOrDefault().GetAxisAlignedBoundingBox();
|
||||
var xScale = aabb.XSize / image.Width;
|
||||
var aabb = this.VisibleMeshes().FirstOrDefault().GetAxisAlignedBoundingBox();
|
||||
var xScale = aabb.XSize / image.Width;
|
||||
|
||||
var affine = Affine.NewScaling(1.0 / pixelsToIntPointsScale * xScale);
|
||||
affine *= Affine.NewTranslation(-aabb.XSize / 2, -aabb.YSize / 2);
|
||||
var affine = Affine.NewScaling(1.0 / pixelsToIntPointsScale * xScale);
|
||||
affine *= Affine.NewTranslation(-aabb.XSize / 2, -aabb.YSize / 2);
|
||||
|
||||
rawVectorShape.transform(affine);
|
||||
this.VertexStorage = rawVectorShape;
|
||||
rawVectorShape.transform(affine);
|
||||
this.VertexStorage = rawVectorShape;
|
||||
|
||||
progressReporter?.Invoke(1, null);
|
||||
}
|
||||
}
|
||||
progressReporter?.Invoke(1, null);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Image)
|
||||
&& invalidateArgs.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Image)
|
||||
&& invalidateArgs.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
|
||||
private Color GetRGBA(byte[] buffer, int offset)
|
||||
{
|
||||
return new Color(buffer[offset + 2], buffer[offset + 1], buffer[offset + 0], buffer[offset + 3]);
|
||||
}
|
||||
private Color GetRGBA(byte[] buffer, int offset)
|
||||
{
|
||||
return new Color(buffer[offset + 2], buffer[offset + 1], buffer[offset + 0], buffer[offset + 3]);
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
UpdateHistogramDisplay();
|
||||
bool propertyUpdated = false;
|
||||
var minSeparation = .01;
|
||||
var rangeStart = RangeStart.Value(this);
|
||||
var rangeEnd = RangeEnd.Value(this);
|
||||
if (rangeStart < 0
|
||||
|| rangeStart > 1
|
||||
|| rangeEnd < 0
|
||||
|| rangeEnd > 1
|
||||
|| rangeStart > rangeEnd - minSeparation)
|
||||
{
|
||||
rangeStart = Math.Max(0, Math.Min(1 - minSeparation, rangeStart));
|
||||
rangeEnd = Math.Max(0, Math.Min(1, rangeEnd));
|
||||
if (rangeStart > rangeEnd - minSeparation)
|
||||
{
|
||||
// values are overlapped or too close together
|
||||
if (rangeEnd < 1 - minSeparation)
|
||||
{
|
||||
// move the end up whenever possible
|
||||
rangeEnd = rangeStart + minSeparation;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move the end to the end and the start up
|
||||
rangeEnd = 1;
|
||||
RangeStart = 1 - minSeparation;
|
||||
}
|
||||
}
|
||||
UpdateHistogramDisplay();
|
||||
bool propertyUpdated = false;
|
||||
var minSeparation = .01;
|
||||
var rangeStart = RangeStart.Value(this);
|
||||
var rangeEnd = RangeEnd.Value(this);
|
||||
if (rangeStart < 0
|
||||
|| rangeStart > 1
|
||||
|| rangeEnd < 0
|
||||
|| rangeEnd > 1
|
||||
|| rangeStart > rangeEnd - minSeparation)
|
||||
{
|
||||
rangeStart = Math.Max(0, Math.Min(1 - minSeparation, rangeStart));
|
||||
rangeEnd = Math.Max(0, Math.Min(1, rangeEnd));
|
||||
if (rangeStart > rangeEnd - minSeparation)
|
||||
{
|
||||
// values are overlapped or too close together
|
||||
if (rangeEnd < 1 - minSeparation)
|
||||
{
|
||||
// move the end up whenever possible
|
||||
rangeEnd = rangeStart + minSeparation;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move the end to the end and the start up
|
||||
rangeEnd = 1;
|
||||
RangeStart = 1 - minSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
propertyUpdated = true;
|
||||
}
|
||||
propertyUpdated = true;
|
||||
}
|
||||
|
||||
var rebuildLock = RebuildLock();
|
||||
// now create a long running task to process the image
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Calculate Path".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
this.GenerateMarchingSquaresAndLines(
|
||||
(progress0to1, status) =>
|
||||
{
|
||||
progressStatus.Progress0To1 = progress0to1;
|
||||
progressStatus.Status = status;
|
||||
reporter.Report(progressStatus);
|
||||
},
|
||||
Image,
|
||||
ThresholdFunction);
|
||||
var rebuildLock = RebuildLock();
|
||||
// now create a long running task to process the image
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Calculate Path".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
this.GenerateMarchingSquaresAndLines(
|
||||
(progress0to1, status) =>
|
||||
{
|
||||
reporter?.Invoke(progress0to1, status);
|
||||
},
|
||||
Image,
|
||||
ThresholdFunction);
|
||||
|
||||
if (propertyUpdated)
|
||||
{
|
||||
UpdateHistogramDisplay();
|
||||
this.Invalidate(InvalidateType.Properties);
|
||||
}
|
||||
if (propertyUpdated)
|
||||
{
|
||||
UpdateHistogramDisplay();
|
||||
this.Invalidate(InvalidateType.Properties);
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLock.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Path));
|
||||
});
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLock.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Path));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
CopyNewImageData();
|
||||
await Rebuild();
|
||||
|
|
@ -391,16 +391,13 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
switch (AnalysisType)
|
||||
{
|
||||
case AnalysisTypes.Transparency:
|
||||
this.GenerateMarchingSquaresAndLines(
|
||||
(progress0to1, status) =>
|
||||
{
|
||||
progressStatus.Progress0To1 = progress0to1;
|
||||
progressStatus.Status = status;
|
||||
reporter.Report(progressStatus);
|
||||
reporter?.Invoke(progress0to1, status);
|
||||
},
|
||||
SourceImage,
|
||||
new AlphaFunction());
|
||||
|
|
@ -411,10 +408,8 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
this.GenerateMarchingSquaresAndLines(
|
||||
(progress0to1, status) =>
|
||||
{
|
||||
progressStatus.Progress0To1 = progress0to1;
|
||||
progressStatus.Status = status;
|
||||
reporter.Report(progressStatus);
|
||||
},
|
||||
reporter?.Invoke(progress0to1, status);
|
||||
},
|
||||
alphaImage,
|
||||
new AlphaFunction());
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -32,19 +32,43 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public class PathObject3D : Object3D, IEditorDraw
|
||||
{
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
public class PathObject3D : Object3D, IEditorDraw, IPrimaryOperationsSpecifier
|
||||
{
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public static IEnumerable<SceneOperation> GetOperations(Type type)
|
||||
{
|
||||
// path Ids
|
||||
var pathIds = new List<string>(new string[] {
|
||||
"LinearExtrude",
|
||||
"Revolve",
|
||||
"InflatePath",
|
||||
"OutlinePath",
|
||||
"SmoothPath"
|
||||
});
|
||||
|
||||
foreach (var pathId in pathIds)
|
||||
{
|
||||
yield return SceneOperations.ById(pathId);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SceneOperation> GetOperations()
|
||||
{
|
||||
return GetOperations(this.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -32,9 +32,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CsvHelper;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.Agg.VertexSource;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.DataConverters3D.UndoCommands;
|
||||
using MatterHackers.Localizations;
|
||||
|
|
@ -42,26 +40,25 @@ using MatterHackers.MatterControl.PartPreviewWindow;
|
|||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
using static g3.SVGWriter;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
{
|
||||
public class LinearExtrudeObject3D : Object3D
|
||||
public class LinearExtrudeObject3D : Object3D, IPrimaryOperationsSpecifier
|
||||
#if DEBUG
|
||||
, IPropertyGridModifier
|
||||
#endif
|
||||
{
|
||||
[Description("The height of the extrusion")]
|
||||
[Slider(.1, 50, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
[MaxDecimalPlaces(2)]
|
||||
public DoubleOrExpression Height { get; set; } = 5;
|
||||
{
|
||||
[Description("The height of the extrusion")]
|
||||
[Slider(.1, 50, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
[MaxDecimalPlaces(2)]
|
||||
public DoubleOrExpression Height { get; set; } = 5;
|
||||
|
||||
#if DEBUG
|
||||
[Description("Bevel the top of the extrusion")]
|
||||
public bool BevelTop { get; set; } = false;
|
||||
[Description("Bevel the top of the extrusion")]
|
||||
public bool BevelTop { get; set; } = false;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ExpandStyles Style { get; set; } = ExpandStyles.Sharp;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ExpandStyles Style { get; set; } = ExpandStyles.Sharp;
|
||||
|
||||
[Slider(0, 20, Easing.EaseType.Quadratic, snapDistance: .1)]
|
||||
public DoubleOrExpression Radius { get; set; } = 3;
|
||||
|
|
@ -72,141 +69,140 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
public override bool CanApply => true;
|
||||
|
||||
public override IVertexSource GetVertexSource()
|
||||
{
|
||||
return this.CombinedVisibleChildrenPaths();
|
||||
}
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
if (Mesh == null)
|
||||
{
|
||||
Cancel(undoBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// only keep the mesh and get rid of everything else
|
||||
using (RebuildLock())
|
||||
{
|
||||
var meshOnlyItem = new Object3D()
|
||||
{
|
||||
Mesh = this.Mesh.Copy(CancellationToken.None)
|
||||
};
|
||||
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
if (Mesh == null)
|
||||
{
|
||||
Cancel(undoBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// only keep the mesh and get rid of everything else
|
||||
using (RebuildLock())
|
||||
{
|
||||
var meshOnlyItem = new Object3D()
|
||||
{
|
||||
Mesh = this.Mesh.Copy(CancellationToken.None)
|
||||
};
|
||||
meshOnlyItem.CopyProperties(this, Object3DPropertyFlags.All);
|
||||
|
||||
meshOnlyItem.CopyProperties(this, Object3DPropertyFlags.All);
|
||||
// and replace us with the children
|
||||
undoBuffer.AddAndDo(new ReplaceCommand(new[] { this }, new[] { meshOnlyItem }));
|
||||
}
|
||||
|
||||
// and replace us with the children
|
||||
undoBuffer.AddAndDo(new ReplaceCommand(new[] { this }, new[] { meshOnlyItem }));
|
||||
}
|
||||
Invalidate(InvalidateType.Children);
|
||||
}
|
||||
}
|
||||
|
||||
Invalidate(InvalidateType.Children);
|
||||
}
|
||||
}
|
||||
public LinearExtrudeObject3D()
|
||||
{
|
||||
Name = "Linear Extrude".Localize();
|
||||
}
|
||||
|
||||
public LinearExtrudeObject3D()
|
||||
{
|
||||
Name = "Linear Extrude".Localize();
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Path)
|
||||
|| invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children))
|
||||
&& invalidateArgs.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateArgs.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Path)
|
||||
|| invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children))
|
||||
&& invalidateArgs.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateArgs.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
}
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private (double x, double y) GetOffset(double radius, double xRatio, double yRatio)
|
||||
{
|
||||
{
|
||||
return (radius * Math.Cos(xRatio * MathHelper.Tau / 4), radius * Math.Sin(yRatio * MathHelper.Tau / 4));
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
var rebuildLock = RebuildLock();
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
var rebuildLock = RebuildLock();
|
||||
|
||||
bool valuesChanged = false;
|
||||
bool valuesChanged = false;
|
||||
|
||||
var height = Height.Value(this);
|
||||
var height = Height.Value(this);
|
||||
#if DEBUG
|
||||
var segments = Segments.ClampIfNotCalculated(this, 1, 32, ref valuesChanged);
|
||||
var segments = Segments.ClampIfNotCalculated(this, 1, 32, ref valuesChanged);
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
var radius = Radius.ClampIfNotCalculated(this, 0, Math.Min(aabb.XSize, Math.Min(aabb.YSize, aabb.ZSize)) / 2, ref valuesChanged);
|
||||
var bevelStart = height - radius;
|
||||
#endif
|
||||
|
||||
// now create a long running task to do the extrusion
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Linear Extrude".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var vertexSource = this.GetVertexSource();
|
||||
List<(double height, double inset)> bevel = null;
|
||||
// now create a long running task to do the extrusion
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Linear Extrude".Localize(),
|
||||
null,
|
||||
(reporter, cancellationToken) =>
|
||||
{
|
||||
var childPaths = this.CombinedVisibleChildrenPaths();
|
||||
List<(double height, double inset)> bevel = null;
|
||||
#if DEBUG
|
||||
if (BevelTop)
|
||||
{
|
||||
if (BevelTop)
|
||||
{
|
||||
bevel = new List<(double, double)>();
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
{
|
||||
(double x, double y) = GetOffset(radius, (i + 1) / (double)segments, i / (double)segments);
|
||||
bevel.Add((bevelStart + y, -radius+x));
|
||||
}
|
||||
bevel.Add((bevelStart + y, -radius + x));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (this.GetVertexSource() != null)
|
||||
{
|
||||
if (childPaths != null)
|
||||
{
|
||||
#if DEBUG
|
||||
Mesh = VertexSourceToMesh.Extrude(this.GetVertexSource(), height, bevel, InflatePathObject3D.GetJoinType(Style));
|
||||
Mesh = VertexSourceToMesh.Extrude(childPaths, height, bevel, InflatePathObject3D.GetJoinType(Style));
|
||||
#else
|
||||
Mesh = VertexSourceToMesh.Extrude(this.GetVertexSource(), height, bevel, ClipperLib.JoinType.jtRound);
|
||||
Mesh = VertexSourceToMesh.Extrude(childPaths, height, bevel, ClipperLib.JoinType.jtRound);
|
||||
#endif
|
||||
if (Mesh.Vertices.Count == 0)
|
||||
{
|
||||
Mesh = null;
|
||||
}
|
||||
}
|
||||
{
|
||||
Mesh = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Mesh = null;
|
||||
Mesh = null;
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLock.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Mesh));
|
||||
});
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLock.Dispose();
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Mesh));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(Radius), () => BevelTop);
|
||||
change.SetRowVisible(nameof(Segments), () => BevelTop);
|
||||
change.SetRowVisible(nameof(Style), () => BevelTop);
|
||||
}
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(Radius), () => BevelTop);
|
||||
change.SetRowVisible(nameof(Segments), () => BevelTop);
|
||||
change.SetRowVisible(nameof(Style), () => BevelTop);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
public IEnumerable<SceneOperation> GetOperations()
|
||||
{
|
||||
yield return SceneOperations.ById("AddBase");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -32,7 +32,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.Agg.VertexSource;
|
||||
using MatterHackers.DataConverters3D;
|
||||
|
|
@ -43,121 +42,121 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
{
|
||||
public class MergePathObject3D : OperationSourceContainerObject3D, IEditorDraw, IObject3DControlsProvider
|
||||
{
|
||||
private ClipperLib.ClipType clipType;
|
||||
private string operationName;
|
||||
public class MergePathObject3D : OperationSourceContainerObject3D, IEditorDraw, IObject3DControlsProvider, IPrimaryOperationsSpecifier
|
||||
{
|
||||
private ClipperLib.ClipType clipType;
|
||||
private string operationName;
|
||||
|
||||
public MergePathObject3D(string name, ClipperLib.ClipType clipType)
|
||||
{
|
||||
this.operationName = name;
|
||||
this.clipType = clipType;
|
||||
Name = name;
|
||||
}
|
||||
public MergePathObject3D(string name, ClipperLib.ClipType clipType)
|
||||
{
|
||||
this.operationName = name;
|
||||
this.clipType = clipType;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public override bool CanApply => true;
|
||||
public override bool CanApply => true;
|
||||
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
this.FlattenToPathObject(undoBuffer);
|
||||
}
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
{
|
||||
this.FlattenToPathObject(undoBuffer);
|
||||
}
|
||||
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
|
||||
}
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
operationName,
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
operationName,
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Merge(reporter, cancellationTokenSource.Token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Merge(reporter, cancellationTokenSource.Token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
// set the mesh to show the path
|
||||
this.Mesh = this.GetVertexSource().Extrude(Constants.PathPolygonsHeight);
|
||||
|
||||
// set the mesh to show the path
|
||||
this.Mesh = this.GetVertexSource().Extrude(Constants.PathPolygonsHeight);
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
private void Merge(Action<double, string> reporter, CancellationToken cancellationToken)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
|
||||
private void Merge(IProgress<ProgressStatus> reporter, CancellationToken cancellationToken)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
var participants = SourceContainer.VisiblePaths();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
if (participants.Count() == 1)
|
||||
{
|
||||
var newMesh = new Object3D();
|
||||
newMesh.CopyProperties(participants.First(), Object3DPropertyFlags.All);
|
||||
newMesh.Mesh = participants.First().Mesh;
|
||||
this.Children.Add(newMesh);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
|
||||
var participants = SourceContainer.VisiblePaths();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
if (participants.Count() == 1)
|
||||
{
|
||||
var newMesh = new Object3D();
|
||||
newMesh.CopyProperties(participants.First(), Object3DPropertyFlags.All);
|
||||
newMesh.Mesh = participants.First().Mesh;
|
||||
this.Children.Add(newMesh);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
var first = participants.First();
|
||||
var resultsVertexSource = first.GetVertexSource().Transform(first.Matrix);
|
||||
|
||||
var first = participants.First();
|
||||
var resultsVertexSource = first.GetVertexSource().Transform(first.Matrix);
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
var totalOperations = participants.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
foreach (var item in participants)
|
||||
{
|
||||
if (item != first
|
||||
&& item.GetVertexSource() != null)
|
||||
{
|
||||
var itemVertexSource = item.GetVertexSource().Transform(item.Matrix);
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
foreach (var item in participants)
|
||||
{
|
||||
if (item != first
|
||||
&& item.GetVertexSource() != null)
|
||||
{
|
||||
var itemVertexSource = item.GetVertexSource().Transform(item.Matrix);
|
||||
resultsVertexSource = resultsVertexSource.MergePaths(itemVertexSource, clipType);
|
||||
|
||||
resultsVertexSource = resultsVertexSource.MergePaths(itemVertexSource, clipType);
|
||||
ratioCompleted += amountPerOperation;
|
||||
reporter?.Invoke(ratioCompleted, null);
|
||||
}
|
||||
}
|
||||
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter?.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
this.VertexStorage = new VertexStorage(resultsVertexSource);
|
||||
|
||||
this.VertexStorage = new VertexStorage(resultsVertexSource);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
public IEnumerable<SceneOperation> GetOperations()
|
||||
{
|
||||
return PathObject3D.GetOperations(this.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
if (startingAngle > 0 || endingAngle < 360)
|
||||
{
|
||||
Sides = agg_basics.Clamp(sides, 1, 360, ref valuesChanged);
|
||||
Sides = Util.Clamp(sides, 1, 360, ref valuesChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sides = agg_basics.Clamp(sides, 3, 360, ref valuesChanged);
|
||||
Sides = Util.Clamp(sides, 3, 360, ref valuesChanged);
|
||||
}
|
||||
|
||||
Invalidate(InvalidateType.DisplayValues);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,21 +226,19 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
var twistedChildren = new List<IObject3D>();
|
||||
|
||||
var status = new ProgressStatus();
|
||||
|
||||
foreach (var sourceItem in SourceContainer.VisibleMeshes())
|
||||
{
|
||||
var originalMesh = sourceItem.Mesh;
|
||||
status.Status = "Copy Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
var status = "Copy Mesh".Localize();
|
||||
reporter?.Invoke(0, status);
|
||||
var transformedMesh = originalMesh.Copy(CancellationToken.None);
|
||||
var itemMatrix = sourceItem.WorldMatrix(SourceContainer);
|
||||
|
||||
// transform into this space
|
||||
transformedMesh.Transform(itemMatrix);
|
||||
|
||||
status.Status = "Split Mesh".Localize();
|
||||
reporter.Report(status);
|
||||
status = "Split Mesh".Localize();
|
||||
reporter?.Invoke(0, status);
|
||||
|
||||
// split the mesh along the z axis
|
||||
transformedMesh.SplitOnPlanes(Vector3.UnitZ, cuts, cutSize / 8);
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -27,101 +27,119 @@ of the authors and should not be interpreted as representing official policies,
|
|||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.Agg.VertexSource;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
namespace MatterHackers.MatterControl.DesignTools.Primitives
|
||||
{
|
||||
public class BoxPathObject3D : PrimitiveObject3D, IObject3DControlsProvider, IEditorDraw
|
||||
{
|
||||
public BoxPathObject3D()
|
||||
{
|
||||
Name = "Box".Localize();
|
||||
Color = Operations.Object3DExtensions.PrimitiveColors["Cube"];
|
||||
}
|
||||
public class BoxPathObject3D : PathObject3D, IObject3DControlsProvider, IEditorDraw, IPropertyGridModifier, IStaticThumbnail
|
||||
{
|
||||
public BoxPathObject3D()
|
||||
{
|
||||
Name = "Box".Localize();
|
||||
Color = MatterHackers.MatterControl.DesignTools.Operations.Object3DExtensions.PrimitiveColors["Cube"];
|
||||
}
|
||||
|
||||
public override string ThumbnailName => "Box";
|
||||
public static double MinEdgeSize = .001;
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
public string ThumbnailName => "Box";
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
/// <summary>
|
||||
/// This is the actual serialized with that can use expressions
|
||||
/// </summary>
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
public DoubleOrExpression Width { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// This is the actual serialized with that can use expressions
|
||||
/// </summary>
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, snapDistance: 1)]
|
||||
public DoubleOrExpression Width { get; set; } = 20;
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
public DoubleOrExpression Depth { get; set; } = 20;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, snapDistance: 1)]
|
||||
public DoubleOrExpression Depth { get; set; } = 20;
|
||||
public bool Round { get; set; }
|
||||
|
||||
public static async Task<BoxPathObject3D> Create()
|
||||
{
|
||||
var item = new BoxPathObject3D();
|
||||
await item.Rebuild();
|
||||
return item;
|
||||
}
|
||||
[Slider(0, 20, Easing.EaseType.Quadratic, snapDistance: .1)]
|
||||
public DoubleOrExpression Radius { get; set; } = 3;
|
||||
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.MoveInZ);
|
||||
object3DControlsLayer.AddWidthDepthControls(this, Width, Depth, null);
|
||||
[Slider(1, 30, Easing.EaseType.Quadratic, snapDistance: 1)]
|
||||
public IntOrExpression RoundSegments { get; set; } = 9;
|
||||
|
||||
object3DControlsLayer.AddControls(ControlTypes.MoveInZ);
|
||||
object3DControlsLayer.AddControls(ControlTypes.RotateXYZ);
|
||||
}
|
||||
public static async Task<BoxPathObject3D> Create()
|
||||
{
|
||||
var item = new BoxPathObject3D();
|
||||
await item.Rebuild();
|
||||
return item;
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
}
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.MoveInZ);
|
||||
object3DControlsLayer.AddWidthDepthControls(this, Width, Depth, null);
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
object3DControlsLayer.AddControls(ControlTypes.MoveInZ);
|
||||
object3DControlsLayer.AddControls(ControlTypes.RotateXYZ);
|
||||
}
|
||||
|
||||
using (RebuildLock())
|
||||
{
|
||||
using (new CenterAndHeightMaintainer(this))
|
||||
{
|
||||
bool valuesChanged = false;
|
||||
var width = Width.ClampIfNotCalculated(this, .01, 10000, ref valuesChanged);
|
||||
var depth = Depth.ClampIfNotCalculated(this, .01, 10000, ref valuesChanged);
|
||||
VertexStorage = new VertexStorage(new RoundedRect(-width / 2, -depth / 2, width / 2, depth / 2, 0));
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateArgs);
|
||||
}
|
||||
}
|
||||
|
||||
this.Mesh = VertexStorage.Extrude(Constants.PathPolygonsHeight);
|
||||
}
|
||||
}
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Path));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
using (RebuildLock())
|
||||
{
|
||||
using (new CenterAndHeightMaintainer(this))
|
||||
{
|
||||
bool valuesChanged = false;
|
||||
var width = Width.ClampIfNotCalculated(this, MinEdgeSize, 1000000, ref valuesChanged);
|
||||
var depth = Depth.ClampIfNotCalculated(this, MinEdgeSize, 1000000, ref valuesChanged);
|
||||
var roundSegments = RoundSegments.ClampIfNotCalculated(this, 1, 90, ref valuesChanged);
|
||||
var roundRadius = Radius.ClampIfNotCalculated(this, 0, Math.Min(width, depth) / 2, ref valuesChanged);
|
||||
|
||||
if (Round)
|
||||
{
|
||||
var roundRect = new RoundedRect(-width / 2, -depth / 2, width / 2, depth / 2, roundRadius);
|
||||
roundRect.NumSegments = roundSegments;
|
||||
VertexStorage = new VertexStorage(roundRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
VertexStorage = new VertexStorage(new RoundedRect(-width / 2, -depth / 2, width / 2, depth / 2, 0));
|
||||
}
|
||||
|
||||
Mesh = VertexStorage.Extrude(Constants.PathPolygonsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Path));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(RoundSegments), () => Round);
|
||||
change.SetRowVisible(nameof(Radius), () => Round);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,15 +54,15 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
/// This is the actual serialized with that can use expressions
|
||||
/// </summary>
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, VectorMath.Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
public DoubleOrExpression Width { get; set; } = 20;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, VectorMath.Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
public DoubleOrExpression Depth { get; set; } = 20;
|
||||
|
||||
[MaxDecimalPlaces(2)]
|
||||
[Slider(1, 400, VectorMath.Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
[Slider(1, 400, Easing.EaseType.Quadratic, useSnappingGrid: true)]
|
||||
public DoubleOrExpression Height { get; set; } = 20;
|
||||
|
||||
public bool Round { get; set; }
|
||||
|
|
@ -108,7 +108,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
public class OperationSourceContainerObject3D : Object3D
|
||||
{
|
||||
public static Func<string, Func<IProgress<ProgressStatus>, CancellationTokenSource, Task>, Task> TaskBuilder { get; set; } =
|
||||
public static Func<string, Func<Action<double, string>, CancellationTokenSource, Task>, Task> TaskBuilder { get; set; } =
|
||||
(name, func) => Task.Run(() => func(null, null));
|
||||
|
||||
public override Mesh Mesh
|
||||
|
|
@ -207,7 +207,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
@ -329,7 +329,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
/// <param name="reporter">Show progress</param>
|
||||
/// <param name="cancellationToken">Can check if the operation has been canceled</param>
|
||||
/// <returns>Did any holes get subtracted</returns>
|
||||
public bool ApplyHoles(IProgress<ProgressStatus> reporter,
|
||||
public bool ApplyHoles(Action<double, string> reporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var removeItems = Children.Where(c => c.WorldOutputType(SourceContainer) == PrintOutputTypes.Hole && c.Visible);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
RebuildImage();
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
RebuildImage();
|
||||
await Rebuild();
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
using (RebuildLock())
|
||||
{
|
||||
Temperature = agg_basics.Clamp(Temperature, 140, 400, ref valuesChanged);
|
||||
Temperature = Util.Clamp(Temperature, 140, 400, ref valuesChanged);
|
||||
|
||||
using (new CenterAndHeightMaintainer(this))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
|
||||
using (RebuildLock())
|
||||
{
|
||||
MaxTemperature = agg_basics.Clamp(MaxTemperature, 140, 400, ref valuesChanged);
|
||||
Sections = agg_basics.Clamp(Sections, 2, 20, ref valuesChanged);
|
||||
ChangeAmount = agg_basics.Clamp(ChangeAmount, 1, 30, ref valuesChanged);
|
||||
MaxTemperature = Util.Clamp(MaxTemperature, 140, 400, ref valuesChanged);
|
||||
Sections = Util.Clamp(Sections, 2, 20, ref valuesChanged);
|
||||
ChangeAmount = Util.Clamp(ChangeAmount, 1, 30, ref valuesChanged);
|
||||
|
||||
using (new CenterAndHeightMaintainer(this))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
var item = new TextObject3D();
|
||||
|
||||
if (!setTo2D)
|
||||
if (setTo2D)
|
||||
{
|
||||
item.Output = OutputDimensions.Output2D;
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
|
||||
else if (Expressions.NeedRebuild(this, invalidateArgs))
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2022, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -35,85 +35,85 @@ using MatterHackers.DataConverters3D;
|
|||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
|
||||
[TypeConverter(typeof(DoubleOrExpression))]
|
||||
public class DoubleOrExpression : DirectOrExpression
|
||||
{
|
||||
public double Value(IObject3D owner)
|
||||
{
|
||||
var value = SheetObject3D.EvaluateExpression<double>(owner, Expression);
|
||||
if (owner.RebuildLocked)
|
||||
[TypeConverter(typeof(DoubleOrExpression))]
|
||||
public class DoubleOrExpression : DirectOrExpression
|
||||
{
|
||||
public double Value(IObject3D owner)
|
||||
{
|
||||
var value = Expressions.EvaluateExpression<double>(owner, Expression);
|
||||
if (owner.RebuildLocked)
|
||||
{
|
||||
ExpressionValueAtLastRebuild = value.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public DoubleOrExpression(double value)
|
||||
{
|
||||
Expression = value.ToString();
|
||||
}
|
||||
public DoubleOrExpression(double value)
|
||||
{
|
||||
Expression = value.ToString();
|
||||
}
|
||||
|
||||
public DoubleOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
public DoubleOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public static implicit operator DoubleOrExpression(double value)
|
||||
{
|
||||
return new DoubleOrExpression(value);
|
||||
}
|
||||
public static implicit operator DoubleOrExpression(double value)
|
||||
{
|
||||
return new DoubleOrExpression(value);
|
||||
}
|
||||
|
||||
public static implicit operator DoubleOrExpression(string expression)
|
||||
{
|
||||
return new DoubleOrExpression(expression);
|
||||
}
|
||||
public static implicit operator DoubleOrExpression(string expression)
|
||||
{
|
||||
return new DoubleOrExpression(expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the expression clap the result and return the clamped value.
|
||||
/// If the expression as not an equation, modify it to be the clamped value.
|
||||
/// </summary>
|
||||
/// <param name="item">The Object to find the table relative to</param>
|
||||
/// <param name="min">The min value to clamp to</param>
|
||||
/// <param name="max">The max value to clamp to</param>
|
||||
/// <param name="valuesChanged">Did the value actual get changed (clamped).</param>
|
||||
/// <returns></returns>
|
||||
public double ClampIfNotCalculated(IObject3D item, double min, double max, ref bool valuesChanged)
|
||||
{
|
||||
var value = agg_basics.Clamp(this.Value(item), min, max, ref valuesChanged);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
// clamp the actual expression as it is not an equation
|
||||
Expression = value.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// Evaluate the expression clap the result and return the clamped value.
|
||||
/// If the expression as not an equation, modify it to be the clamped value.
|
||||
/// </summary>
|
||||
/// <param name="item">The Object to find the table relative to</param>
|
||||
/// <param name="min">The min value to clamp to</param>
|
||||
/// <param name="max">The max value to clamp to</param>
|
||||
/// <param name="valuesChanged">Did the value actual get changed (clamped).</param>
|
||||
/// <returns></returns>
|
||||
public double ClampIfNotCalculated(IObject3D item, double min, double max, ref bool valuesChanged)
|
||||
{
|
||||
var value = Util.Clamp(this.Value(item), min, max, ref valuesChanged);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
// clamp the actual expression as it is not an equation
|
||||
Expression = value.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public double DefaultAndClampIfNotCalculated(IObject3D item,
|
||||
double min,
|
||||
double max,
|
||||
string keyName,
|
||||
double defaultValue,
|
||||
ref bool changed)
|
||||
{
|
||||
var currentValue = this.Value(item);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
double databaseValue = UserSettings.Instance.Fields.GetDouble(keyName, defaultValue);
|
||||
public double DefaultAndClampIfNotCalculated(IObject3D item,
|
||||
double min,
|
||||
double max,
|
||||
string keyName,
|
||||
double defaultValue,
|
||||
ref bool changed)
|
||||
{
|
||||
var currentValue = this.Value(item);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
double databaseValue = UserSettings.Instance.Fields.GetDouble(keyName, defaultValue);
|
||||
|
||||
if (currentValue == 0)
|
||||
{
|
||||
currentValue = databaseValue;
|
||||
changed = true;
|
||||
}
|
||||
if (currentValue == 0)
|
||||
{
|
||||
currentValue = databaseValue;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
currentValue = agg_basics.Clamp(currentValue, min, max, ref changed);
|
||||
currentValue = Util.Clamp(currentValue, min, max, ref changed);
|
||||
|
||||
Expression = currentValue.ToString();
|
||||
}
|
||||
Expression = currentValue.ToString();
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2022, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -33,70 +33,70 @@ using MatterHackers.DataConverters3D;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
[TypeConverter(typeof(IntOrExpression))]
|
||||
public class IntOrExpression : DirectOrExpression
|
||||
{
|
||||
public int Value(IObject3D owner)
|
||||
{
|
||||
var rebuilding = owner.RebuildLocked;
|
||||
var value = SheetObject3D.EvaluateExpression<int>(owner, Expression);
|
||||
if (rebuilding)
|
||||
{
|
||||
ExpressionValueAtLastRebuild = value.ToString();
|
||||
}
|
||||
[TypeConverter(typeof(IntOrExpression))]
|
||||
public class IntOrExpression : DirectOrExpression
|
||||
{
|
||||
public int Value(IObject3D owner)
|
||||
{
|
||||
var rebuilding = owner.RebuildLocked;
|
||||
var value = Expressions.EvaluateExpression<int>(owner, Expression);
|
||||
if (rebuilding)
|
||||
{
|
||||
ExpressionValueAtLastRebuild = value.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public IntOrExpression(int value)
|
||||
{
|
||||
Expression = value.ToString();
|
||||
}
|
||||
public IntOrExpression(int value)
|
||||
{
|
||||
Expression = value.ToString();
|
||||
}
|
||||
|
||||
public IntOrExpression(double value)
|
||||
{
|
||||
Expression = ((int)value).ToString();
|
||||
}
|
||||
public IntOrExpression(double value)
|
||||
{
|
||||
Expression = ((int)value).ToString();
|
||||
}
|
||||
|
||||
public IntOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
public IntOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public static implicit operator IntOrExpression(int value)
|
||||
{
|
||||
return new IntOrExpression(value);
|
||||
}
|
||||
public static implicit operator IntOrExpression(int value)
|
||||
{
|
||||
return new IntOrExpression(value);
|
||||
}
|
||||
|
||||
public static implicit operator IntOrExpression(double value)
|
||||
{
|
||||
return new IntOrExpression(value);
|
||||
}
|
||||
public static implicit operator IntOrExpression(double value)
|
||||
{
|
||||
return new IntOrExpression(value);
|
||||
}
|
||||
|
||||
public static implicit operator IntOrExpression(string expression)
|
||||
{
|
||||
return new IntOrExpression(expression);
|
||||
}
|
||||
public static implicit operator IntOrExpression(string expression)
|
||||
{
|
||||
return new IntOrExpression(expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the expression clap the result and return the clamped value.
|
||||
/// If the expression as not an equation, modify it to be the clamped value.
|
||||
/// </summary>
|
||||
/// <param name="item">The Object to find the table relative to</param>
|
||||
/// <param name="min">The min value to clamp to</param>
|
||||
/// <param name="max">The max value to clamp to</param>
|
||||
/// <param name="valuesChanged">Did the value actual get changed (clamped).</param>
|
||||
/// <returns></returns>
|
||||
public int ClampIfNotCalculated(IObject3D item, int min, int max, ref bool valuesChanged)
|
||||
{
|
||||
var value = agg_basics.Clamp(this.Value(item), min, max, ref valuesChanged);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
// clamp the actual expression as it is not an equation
|
||||
Expression = value.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// Evaluate the expression clap the result and return the clamped value.
|
||||
/// If the expression as not an equation, modify it to be the clamped value.
|
||||
/// </summary>
|
||||
/// <param name="item">The Object to find the table relative to</param>
|
||||
/// <param name="min">The min value to clamp to</param>
|
||||
/// <param name="max">The max value to clamp to</param>
|
||||
/// <param name="valuesChanged">Did the value actual get changed (clamped).</param>
|
||||
/// <returns></returns>
|
||||
public int ClampIfNotCalculated(IObject3D item, int min, int max, ref bool valuesChanged)
|
||||
{
|
||||
var value = Util.Clamp(this.Value(item), min, max, ref valuesChanged);
|
||||
if (!this.IsEquation)
|
||||
{
|
||||
// clamp the actual expression as it is not an equation
|
||||
Expression = value.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -28,660 +28,129 @@ either expressed or implied, of the FreeBSD Project.
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.Platform;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using org.mariuszgromada.math.mxparser;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
[HideChildrenFromTreeView]
|
||||
[HideMeterialAndColor]
|
||||
[WebPageLink("Documentation", "Open", "https://www.matterhackers.com/support/mattercontrol-variable-support")]
|
||||
[MarkDownDescription("[BETA] - Experimental support for variables and equations with a sheets like interface.")]
|
||||
public class SheetObject3D : Object3D, IStaticThumbnail
|
||||
{
|
||||
private SheetData _sheetData;
|
||||
public SheetData SheetData
|
||||
{
|
||||
get => _sheetData;
|
||||
|
||||
set
|
||||
{
|
||||
if (_sheetData != value)
|
||||
{
|
||||
if (_sheetData != null)
|
||||
{
|
||||
_sheetData.Recalculated -= SendInvalidateToAll;
|
||||
}
|
||||
|
||||
_sheetData = value;
|
||||
_sheetData.Recalculated += SendInvalidateToAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<SheetObject3D> Create()
|
||||
{
|
||||
var item = new SheetObject3D
|
||||
{
|
||||
SheetData = new SheetData(5, 5)
|
||||
};
|
||||
await item.Rebuild();
|
||||
return item;
|
||||
}
|
||||
|
||||
public string ThumbnailName => "Sheet";
|
||||
|
||||
|
||||
private static object loadLock = new object();
|
||||
private static IObject3D sheetObject;
|
||||
|
||||
public override Mesh Mesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Children.Count == 0)
|
||||
{
|
||||
lock (loadLock)
|
||||
{
|
||||
if (sheetObject == null)
|
||||
{
|
||||
sheetObject = MeshContentProvider.LoadMCX(StaticData.Instance.OpenStream(Path.Combine("Stls", "sheet_icon.mcx")));
|
||||
}
|
||||
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
list.Add(sheetObject.Clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
set => base.Mesh = value;
|
||||
}
|
||||
|
||||
public SheetObject3D()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Printable => false;
|
||||
|
||||
public class UpdateItem
|
||||
{
|
||||
internal int depth;
|
||||
internal IObject3D item;
|
||||
internal RebuildLock rebuildLock;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var state = rebuildLock == null ? "unlocked" : "locked";
|
||||
return $"{depth} {state} - {item}";
|
||||
}
|
||||
}
|
||||
|
||||
public static List<UpdateItem> SortAndLockUpdateItems(IObject3D root, Func<IObject3D, bool> includeObject, bool checkForExpression)
|
||||
{
|
||||
var requiredUpdateItems = new Dictionary<IObject3D, UpdateItem>();
|
||||
foreach (var child in root.Descendants())
|
||||
{
|
||||
if (includeObject(child))
|
||||
{
|
||||
var parent = child;
|
||||
var depthToThis = 0;
|
||||
while(parent.Parent != root)
|
||||
{
|
||||
depthToThis++;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
AddItemsRequiringUpdateToDictionary(child, requiredUpdateItems, depthToThis, includeObject, checkForExpression);
|
||||
}
|
||||
}
|
||||
|
||||
var updateItems = requiredUpdateItems.Values.ToList();
|
||||
// sort them
|
||||
updateItems.Sort((a, b) => a.depth.CompareTo(b.depth));
|
||||
|
||||
// lock everything
|
||||
foreach (var depthItem in updateItems)
|
||||
{
|
||||
depthItem.rebuildLock = depthItem.item.RebuildLock();
|
||||
}
|
||||
|
||||
return updateItems;
|
||||
}
|
||||
|
||||
private void SendInvalidateToAll(object s, EventArgs e)
|
||||
{
|
||||
var updateItems = SortAndLockUpdateItems(this.Parent, (item) =>
|
||||
{
|
||||
if (item == this || item.Parent == this)
|
||||
{
|
||||
// don't process this
|
||||
return false;
|
||||
}
|
||||
else if (item.Parent is ArrayObject3D arrayObject3D
|
||||
&& arrayObject3D.SourceContainer != item)
|
||||
{
|
||||
// don't process the copied children of an array object
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
SendInvalidateInRebuildOrder(updateItems, InvalidateType.SheetUpdated, this);
|
||||
}
|
||||
|
||||
public static RunningInterval SendInvalidateInRebuildOrder(List<UpdateItem> updateItems,
|
||||
InvalidateType invalidateType,
|
||||
IObject3D sender = null)
|
||||
{
|
||||
// and send the invalidate
|
||||
RunningInterval runningInterval = null;
|
||||
void RebuildWhenUnlocked()
|
||||
{
|
||||
var count = updateItems.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
// get the last item from the list
|
||||
var lastIndex = count - 1;
|
||||
var lastUpdateItem = updateItems[lastIndex];
|
||||
// we start with everything locked, so unlock the last layer and tell it to rebuild
|
||||
if (lastUpdateItem.rebuildLock != null)
|
||||
{
|
||||
// release the lock and rebuild
|
||||
// and ask it to update
|
||||
var depthToBuild = lastUpdateItem.depth;
|
||||
for (int i = 0; i < updateItems.Count; i++)
|
||||
{
|
||||
var updateItem = updateItems[i];
|
||||
if (updateItem.depth == lastUpdateItem.depth)
|
||||
{
|
||||
updateItem.rebuildLock.Dispose();
|
||||
updateItem.rebuildLock = null;
|
||||
var updateSender = sender == null ? updateItem.item : sender;
|
||||
updateItem.item.Invalidate(new InvalidateArgs(updateSender, invalidateType));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (updateItems.Where(i =>
|
||||
{
|
||||
return i.depth == lastUpdateItem.depth && i.item.RebuildLocked;
|
||||
}).Any())
|
||||
{
|
||||
// wait for the current rebuild to end (the one we requested above)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// now that all the items at this level have rebuilt, remove them from out tracking
|
||||
for (int i = updateItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (updateItems[i].depth == lastUpdateItem.depth)
|
||||
{
|
||||
updateItems.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UiThread.ClearInterval(runningInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild depth first
|
||||
runningInterval = UiThread.SetInterval(RebuildWhenUnlocked, .01);
|
||||
|
||||
return runningInterval;
|
||||
}
|
||||
|
||||
private static readonly Type[] ExpressionTypes =
|
||||
{
|
||||
typeof(StringOrExpression),
|
||||
typeof(DoubleOrExpression),
|
||||
typeof(IntOrExpression),
|
||||
|
||||
};
|
||||
|
||||
|
||||
public static IEnumerable<EditableProperty> GetExpressionPropreties(IObject3D item)
|
||||
{
|
||||
return item.GetType().GetProperties(OwnedPropertiesOnly)
|
||||
.Where(pi => ExpressionTypes.Contains(pi.PropertyType)
|
||||
&& pi.GetGetMethod() != null
|
||||
&& pi.GetSetMethod() != null)
|
||||
.Select(p => new EditableProperty(p, item));
|
||||
}
|
||||
|
||||
//private static bool HasValuesThatWillChange(IObject3D item)
|
||||
// {
|
||||
// // enumerate public properties on child
|
||||
// foreach (var property in GetExpressionPropreties(item))
|
||||
// {
|
||||
// var propertyValue = property.Value;
|
||||
|
||||
// if (propertyValue is IDirectOrExpression expression)
|
||||
// {
|
||||
// // return the value
|
||||
// var currentValue = item.GetType().GetProperty(property.Name).GetValue(child, null).ToString();
|
||||
// var newValue = EvaluateExpression<string>(item, propertyValue.ToString()).ToString();
|
||||
// inExpression = inExpression.Replace("[" + constant + "]", value);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
private static void AddItemsRequiringUpdateToDictionary(IObject3D inItem,
|
||||
Dictionary<IObject3D, UpdateItem> updatedItems,
|
||||
int inDepth,
|
||||
Func<IObject3D, bool> includeObject,
|
||||
bool checkForExpression)
|
||||
{
|
||||
// process depth first
|
||||
foreach(var child in inItem.Children)
|
||||
{
|
||||
AddItemsRequiringUpdateToDictionary(child, updatedItems, inDepth + 1, includeObject, checkForExpression);
|
||||
}
|
||||
|
||||
var depth2 = inDepth;
|
||||
if (includeObject(inItem)
|
||||
&& (!checkForExpression || HasExpressionWithString(inItem, "=", true)))
|
||||
{
|
||||
var itemToAdd = inItem;
|
||||
while (itemToAdd != null
|
||||
&& depth2 >= 0)
|
||||
{
|
||||
updatedItems[itemToAdd] = new UpdateItem()
|
||||
{
|
||||
depth = depth2,
|
||||
item = itemToAdd
|
||||
};
|
||||
depth2--;
|
||||
itemToAdd = itemToAdd?.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex ConstantFinder = new Regex("(?<=\\[).+?(?=\\])", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
private static Random rand = new Random();
|
||||
|
||||
private static Dictionary<string, Func<IObject3D, double>> constants = new Dictionary<string, Func<IObject3D, double>>()
|
||||
{
|
||||
// length
|
||||
["cm"] = (owner) => 10,
|
||||
["m"] = (owner) => 1000,
|
||||
["inch"] = (owner) => 25.4,
|
||||
["ft"] = (owner) => 304.8,
|
||||
// math constant
|
||||
["pi"] = (owner) => Math.PI,
|
||||
["tau"] = (owner) => Math.PI * 2,
|
||||
["e"] = (owner) => Math.E,
|
||||
// functions
|
||||
["rand"] = (owner) => rand.NextDouble(),
|
||||
// array function
|
||||
["index"] = (owner) => RetrieveArrayIndex(owner, 0),
|
||||
["index0"] = (owner) => RetrieveArrayIndex(owner, 0),
|
||||
["index1"] = (owner) => RetrieveArrayIndex(owner, 1),
|
||||
["index2"] = (owner) => RetrieveArrayIndex(owner, 2),
|
||||
};
|
||||
|
||||
private static ArrayObject3D FindParentArray(IObject3D item, int wantLevel)
|
||||
{
|
||||
int foundLevel = 0;
|
||||
// look through all the parents
|
||||
foreach (var parent in item.Parents())
|
||||
{
|
||||
// if it is a sheet
|
||||
if (parent is ArrayObject3D arrayObject)
|
||||
{
|
||||
if (foundLevel == wantLevel)
|
||||
{
|
||||
return arrayObject;
|
||||
}
|
||||
|
||||
foundLevel++;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int RetrieveArrayIndex(IObject3D item, int level)
|
||||
{
|
||||
var arrayObject = FindParentArray(item, level);
|
||||
|
||||
if (arrayObject != null)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var child in arrayObject.Children)
|
||||
{
|
||||
if (!(child is OperationSourceObject3D))
|
||||
{
|
||||
if (child.DescendantsAndSelf().Where(i => i == item).Any())
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string ReplaceConstantsWithValues(IObject3D owner, string stringWithConstants)
|
||||
{
|
||||
string Replace(string inputString, string setting)
|
||||
{
|
||||
if (constants.ContainsKey(setting))
|
||||
{
|
||||
var value = constants[setting];
|
||||
|
||||
// braces then brackets replacement
|
||||
inputString = inputString.Replace("[" + setting + "]", value(owner).ToString());
|
||||
}
|
||||
|
||||
return inputString;
|
||||
}
|
||||
|
||||
MatchCollection matches = ConstantFinder.Matches(stringWithConstants);
|
||||
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
var replacementTerm = matches[i].Value;
|
||||
stringWithConstants = Replace(stringWithConstants, replacementTerm);
|
||||
}
|
||||
|
||||
return stringWithConstants;
|
||||
}
|
||||
|
||||
private static string GetDisplayName(PropertyInfo prop)
|
||||
{
|
||||
var nameAttribute = prop.GetCustomAttributes(true).OfType<DisplayNameAttribute>().FirstOrDefault();
|
||||
return nameAttribute?.DisplayName ?? prop.Name.SplitCamelCase();
|
||||
}
|
||||
|
||||
private static string SearchSiblingProperties(IObject3D owner, string inExpression)
|
||||
[HideMeterialAndColor]
|
||||
[WebPageLink("Documentation", "Open", "https://www.matterhackers.com/support/mattercontrol-variable-support")]
|
||||
[MarkDownDescription("[BETA] - Experimental support for variables and equations with a sheets like interface.")]
|
||||
public class SheetObject3D : Object3D, IStaticThumbnail, IVariableContainer
|
||||
{
|
||||
private SheetData _sheetData;
|
||||
public SheetData SheetData
|
||||
{
|
||||
var parent = owner.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
var matches = ConstantFinder.Matches(inExpression);
|
||||
get => _sheetData;
|
||||
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
var constant = matches[i].Value;
|
||||
// split inExpression on .
|
||||
var splitExpression = constant.Split('.');
|
||||
if (splitExpression.Length == 2)
|
||||
{
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
// skip if owner
|
||||
if (child != owner)
|
||||
{
|
||||
var itemName = splitExpression[0];
|
||||
var propertyName = splitExpression[1];
|
||||
// if child has the same name as itemName
|
||||
if (child.Name == itemName)
|
||||
{
|
||||
// enumerate public properties on child
|
||||
foreach (var property in child.GetType().GetProperties())
|
||||
{
|
||||
var displayName = GetDisplayName(property);
|
||||
// if property name matches propertyName
|
||||
if (displayName == propertyName)
|
||||
{
|
||||
// return the value
|
||||
var expression = child.GetType().GetProperty(property.Name).GetValue(child, null).ToString();
|
||||
var value = SheetObject3D.EvaluateExpression<double>(child, expression).ToString();
|
||||
inExpression = inExpression.Replace("[" + constant + "]", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_sheetData != value)
|
||||
{
|
||||
if (_sheetData != null)
|
||||
{
|
||||
_sheetData.Recalculated -= SendInvalidateToAll;
|
||||
}
|
||||
|
||||
_sheetData = value;
|
||||
_sheetData.Recalculated += SendInvalidateToAll;
|
||||
}
|
||||
}
|
||||
|
||||
return inExpression;
|
||||
}
|
||||
|
||||
public static T EvaluateExpression<T>(IObject3D owner, string inExpression)
|
||||
{
|
||||
var inputExpression = inExpression;
|
||||
var printer = owner.ContainingPrinter();
|
||||
if (printer != null)
|
||||
{
|
||||
inputExpression = printer.Settings.ReplaceSettingsNamesWithValues(inputExpression, false);
|
||||
}
|
||||
|
||||
inputExpression = SearchSiblingProperties(owner, inputExpression);
|
||||
|
||||
inputExpression = ReplaceConstantsWithValues(owner, inputExpression);
|
||||
|
||||
// check if the expression is an equation (starts with "=")
|
||||
if (inputExpression.Length > 0 && inputExpression[0] == '=')
|
||||
{
|
||||
// look through all the parents
|
||||
var sheet = FindFirstSheet(owner);
|
||||
if (sheet != null)
|
||||
{
|
||||
// try to manage the cell into the correct data type
|
||||
string value = sheet.SheetData.EvaluateExpression(inputExpression);
|
||||
return CastResult<T>(value, inputExpression);
|
||||
}
|
||||
|
||||
// could not find a sheet, try to evaluate the expression directly
|
||||
var evaluator = new Expression(inputExpression.Substring(1).ToLower());
|
||||
if (evaluator.checkSyntax())
|
||||
{
|
||||
Debug.WriteLine(evaluator.getErrorMessage());
|
||||
}
|
||||
|
||||
return CastResult<T>(evaluator.calculate().ToString(), inputExpression);
|
||||
}
|
||||
else // not an equation so try to parse it directly
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)(object)inputExpression;
|
||||
}
|
||||
|
||||
double.TryParse(inputExpression, out var result);
|
||||
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
return (T)(object)result;
|
||||
}
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return (T)(object)(int)Math.Round(result);
|
||||
}
|
||||
|
||||
return (T)(object)(int)0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the sheet that the given item will reference
|
||||
/// </summary>
|
||||
/// <param name="item">The item to start the search from</param>
|
||||
/// <returns></returns>
|
||||
private static SheetObject3D FindFirstSheet(IObject3D item)
|
||||
{
|
||||
// look through all the parents
|
||||
foreach (var parent in item.Parents())
|
||||
{
|
||||
// then each child of any give parent
|
||||
foreach (var sibling in parent.Children)
|
||||
{
|
||||
// if it is a sheet
|
||||
if (sibling != item
|
||||
&& sibling is SheetObject3D sheet)
|
||||
{
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there are any references from the item to the sheet.
|
||||
/// </summary>
|
||||
/// <param name="itemToCheck">The item to validate editable properties on</param>
|
||||
/// <param name="sheetToCheck">The sheet to check if this object references</param>
|
||||
/// <returns></returns>
|
||||
public static bool NeedsRebuild(IObject3D itemToCheck, InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if (!invalidateArgs.InvalidateType.HasFlag(InvalidateType.SheetUpdated))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invalidateArgs.Source is SheetObject3D sheet)
|
||||
{
|
||||
// Check if the sheet is the first sheet parent of this item (if not it will not change it's data).
|
||||
if (FindFirstSheet(itemToCheck) == sheet)
|
||||
{
|
||||
return HasExpressionWithString(itemToCheck, "=", true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectOrExpression> GetActiveExpressions(IObject3D item, string checkForString, bool startsWith)
|
||||
public static async Task<SheetObject3D> Create()
|
||||
{
|
||||
foreach (var property in PublicPropertyEditor.GetEditablePropreties(item))
|
||||
{
|
||||
var propertyValue = property.Value;
|
||||
var item = new SheetObject3D
|
||||
{
|
||||
SheetData = new SheetData(5, 5)
|
||||
};
|
||||
await item.Rebuild();
|
||||
return item;
|
||||
}
|
||||
|
||||
if (propertyValue is DirectOrExpression directOrExpression)
|
||||
{
|
||||
if (startsWith)
|
||||
{
|
||||
if (directOrExpression.Expression.StartsWith(checkForString))
|
||||
{
|
||||
// WIP: check if the value has actually changed, this will update every object on any cell change
|
||||
yield return directOrExpression;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(directOrExpression.Expression.Contains(checkForString))
|
||||
{
|
||||
yield return directOrExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public string ThumbnailName => "Sheet";
|
||||
|
||||
public static IEnumerable<int> GetComponentExpressions(ComponentObject3D component, string checkForString, bool startsWith)
|
||||
{
|
||||
for (var i = 0; i < component.SurfacedEditors.Count; i++)
|
||||
{
|
||||
var (cellId, cellData) = component.DecodeContent(i);
|
||||
|
||||
if (cellId != null)
|
||||
{
|
||||
if (startsWith)
|
||||
{
|
||||
if (cellData.StartsWith(checkForString))
|
||||
{
|
||||
// WIP: check if the value has actually changed, this will update every object on any cell change
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cellData.Contains(checkForString))
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private static object loadLock = new object();
|
||||
private static IObject3D sheetObject;
|
||||
|
||||
public static bool HasExpressionWithString(IObject3D itemToCheck, string checkForString, bool startsWith)
|
||||
{
|
||||
foreach (var item in itemToCheck.DescendantsAndSelf())
|
||||
{
|
||||
if (GetActiveExpressions(item, checkForString, startsWith).Any()
|
||||
|| (itemToCheck is ComponentObject3D component
|
||||
&& GetComponentExpressions(component, checkForString, startsWith).Any()))
|
||||
public override Mesh Mesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Children.Count == 0)
|
||||
{
|
||||
// three is one so return true
|
||||
return true;
|
||||
lock (loadLock)
|
||||
{
|
||||
if (sheetObject == null)
|
||||
{
|
||||
sheetObject = MeshContentProvider.LoadMCX(StaticData.Instance.OpenStream(Path.Combine("Stls", "sheet_icon.mcx")));
|
||||
}
|
||||
|
||||
this.Children.Modify((list) =>
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
list.Add(sheetObject.Clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T CastResult<T>(string value, string inputExpression)
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
// if parsing the equation resulted in NaN as the output
|
||||
if (value == "NaN")
|
||||
{
|
||||
// return the actual expression
|
||||
return (T)(object)inputExpression;
|
||||
}
|
||||
set => base.Mesh = value;
|
||||
}
|
||||
|
||||
// get the value of the cell
|
||||
return (T)(object)value;
|
||||
}
|
||||
public SheetObject3D()
|
||||
{
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
if (double.TryParse(value, out double doubleValue)
|
||||
&& !double.IsNaN(doubleValue)
|
||||
&& !double.IsInfinity(doubleValue))
|
||||
{
|
||||
return (T)(object)doubleValue;
|
||||
}
|
||||
// else return an error
|
||||
return (T)(object).1;
|
||||
}
|
||||
public override bool Printable => false;
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
if (double.TryParse(value, out double doubleValue)
|
||||
&& !double.IsNaN(doubleValue)
|
||||
&& !double.IsInfinity(doubleValue))
|
||||
{
|
||||
return (T)(object)(int)Math.Round(doubleValue);
|
||||
}
|
||||
// else return an error
|
||||
return (T)(object)1;
|
||||
}
|
||||
|
||||
return (T)(object)default(T);
|
||||
}
|
||||
}
|
||||
public class UpdateItem
|
||||
{
|
||||
internal int depth;
|
||||
internal IObject3D item;
|
||||
internal RebuildLock rebuildLock;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var state = rebuildLock == null ? "unlocked" : "locked";
|
||||
return $"{depth} {state} - {item}";
|
||||
}
|
||||
}
|
||||
|
||||
private void SendInvalidateToAll(object s, EventArgs e)
|
||||
{
|
||||
var updateItems = Expressions.SortAndLockUpdateItems(this.Parent, (item) =>
|
||||
{
|
||||
if (item == this || item.Parent == this)
|
||||
{
|
||||
// don't process this
|
||||
return false;
|
||||
}
|
||||
else if (item.Parent is ArrayObject3D arrayObject3D
|
||||
&& arrayObject3D.SourceContainer != item)
|
||||
{
|
||||
// don't process the copied children of an array object
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
Expressions.SendInvalidateInRebuildOrder(updateItems, InvalidateType.SheetUpdated, this);
|
||||
}
|
||||
|
||||
public string EvaluateExpression(string inputExpression)
|
||||
{
|
||||
return SheetData.EvaluateExpression(inputExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2022, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -32,29 +32,29 @@ using MatterHackers.DataConverters3D;
|
|||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
[TypeConverter(typeof(StringOrExpression))]
|
||||
public class StringOrExpression : DirectOrExpression
|
||||
{
|
||||
public string Value(IObject3D owner)
|
||||
{
|
||||
var rebuilding = owner.RebuildLocked;
|
||||
var value = SheetObject3D.EvaluateExpression<string>(owner, Expression);
|
||||
if (rebuilding)
|
||||
{
|
||||
ExpressionValueAtLastRebuild = value.ToString();
|
||||
}
|
||||
[TypeConverter(typeof(StringOrExpression))]
|
||||
public class StringOrExpression : DirectOrExpression
|
||||
{
|
||||
public string Value(IObject3D owner)
|
||||
{
|
||||
var rebuilding = owner.RebuildLocked;
|
||||
var value = Expressions.EvaluateExpression<string>(owner, Expression);
|
||||
if (rebuilding)
|
||||
{
|
||||
ExpressionValueAtLastRebuild = value.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public StringOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
public StringOrExpression(string expression)
|
||||
{
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public static implicit operator StringOrExpression(string expression)
|
||||
{
|
||||
return new StringOrExpression(expression);
|
||||
}
|
||||
}
|
||||
public static implicit operator StringOrExpression(string expression)
|
||||
{
|
||||
return new StringOrExpression(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.RayTracer;
|
||||
|
|
@ -143,22 +144,19 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
return BoundingVolumeHierarchy.CreateNewHierachy(allPolys, bvhCreationOptions);
|
||||
}
|
||||
|
||||
public Task Create(IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public Task Create(Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return Create(progress, null);
|
||||
}
|
||||
|
||||
public Task Create(IProgress<ProgressStatus> progress, CancellationTokenSource cancellationTokenSource)
|
||||
public Task Create(Action<double, string> progress, CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
var selectedItem = scene.SelectedItem;
|
||||
|
||||
using (new DataConverters3D.SelectionMaintainer(scene))
|
||||
{
|
||||
var status = new ProgressStatus
|
||||
{
|
||||
Status = "Enter"
|
||||
};
|
||||
progress?.Report(status);
|
||||
var status = "Enter".Localize();
|
||||
progress?.Invoke(0, status);
|
||||
|
||||
// Get visible meshes for each of them
|
||||
var allBedItems = scene.Children.SelectMany(i => i.VisibleMeshes());
|
||||
|
|
@ -204,12 +202,12 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
}
|
||||
|
||||
// get all the support plane intersections
|
||||
status.Status = "Trace";
|
||||
progress?.Report(status);
|
||||
status = "Trace".Localize();
|
||||
progress?.Invoke(0, status);
|
||||
var detectedPlanes = DetectRequiredSupportByTracing(gridBounds, allBedItems);
|
||||
|
||||
status.Status = "Columns";
|
||||
progress?.Report(status);
|
||||
status = "Columns".Localize();
|
||||
progress?.Invoke(0, status);
|
||||
|
||||
// minimum height requiring support is 1/2 the layer height
|
||||
AddSupportColumns(gridBounds, detectedPlanes);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ namespace MatterControlLib
|
|||
}
|
||||
}
|
||||
|
||||
private static void IndexZipFile(string filePath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
private static void IndexZipFile(string filePath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
Dictionary<string, HelpArticle> helpArticles;
|
||||
|
||||
|
|
@ -126,19 +126,13 @@ namespace MatterControlLib
|
|||
ProcessHelpTree(rootHelpArticle, helpArticles);
|
||||
}
|
||||
|
||||
var progressStatus = new ProgressStatus()
|
||||
{
|
||||
//Status = "",
|
||||
Progress0To1 = 0
|
||||
};
|
||||
|
||||
var count = zip.Entries.Count;
|
||||
double i = 0;
|
||||
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
progressStatus.Progress0To1 = i++ / count;
|
||||
progress.Report(progressStatus);
|
||||
var progress0To1 = i++ / (double)count;
|
||||
progress?.Invoke(progress0To1, null);
|
||||
|
||||
// Observe and abort on cancellationToken signal
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
public bool ExportPossible(ILibraryAsset libraryItem) => true;
|
||||
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var firstItem = libraryItems.OfType<ILibraryAsset>().FirstOrDefault();
|
||||
if (firstItem is ILibraryAsset libraryItem)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
public bool ExportPossible(ILibraryAsset libraryItem) => true;
|
||||
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var streamItems = libraryItems.OfType<ILibraryAssetStream>();
|
||||
if (streamItems.Any())
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
return container;
|
||||
}
|
||||
|
||||
public virtual async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public virtual async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var firstItem = libraryItems.OfType<ILibraryAsset>().FirstOrDefault();
|
||||
if (firstItem != null)
|
||||
|
|
@ -185,16 +185,12 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
}
|
||||
else if (firstItem is ILibraryObject3D object3DItem)
|
||||
{
|
||||
var status = new ProgressStatus()
|
||||
{
|
||||
Status = "Saving Asset".Localize()
|
||||
};
|
||||
var status = "Saving Asset".Localize();
|
||||
|
||||
loadedItem = await object3DItem.CreateContent(null);
|
||||
await loadedItem.PersistAssets((percentComplete, text) =>
|
||||
await loadedItem.PersistAssets((progress2, text) =>
|
||||
{
|
||||
status.Progress0To1 = percentComplete;
|
||||
progress.Report(status);
|
||||
progress?.Invoke(progress2, status);
|
||||
});
|
||||
}
|
||||
else if (assetStream != null)
|
||||
|
|
@ -340,7 +336,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
return accumulatedStream;
|
||||
}
|
||||
|
||||
private void ApplyStreamPipelineAndExport(GCodeFileStream gCodeFileStream, string outputPath, IProgress<ProgressStatus> progress)
|
||||
private void ApplyStreamPipelineAndExport(GCodeFileStream gCodeFileStream, string outputPath, Action<double, string> progress)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -349,10 +345,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
var totalLines = gCodeFileStream.GCodeFile.LineCount;
|
||||
var currentLine = 0;
|
||||
|
||||
var status = new ProgressStatus()
|
||||
{
|
||||
Status = "Writing G-Code".Localize()
|
||||
};
|
||||
var status = "Writing G-Code".Localize();
|
||||
|
||||
// Run each line from the source gcode through the loaded pipeline and dump to the output location
|
||||
using (var file = new StreamWriter(outputPath))
|
||||
|
|
@ -387,8 +380,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
if (currentLine % 1024 == 0)
|
||||
{
|
||||
status.Progress0To1 = currentLine / (double)totalLines;
|
||||
progress.Report(status);
|
||||
progress?.Invoke(currentLine / (double)totalLines, status);
|
||||
}
|
||||
currentLine++;
|
||||
}
|
||||
|
|
@ -403,7 +395,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
}
|
||||
}
|
||||
|
||||
private void ApplyStreamPipelineAndExport(string gcodeFilename, string outputPath, IProgress<ProgressStatus> progress)
|
||||
private void ApplyStreamPipelineAndExport(string gcodeFilename, string outputPath, Action<double, string> progress)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ namespace MatterHackers.MatterControl
|
|||
|
||||
void Initialize(PrinterConfig printer);
|
||||
|
||||
Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken);
|
||||
Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken);
|
||||
|
||||
bool Enabled { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
public bool ExportPossible(ILibraryAsset libraryItem) => true;
|
||||
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,67 +38,69 @@ using MatterHackers.Localizations;
|
|||
|
||||
namespace MatterHackers.MatterControl.Library.Export
|
||||
{
|
||||
public static class MeshExport
|
||||
{
|
||||
public static async Task<bool> ExportMesh(ILibraryItem source, string filePathToSave, bool mergeMeshes, IProgress<ProgressStatus> progress)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = new ProgressStatus()
|
||||
{
|
||||
Status = "Exporting".Localize()
|
||||
};
|
||||
|
||||
if (source is ILibraryObject3D contentItem)
|
||||
{
|
||||
// If the content is an IObject3D, then we need to load it and MeshFileIO save to the target path
|
||||
var content = await contentItem.GetObject3D(null);
|
||||
return Object3D.Save(content, filePathToSave, mergeMeshes, CancellationToken.None, reportProgress: (ratio, name) =>
|
||||
public static class MeshExport
|
||||
{
|
||||
/// <summary>
|
||||
/// Export the source item(s) to a single or multiple STL files
|
||||
/// </summary>
|
||||
/// <param name="source">The source item(s)</param>
|
||||
/// <param name="filePathToSave">The destination to save to (only the folder will be used in saveMultipleStls</param>
|
||||
/// <param name="mergeMeshes">Do a Combine on the individual meshes to ensure they are a single item</param>
|
||||
/// <param name="saveMultipleStls">Save multiple stls using the name of the objects in the scene</param>
|
||||
/// <param name="progress">Update the save progress</param>
|
||||
/// <returns>If the function succeded</returns>
|
||||
public static async Task<bool> ExportMesh(ILibraryItem source, string filePathToSave, bool mergeMeshes, Action<double, string> progress, bool saveMultipleStls = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (source is ILibraryObject3D contentItem)
|
||||
{
|
||||
// If the content is an IObject3D, then we need to load it and MeshFileIO save to the target path
|
||||
var content = await contentItem.GetObject3D(null);
|
||||
return Object3D.Save(content, filePathToSave, mergeMeshes, CancellationToken.None, reportProgress: (ratio, name) =>
|
||||
{
|
||||
status.Progress0To1 = ratio;
|
||||
progress.Report(status);
|
||||
});
|
||||
}
|
||||
else if (source is ILibraryAssetStream streamContent)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filePathToSave))
|
||||
{
|
||||
// If the file is already the target type, it just needs copied to the target path
|
||||
if (Path.GetExtension(streamContent.FileName).ToUpper() == Path.GetExtension(filePathToSave).ToUpper())
|
||||
{
|
||||
using (var result = await streamContent.GetStream(null))
|
||||
using (var fileStream = File.Create(filePathToSave))
|
||||
{
|
||||
result.Stream.CopyTo(fileStream);
|
||||
}
|
||||
progress?.Invoke(ratio, "Exporting".Localize());
|
||||
}, saveMultipleStls: saveMultipleStls);
|
||||
}
|
||||
else if (source is ILibraryAssetStream streamContent)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filePathToSave))
|
||||
{
|
||||
// If the file is already the target type, it just needs copied to the target path
|
||||
if (Path.GetExtension(streamContent.FileName).ToUpper() == Path.GetExtension(filePathToSave).ToUpper())
|
||||
{
|
||||
using (var result = await streamContent.GetStream(null))
|
||||
using (var fileStream = File.Create(filePathToSave))
|
||||
{
|
||||
result.Stream.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we need to load the content and MeshFileIO save to the target path
|
||||
using (var result = await streamContent.GetStream(null))
|
||||
{
|
||||
IObject3D item = Object3D.Load(result.Stream, Path.GetExtension(streamContent.FileName), CancellationToken.None);
|
||||
if (item != null)
|
||||
{
|
||||
return Object3D.Save(item, filePathToSave, mergeMeshes, CancellationToken.None, reportProgress: (ratio, name) =>
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we need to load the content and MeshFileIO save to the target path
|
||||
using (var result = await streamContent.GetStream(null))
|
||||
{
|
||||
IObject3D item = Object3D.Load(result.Stream, Path.GetExtension(streamContent.FileName), CancellationToken.None);
|
||||
if (item != null)
|
||||
{
|
||||
return Object3D.Save(item, filePathToSave, mergeMeshes, CancellationToken.None, reportProgress: (ratio, name) =>
|
||||
{
|
||||
status.Progress0To1 = ratio;
|
||||
progress.Report(status);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine("Error exporting file: " + ex.Message);
|
||||
}
|
||||
progress?.Invoke(ratio, null);
|
||||
}, saveMultipleStls: saveMultipleStls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine("Error exporting file: " + ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems,
|
||||
string outputPath,
|
||||
IProgress<ProgressStatus> progress,
|
||||
Action<double, string> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var first = true;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace MatterHackers.MatterControl.Library.Export
|
|||
|
||||
public bool ExportPossible(ILibraryAsset libraryItem) => true;
|
||||
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
||||
public async Task<List<ValidationError>> Generate(IEnumerable<ILibraryItem> libraryItems, string outputPath, Action<double, string> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var streamItems = libraryItems.OfType<ILibraryAssetStream>();
|
||||
if (streamItems.Any())
|
||||
|
|
|
|||
|
|
@ -43,456 +43,449 @@ using MatterHackers.MatterControl.DataStorage;
|
|||
|
||||
namespace MatterHackers.MatterControl.Library
|
||||
{
|
||||
public class FileSystemContainer : WritableContainer, ICustomSearch
|
||||
{
|
||||
private FileSystemWatcher directoryWatcher;
|
||||
public class FileSystemContainer : WritableContainer, ICustomSearch
|
||||
{
|
||||
private FileSystemWatcher directoryWatcher;
|
||||
|
||||
private bool isActiveContainer;
|
||||
private bool isDirty;
|
||||
private string keywordFilter;
|
||||
private bool isActiveContainer;
|
||||
private bool isDirty;
|
||||
private string keywordFilter;
|
||||
|
||||
private long lastTimeContentsChanged;
|
||||
private long lastTimeContentsChanged;
|
||||
|
||||
private RunningInterval waitingForRefresh;
|
||||
private RunningInterval waitingForRefresh;
|
||||
|
||||
public FileSystemContainer(string fullPath)
|
||||
{
|
||||
this.CustomSearch = this;
|
||||
this.FullPath = fullPath;
|
||||
this.Name = Path.GetFileName(fullPath);
|
||||
public FileSystemContainer(string fullPath)
|
||||
{
|
||||
this.CustomSearch = this;
|
||||
this.FullPath = fullPath;
|
||||
this.Name = Path.GetFileName(fullPath);
|
||||
|
||||
this.IsProtected = false;
|
||||
this.IsProtected = false;
|
||||
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
this.Items = new SafeList<ILibraryItem>();
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
this.Items = new SafeList<ILibraryItem>();
|
||||
|
||||
if (AggContext.OperatingSystem == OSType.Windows
|
||||
&& Directory.Exists(fullPath))
|
||||
{
|
||||
directoryWatcher = new FileSystemWatcher(fullPath);
|
||||
directoryWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
|
||||
directoryWatcher.Changed += DirectoryContentsChanged;
|
||||
directoryWatcher.Created += DirectoryContentsChanged;
|
||||
directoryWatcher.Deleted += DirectoryContentsChanged;
|
||||
directoryWatcher.Renamed += DirectoryContentsChanged;
|
||||
directoryWatcher.IncludeSubdirectories = false;
|
||||
if (AggContext.OperatingSystem == OSType.Windows
|
||||
&& Directory.Exists(fullPath))
|
||||
{
|
||||
directoryWatcher = new FileSystemWatcher(fullPath);
|
||||
directoryWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
|
||||
directoryWatcher.Changed += DirectoryContentsChanged;
|
||||
directoryWatcher.Created += DirectoryContentsChanged;
|
||||
directoryWatcher.Deleted += DirectoryContentsChanged;
|
||||
directoryWatcher.Renamed += DirectoryContentsChanged;
|
||||
directoryWatcher.IncludeSubdirectories = false;
|
||||
|
||||
// Begin watching.
|
||||
directoryWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
// Begin watching.
|
||||
directoryWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Save(ILibraryItem item, IObject3D content)
|
||||
{
|
||||
if (item is FileSystemFileItem fileItem)
|
||||
{
|
||||
if (fileItem.FilePath.Contains(ApplicationDataStorage.Instance.ApplicationLibraryDataPath))
|
||||
{
|
||||
// save using the normal uncompressed mcx file
|
||||
// Serialize the scene to disk using a modified Json.net pipeline with custom ContractResolvers and JsonConverters
|
||||
File.WriteAllText(fileItem.FilePath, content.ToJson().Result);
|
||||
|
||||
this.OnItemContentChanged(new LibraryItemChangedEventArgs(fileItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationController.Instance.Tasks.Execute(
|
||||
"Saving Changes".Localize(),
|
||||
null,
|
||||
async (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var status = new ProgressStatus()
|
||||
{
|
||||
Status = "Saving Asset".Localize()
|
||||
};
|
||||
|
||||
var directory = Path.GetDirectoryName(fileItem.FilePath);
|
||||
var filename = Path.GetFileNameWithoutExtension(fileItem.FilePath);
|
||||
var backupName = Path.Combine(directory, Path.ChangeExtension(filename + "_bak", ".mcx"));
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(backupName))
|
||||
{
|
||||
File.Delete(backupName);
|
||||
}
|
||||
|
||||
// rename any existing file
|
||||
if (File.Exists(fileItem.FilePath))
|
||||
{
|
||||
File.Move(fileItem.FilePath, backupName);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// make sure we have all the mesh items in the cache for saving to the archive
|
||||
await content.PersistAssets((percentComplete, text) =>
|
||||
{
|
||||
status.Progress0To1 = percentComplete * .9;
|
||||
reporter.Report(status);
|
||||
}, true);
|
||||
|
||||
var persistableItems = content.GetPersistable(true);
|
||||
var persistCount = persistableItems.Count();
|
||||
var savedCount = 0;
|
||||
|
||||
// save to a binary mcx file (a zip with a scene.mcx and an assets folder)
|
||||
using (var file = File.OpenWrite(fileItem.FilePath))
|
||||
{
|
||||
using (var zip = new ZipArchive(file, ZipArchiveMode.Create))
|
||||
{
|
||||
var savedItems = new HashSet<string>();
|
||||
foreach (var persistableItem in persistableItems)
|
||||
{
|
||||
string sourcePath = null;
|
||||
if (persistableItem.MeshPath != null)
|
||||
{
|
||||
sourcePath = Path.Combine(ApplicationDataStorage.Instance.LibraryAssetsPath, Path.GetFileName(persistableItem.MeshPath));
|
||||
}
|
||||
|
||||
if (persistableItem is AssetObject3D assetObject3D)
|
||||
{
|
||||
sourcePath = Path.Combine(ApplicationDataStorage.Instance.LibraryAssetsPath, Path.GetFileName(assetObject3D.AssetPath));
|
||||
}
|
||||
|
||||
var destFilename = Path.GetFileName(sourcePath);
|
||||
if (File.Exists(sourcePath))
|
||||
{
|
||||
if (!savedItems.Contains(destFilename))
|
||||
{
|
||||
savedItems.Add(destFilename);
|
||||
var assetName = Path.Combine("Assets", destFilename);
|
||||
zip.CreateEntryFromFile(sourcePath, assetName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
savedCount++;
|
||||
status.Progress0To1 = .9 + .1 * (savedCount / persistCount);
|
||||
reporter.Report(status);
|
||||
}
|
||||
|
||||
var sceneEntry = zip.CreateEntry("scene.mcx");
|
||||
using (var sceneStream = sceneEntry.Open())
|
||||
{
|
||||
using (var writer = new StreamWriter(sceneStream))
|
||||
{
|
||||
writer.Write(await content.ToJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the scene to disk using a modified Json.net pipeline with custom ContractResolvers and JsonConverters
|
||||
this.OnItemContentChanged(new LibraryItemChangedEventArgs(fileItem));
|
||||
|
||||
// remove the existing file after a successfull save
|
||||
try
|
||||
{
|
||||
if (File.Exists(backupName))
|
||||
{
|
||||
File.Delete(backupName);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetThumbnail(ILibraryItem item, int width, int height, ImageBuffer imageBuffer)
|
||||
{
|
||||
#if DEBUG
|
||||
// throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override ICustomSearch CustomSearch { get; }
|
||||
|
||||
public string FullPath { get; protected set; }
|
||||
|
||||
// Indicates if the new AMF file should use the original file name incremented until no name collision occurs
|
||||
public bool UseIncrementedNameDuringTypeChange { get; internal set; }
|
||||
|
||||
public override void Activate()
|
||||
{
|
||||
this.isActiveContainer = true;
|
||||
base.Activate();
|
||||
}
|
||||
|
||||
public async override void Add(IEnumerable<ILibraryItem> items)
|
||||
{
|
||||
if (!items.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
// turn them off whil ewe add the content
|
||||
directoryWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(this.FullPath);
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case CreateFolderItem newFolder:
|
||||
string targetFolderPath = Path.Combine(this.FullPath, newFolder.Name);
|
||||
|
||||
// TODO: write adaption of GetNonCollidingName for directories
|
||||
Directory.CreateDirectory(targetFolderPath);
|
||||
this.isDirty = true;
|
||||
|
||||
break;
|
||||
|
||||
case ILibraryAssetStream streamItem:
|
||||
string targetPath = Path.Combine(this.FullPath, streamItem.FileName);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(targetPath))
|
||||
{
|
||||
targetPath = GetNonCollidingName(Path.GetFileName(targetPath));
|
||||
}
|
||||
|
||||
using (var outputStream = File.OpenWrite(targetPath))
|
||||
using (var contentStream = await streamItem.GetStream(null))
|
||||
{
|
||||
contentStream.Stream.CopyTo(outputStream);
|
||||
}
|
||||
|
||||
this.Items.Add(new FileSystemFileItem(targetPath));
|
||||
this.isDirty = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
ApplicationController.Instance.LogError($"Error adding file: {targetPath}\r\n{ex.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
// turn them back on
|
||||
directoryWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
if (this.isDirty)
|
||||
{
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyFilter(string filter, ILibraryContext libraryContext)
|
||||
{
|
||||
keywordFilter = filter;
|
||||
this.Load();
|
||||
this.OnContentChanged();
|
||||
}
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
keywordFilter = null;
|
||||
this.Load();
|
||||
this.OnContentChanged();
|
||||
}
|
||||
|
||||
public override void Deactivate()
|
||||
{
|
||||
this.isActiveContainer = false;
|
||||
base.Deactivate();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
directoryWatcher.EnableRaisingEvents = false;
|
||||
|
||||
directoryWatcher.Changed -= DirectoryContentsChanged;
|
||||
directoryWatcher.Created -= DirectoryContentsChanged;
|
||||
directoryWatcher.Deleted -= DirectoryContentsChanged;
|
||||
directoryWatcher.Renamed -= DirectoryContentsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
this.Load(false);
|
||||
}
|
||||
|
||||
public void Load(bool recursive)
|
||||
{
|
||||
SearchOption searchDepth = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
try
|
||||
{
|
||||
string filter = keywordFilter?.Trim() ?? "";
|
||||
|
||||
var allFiles = Directory.GetFiles(FullPath, "*.*", searchDepth);
|
||||
|
||||
var zipFiles = allFiles.Where(f => Path.GetExtension(f).IndexOf(".zip", StringComparison.OrdinalIgnoreCase) != -1);
|
||||
|
||||
var nonZipFiles = allFiles.Except(zipFiles);
|
||||
|
||||
if (filter == "")
|
||||
{
|
||||
var directories = Directory.GetDirectories(FullPath, "*.*", searchDepth).Select(p => new DirectoryContainerLink(p)).ToList<ILibraryContainerLink>();
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>(directories.Concat(zipFiles.Select(f => new LocalZipContainerLink(f))));
|
||||
var libraryFiles = allFiles.Where(f => Path.GetExtension(f).IndexOf(".library", StringComparison.OrdinalIgnoreCase) != -1);
|
||||
this.ChildContainers.AddRange(libraryFiles.Select(f => LibraryJsonFile.ContainerFromLocalFile(f)));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
}
|
||||
|
||||
var matchedFiles = nonZipFiles.Where(filePath =>
|
||||
{
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
|
||||
return (filter == "" || FileNameContainsFilter(filePath, filter))
|
||||
&& ApplicationController.Instance.Library.IsContentFileType(fileName);
|
||||
});
|
||||
|
||||
this.ChildContainers.Modify((list) =>
|
||||
{
|
||||
list.Sort((a, b) => a.Name.CompareTo(b.Name));
|
||||
});
|
||||
|
||||
// Matched files projected onto FileSystemFileItem
|
||||
this.Items = new SafeList<ILibraryItem>(matchedFiles.OrderBy(f => f).Select(f => new FileSystemFileItem(f)));
|
||||
|
||||
var indexMd = nonZipFiles.Where(f => f.EndsWith("index.md")).FirstOrDefault();
|
||||
if (indexMd != null)
|
||||
public override async Task Save(ILibraryItem item, IObject3D content)
|
||||
{
|
||||
if (item is FileSystemFileItem fileItem)
|
||||
{
|
||||
if (fileItem.FilePath.Contains(ApplicationDataStorage.Instance.ApplicationLibraryDataPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
HeaderMarkdown = File.ReadAllText(indexMd);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
// save using the normal uncompressed mcx file
|
||||
// Serialize the scene to disk using a modified Json.net pipeline with custom ContractResolvers and JsonConverters
|
||||
File.WriteAllText(fileItem.FilePath, content.ToJson().Result);
|
||||
|
||||
this.isDirty = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
this.Items = new SafeList<ILibraryItem>()
|
||||
{
|
||||
new MessageItem("Error loading container - " + ex.Message)
|
||||
};
|
||||
}
|
||||
}
|
||||
this.OnItemContentChanged(new LibraryItemChangedEventArgs(fileItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationController.Instance.Tasks.Execute(
|
||||
"Saving Changes".Localize(),
|
||||
null,
|
||||
async (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var directory = Path.GetDirectoryName(fileItem.FilePath);
|
||||
var filename = Path.GetFileNameWithoutExtension(fileItem.FilePath);
|
||||
var backupName = Path.Combine(directory, Path.ChangeExtension(filename + "_bak", ".mcx"));
|
||||
|
||||
public override void Remove(IEnumerable<ILibraryItem> items)
|
||||
{
|
||||
if (AggContext.OperatingSystem == OSType.Windows)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is FileSystemItem fileItem
|
||||
&& File.Exists(fileItem.FilePath))
|
||||
{
|
||||
File.Delete(fileItem.FilePath);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
if (File.Exists(backupName))
|
||||
{
|
||||
File.Delete(backupName);
|
||||
}
|
||||
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
// rename any existing file
|
||||
if (File.Exists(fileItem.FilePath))
|
||||
{
|
||||
File.Move(fileItem.FilePath, backupName);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
private void DirectoryContentsChanged(object sender, EventArgs e)
|
||||
{
|
||||
// Flag for reload
|
||||
isDirty = true;
|
||||
// make sure we have all the mesh items in the cache for saving to the archive
|
||||
await content.PersistAssets((percentComplete, text) =>
|
||||
{
|
||||
reporter?.Invoke(percentComplete * .9, null);
|
||||
}, true);
|
||||
|
||||
lastTimeContentsChanged = UiThread.CurrentTimerMs;
|
||||
var persistableItems = content.GetPersistable(true);
|
||||
var persistCount = persistableItems.Count();
|
||||
var savedCount = 0;
|
||||
|
||||
// Only refresh content if we're the active container
|
||||
if (isActiveContainer
|
||||
&& waitingForRefresh == null)
|
||||
{
|
||||
waitingForRefresh = UiThread.SetInterval(WaitToRefresh, .5);
|
||||
}
|
||||
}
|
||||
// save to a binary mcx file (a zip with a scene.mcx and an assets folder)
|
||||
using (var file = File.OpenWrite(fileItem.FilePath))
|
||||
{
|
||||
using (var zip = new ZipArchive(file, ZipArchiveMode.Create))
|
||||
{
|
||||
var savedItems = new HashSet<string>();
|
||||
foreach (var persistableItem in persistableItems)
|
||||
{
|
||||
string sourcePath = null;
|
||||
if (persistableItem.MeshPath != null)
|
||||
{
|
||||
sourcePath = Path.Combine(ApplicationDataStorage.Instance.LibraryAssetsPath, Path.GetFileName(persistableItem.MeshPath));
|
||||
}
|
||||
|
||||
private bool FileNameContainsFilter(string filename, string filter)
|
||||
{
|
||||
string nameWithSpaces = Path.GetFileNameWithoutExtension(filename.Replace('_', ' '));
|
||||
if (persistableItem is AssetObject3D assetObject3D)
|
||||
{
|
||||
sourcePath = Path.Combine(ApplicationDataStorage.Instance.LibraryAssetsPath, Path.GetFileName(assetObject3D.AssetPath));
|
||||
}
|
||||
|
||||
// Split the filter on word boundaries and determine if all terms in the given file name
|
||||
foreach (string word in filter.Split(' '))
|
||||
{
|
||||
if (nameWithSpaces.IndexOf(word, StringComparison.OrdinalIgnoreCase) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var destFilename = Path.GetFileName(sourcePath);
|
||||
if (File.Exists(sourcePath))
|
||||
{
|
||||
if (!savedItems.Contains(destFilename))
|
||||
{
|
||||
savedItems.Add(destFilename);
|
||||
var assetName = Path.Combine("Assets", destFilename);
|
||||
zip.CreateEntryFromFile(sourcePath, assetName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
savedCount++;
|
||||
reporter?.Invoke(.9 + .1 * (savedCount / persistCount), null);
|
||||
}
|
||||
|
||||
private string GetNonCollidingName(string fileName)
|
||||
{
|
||||
// Switching from .stl, .obj or similar to AMF. Save the file and update the
|
||||
// the filename with an incremented (n) value to reflect the extension change in the UI
|
||||
var similarFileNames = Directory.GetFiles(this.FullPath, $"{Path.GetFileNameWithoutExtension(fileName)}.*");
|
||||
var sceneEntry = zip.CreateEntry("scene.mcx");
|
||||
using (var sceneStream = sceneEntry.Open())
|
||||
{
|
||||
using (var writer = new StreamWriter(sceneStream))
|
||||
{
|
||||
writer.Write(await content.ToJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ;
|
||||
var validName = agg_basics.GetNonCollidingName(fileName, (testName) => !File.Exists(testName));
|
||||
// Serialize the scene to disk using a modified Json.net pipeline with custom ContractResolvers and JsonConverters
|
||||
this.OnItemContentChanged(new LibraryItemChangedEventArgs(fileItem));
|
||||
|
||||
return validName;
|
||||
}
|
||||
// remove the existing file after a successfull save
|
||||
try
|
||||
{
|
||||
if (File.Exists(backupName))
|
||||
{
|
||||
File.Delete(backupName);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitToRefresh()
|
||||
{
|
||||
if (UiThread.CurrentTimerMs > lastTimeContentsChanged + 500
|
||||
&& waitingForRefresh != null)
|
||||
{
|
||||
UiThread.ClearInterval(waitingForRefresh);
|
||||
public override void SetThumbnail(ILibraryItem item, int width, int height, ImageBuffer imageBuffer)
|
||||
{
|
||||
#if DEBUG
|
||||
// throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
|
||||
waitingForRefresh = null;
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
public override ICustomSearch CustomSearch { get; }
|
||||
|
||||
public class DirectoryContainerLink : FileSystemItem, ILibraryContainerLink
|
||||
{
|
||||
public DirectoryContainerLink(string path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
public string FullPath { get; protected set; }
|
||||
|
||||
public bool IsReadOnly { get; set; } = false;
|
||||
// Indicates if the new AMF file should use the original file name incremented until no name collision occurs
|
||||
public bool UseIncrementedNameDuringTypeChange { get; internal set; }
|
||||
|
||||
public bool UseIncrementedNameDuringTypeChange { get; set; }
|
||||
public override void Activate()
|
||||
{
|
||||
this.isActiveContainer = true;
|
||||
base.Activate();
|
||||
}
|
||||
|
||||
public Task<ILibraryContainer> GetContainer(Action<double, string> reportProgress)
|
||||
{
|
||||
return Task.FromResult<ILibraryContainer>(
|
||||
new FileSystemContainer(this.FilePath)
|
||||
{
|
||||
UseIncrementedNameDuringTypeChange = this.UseIncrementedNameDuringTypeChange,
|
||||
Name = this.Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
public async override void Add(IEnumerable<ILibraryItem> items)
|
||||
{
|
||||
if (!items.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
// turn them off whil ewe add the content
|
||||
directoryWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(this.FullPath);
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case CreateFolderItem newFolder:
|
||||
string targetFolderPath = Path.Combine(this.FullPath, newFolder.Name);
|
||||
|
||||
// TODO: write adaption of GetNonCollidingName for directories
|
||||
Directory.CreateDirectory(targetFolderPath);
|
||||
this.isDirty = true;
|
||||
|
||||
break;
|
||||
|
||||
case ILibraryAssetStream streamItem:
|
||||
string targetPath = Path.Combine(this.FullPath, streamItem.FileName);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(targetPath))
|
||||
{
|
||||
targetPath = GetNonCollidingName(Path.GetFileName(targetPath));
|
||||
}
|
||||
|
||||
using (var outputStream = File.OpenWrite(targetPath))
|
||||
using (var contentStream = await streamItem.GetStream(null))
|
||||
{
|
||||
contentStream.Stream.CopyTo(outputStream);
|
||||
}
|
||||
|
||||
this.Items.Add(new FileSystemFileItem(targetPath));
|
||||
this.isDirty = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
ApplicationController.Instance.LogError($"Error adding file: {targetPath}\r\n{ex.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
// turn them back on
|
||||
directoryWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
if (this.isDirty)
|
||||
{
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyFilter(string filter, ILibraryContext libraryContext)
|
||||
{
|
||||
keywordFilter = filter;
|
||||
this.Load();
|
||||
this.OnContentChanged();
|
||||
}
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
keywordFilter = null;
|
||||
this.Load();
|
||||
this.OnContentChanged();
|
||||
}
|
||||
|
||||
public override void Deactivate()
|
||||
{
|
||||
this.isActiveContainer = false;
|
||||
base.Deactivate();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (directoryWatcher != null)
|
||||
{
|
||||
directoryWatcher.EnableRaisingEvents = false;
|
||||
|
||||
directoryWatcher.Changed -= DirectoryContentsChanged;
|
||||
directoryWatcher.Created -= DirectoryContentsChanged;
|
||||
directoryWatcher.Deleted -= DirectoryContentsChanged;
|
||||
directoryWatcher.Renamed -= DirectoryContentsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
this.Load(false);
|
||||
}
|
||||
|
||||
public void Load(bool recursive)
|
||||
{
|
||||
SearchOption searchDepth = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
try
|
||||
{
|
||||
string filter = keywordFilter?.Trim() ?? "";
|
||||
|
||||
var allFiles = Directory.GetFiles(FullPath, "*.*", searchDepth);
|
||||
|
||||
var zipFiles = allFiles.Where(f => Path.GetExtension(f).IndexOf(".zip", StringComparison.OrdinalIgnoreCase) != -1);
|
||||
|
||||
var nonZipFiles = allFiles.Except(zipFiles);
|
||||
|
||||
if (filter == "")
|
||||
{
|
||||
var directories = Directory.GetDirectories(FullPath, "*.*", searchDepth).Select(p => new DirectoryContainerLink(p)).ToList<ILibraryContainerLink>();
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>(directories.Concat(zipFiles.Select(f => new LocalZipContainerLink(f))));
|
||||
var libraryFiles = allFiles.Where(f => Path.GetExtension(f).IndexOf(".library", StringComparison.OrdinalIgnoreCase) != -1);
|
||||
this.ChildContainers.AddRange(libraryFiles.Select(f => LibraryJsonFile.ContainerFromLocalFile(f)));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
}
|
||||
|
||||
var matchedFiles = nonZipFiles.Where(filePath =>
|
||||
{
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
|
||||
return (filter == "" || FileNameContainsFilter(filePath, filter))
|
||||
&& ApplicationController.Instance.Library.IsContentFileType(fileName);
|
||||
});
|
||||
|
||||
this.ChildContainers.Modify((list) =>
|
||||
{
|
||||
list.Sort((a, b) => a.Name.CompareTo(b.Name));
|
||||
});
|
||||
|
||||
// Matched files projected onto FileSystemFileItem
|
||||
this.Items = new SafeList<ILibraryItem>(matchedFiles.OrderBy(f => f).Select(f => new FileSystemFileItem(f)));
|
||||
|
||||
var indexMd = nonZipFiles.Where(f => f.EndsWith("index.md")).FirstOrDefault();
|
||||
if (indexMd != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
HeaderMarkdown = File.ReadAllText(indexMd);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
this.isDirty = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ChildContainers = new SafeList<ILibraryContainerLink>();
|
||||
this.Items = new SafeList<ILibraryItem>()
|
||||
{
|
||||
new MessageItem("Error loading container - " + ex.Message)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove(IEnumerable<ILibraryItem> items)
|
||||
{
|
||||
if (AggContext.OperatingSystem == OSType.Windows)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is FileSystemItem fileItem
|
||||
&& File.Exists(fileItem.FilePath))
|
||||
{
|
||||
File.Delete(fileItem.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
|
||||
private void DirectoryContentsChanged(object sender, EventArgs e)
|
||||
{
|
||||
// Flag for reload
|
||||
isDirty = true;
|
||||
|
||||
lastTimeContentsChanged = UiThread.CurrentTimerMs;
|
||||
|
||||
// Only refresh content if we're the active container
|
||||
if (isActiveContainer
|
||||
&& waitingForRefresh == null)
|
||||
{
|
||||
waitingForRefresh = UiThread.SetInterval(WaitToRefresh, .5);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FileNameContainsFilter(string filename, string filter)
|
||||
{
|
||||
string nameWithSpaces = Path.GetFileNameWithoutExtension(filename.Replace('_', ' '));
|
||||
|
||||
// Split the filter on word boundaries and determine if all terms in the given file name
|
||||
foreach (string word in filter.Split(' '))
|
||||
{
|
||||
if (nameWithSpaces.IndexOf(word, StringComparison.OrdinalIgnoreCase) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetNonCollidingName(string fileName)
|
||||
{
|
||||
// Switching from .stl, .obj or similar to AMF. Save the file and update the
|
||||
// the filename with an incremented (n) value to reflect the extension change in the UI
|
||||
var similarFileNames = Directory.GetFiles(this.FullPath, $"{Path.GetFileNameWithoutExtension(fileName)}.*");
|
||||
|
||||
// ;
|
||||
var validName = Util.GetNonCollidingName(fileName, (testName) => !File.Exists(testName));
|
||||
|
||||
return validName;
|
||||
}
|
||||
|
||||
private void WaitToRefresh()
|
||||
{
|
||||
if (UiThread.CurrentTimerMs > lastTimeContentsChanged + 500
|
||||
&& waitingForRefresh != null)
|
||||
{
|
||||
UiThread.ClearInterval(waitingForRefresh);
|
||||
|
||||
waitingForRefresh = null;
|
||||
this.ReloadContent();
|
||||
}
|
||||
}
|
||||
|
||||
public class DirectoryContainerLink : FileSystemItem, ILibraryContainerLink
|
||||
{
|
||||
public DirectoryContainerLink(string path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsReadOnly { get; set; } = false;
|
||||
|
||||
public bool UseIncrementedNameDuringTypeChange { get; set; }
|
||||
|
||||
public Task<ILibraryContainer> GetContainer(Action<double, string> reportProgress)
|
||||
{
|
||||
return Task.FromResult<ILibraryContainer>(
|
||||
new FileSystemContainer(this.FilePath)
|
||||
{
|
||||
UseIncrementedNameDuringTypeChange = this.UseIncrementedNameDuringTypeChange,
|
||||
Name = this.Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
public DateTime DateModified { get; }
|
||||
|
||||
public virtual string ID => agg_basics.GetLongHashCode(this.FilePath).ToString();
|
||||
public virtual string ID => Util.GetLongHashCode(this.FilePath).ToString();
|
||||
|
||||
public virtual bool IsProtected => false;
|
||||
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
public long FileSize { get; } = -1;
|
||||
|
||||
public string ID => agg_basics.GetLongHashCode(AssetPath).ToString();
|
||||
public string ID => Util.GetLongHashCode(AssetPath).ToString();
|
||||
|
||||
public bool IsProtected => true;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
public long FileSize { get; private set; }
|
||||
|
||||
public string ID => agg_basics.GetLongHashCode(Url).ToString();
|
||||
public string ID => Util.GetLongHashCode(Url).ToString();
|
||||
|
||||
public bool IsLocked { get; internal set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
public string Category { get; } = "";
|
||||
|
||||
public string ID => agg_basics.GetLongHashCode(AssetPath).ToString();
|
||||
public string ID => Util.GetLongHashCode(AssetPath).ToString();
|
||||
|
||||
public event EventHandler NameChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2022, John Lewin, Lars Brubaker
|
||||
Copyright (c) 2023, John Lewin, Lars Brubaker
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -48,34 +48,26 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
this.ChildContainers.Add(
|
||||
new DynamicContainerLink(
|
||||
"Calibration Parts".Localize(),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png")),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "calibration_library_icon.png")),
|
||||
() => new CalibrationPartsContainer())
|
||||
{
|
||||
IsReadOnly = true
|
||||
});
|
||||
|
||||
this.ChildContainers.Add(
|
||||
new DynamicContainerLink(
|
||||
"Scripting".Localize(),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png")),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "scripting_icon.png")),
|
||||
() => new ScriptingPartsContainer())
|
||||
{
|
||||
IsReadOnly = true
|
||||
});
|
||||
|
||||
this.ChildContainers.Add(
|
||||
new DynamicContainerLink(
|
||||
"Primitives".Localize(),
|
||||
"Primitives 3D".Localize(),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png")),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "primitives_library_icon.png")),
|
||||
() => new PrimitivesContainer())
|
||||
() => new Primitives3DContainer())
|
||||
{
|
||||
IsReadOnly = true
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
this.ChildContainers.Add(
|
||||
new DynamicContainerLink(
|
||||
"Primitives 2D".Localize(),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png")),
|
||||
StaticData.Instance.LoadIcon(Path.Combine("Library", "primitives_library_icon.png")),
|
||||
() => new Primitives2DContainer())
|
||||
{
|
||||
IsReadOnly = true
|
||||
});
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
int index = 0;
|
||||
|
||||
|
|
@ -114,10 +106,11 @@ namespace MatterHackers.MatterControl.Library
|
|||
return Task.FromResult<IObject3D>(path);
|
||||
})
|
||||
{ DateCreated = new System.DateTime(index++) },
|
||||
}
|
||||
},
|
||||
Name = "Experimental".Localize()
|
||||
})
|
||||
{
|
||||
IsReadOnly = true
|
||||
IsReadOnly = true,
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright (c) 2019, 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 MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.DesignTools;
|
||||
using MatterHackers.MatterControl.DesignTools.Primitives;
|
||||
|
||||
namespace MatterHackers.MatterControl.Library
|
||||
{
|
||||
public class Primitives2DContainer : LibraryContainer
|
||||
{
|
||||
public Primitives2DContainer()
|
||||
{
|
||||
Name = "Primitives 2D".Localize();
|
||||
DefaultSort = new LibrarySortBehavior()
|
||||
{
|
||||
SortKey = SortKey.ModifiedDate,
|
||||
Ascending = true,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var library = ApplicationController.Instance.Library;
|
||||
|
||||
long index = DateTime.Now.Ticks;
|
||||
var libraryItems = new List<GeneratorItem>()
|
||||
{
|
||||
new GeneratorItem(
|
||||
"Box".Localize(),
|
||||
async () => await BoxPathObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Triangle".Localize(),
|
||||
async () => await PyramidObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Trapezoid".Localize(),
|
||||
async () => await WedgeObject3D_2.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Text".Localize(),
|
||||
async () => await TextObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Oval".Localize(),
|
||||
async () => await CylinderObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Star".Localize(),
|
||||
async () => await ConeObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Ring".Localize(),
|
||||
async () => await RingObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Circle".Localize(),
|
||||
async () => await SphereObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
};
|
||||
|
||||
string title = "2D Shapes".Localize();
|
||||
|
||||
foreach (var item in libraryItems)
|
||||
{
|
||||
item.Category = title;
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,9 +40,9 @@ using MatterHackers.MatterControl.DesignTools.Operations;
|
|||
|
||||
namespace MatterHackers.MatterControl.Library
|
||||
{
|
||||
public class PrimitivesContainer : LibraryContainer
|
||||
public class Primitives3DContainer : LibraryContainer
|
||||
{
|
||||
public PrimitivesContainer()
|
||||
public Primitives3DContainer()
|
||||
{
|
||||
Name = "Primitives".Localize();
|
||||
DefaultSort = new LibrarySortBehavior()
|
||||
|
|
@ -81,7 +81,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Text".Localize(),
|
||||
async () => await TextObject3D.Create(true))
|
||||
async () => await TextObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Cylinder".Localize(),
|
||||
|
|
@ -188,67 +188,4 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
public class Primitives2DContainer : LibraryContainer
|
||||
{
|
||||
public Primitives2DContainer()
|
||||
{
|
||||
Name = "Primitives 2D".Localize();
|
||||
DefaultSort = new LibrarySortBehavior()
|
||||
{
|
||||
SortKey = SortKey.ModifiedDate,
|
||||
Ascending = true,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var library = ApplicationController.Instance.Library;
|
||||
|
||||
long index = DateTime.Now.Ticks;
|
||||
var libraryItems = new List<GeneratorItem>()
|
||||
{
|
||||
new GeneratorItem(
|
||||
"Box".Localize(),
|
||||
async () => await BoxPathObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Triangle".Localize(),
|
||||
async () => await PyramidObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Trapezoid".Localize(),
|
||||
async () => await WedgeObject3D_2.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Text".Localize(),
|
||||
async () => await TextObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Oval".Localize(),
|
||||
async () => await CylinderObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Star".Localize(),
|
||||
async () => await ConeObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Ring".Localize(),
|
||||
async () => await RingObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
new GeneratorItem(
|
||||
"Circle".Localize(),
|
||||
async () => await SphereObject3D.Create())
|
||||
{ DateCreated = new DateTime(index++) },
|
||||
};
|
||||
|
||||
string title = "2D Shapes".Localize();
|
||||
|
||||
foreach (var item in libraryItems)
|
||||
{
|
||||
item.Category = title;
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
public string Category { get; } = "";
|
||||
|
||||
public string ID => agg_basics.GetLongHashCode(AssetPath).ToString();
|
||||
public string ID => Util.GetLongHashCode(AssetPath).ToString();
|
||||
|
||||
public event EventHandler NameChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
/// </summary>
|
||||
public long FileSize { get; private set; }
|
||||
|
||||
public override string ID => agg_basics.GetLongHashCode($"{this.FilePath}/{this.RelativePath}").ToString();
|
||||
public override string ID => Util.GetLongHashCode($"{this.FilePath}/{this.RelativePath}").ToString();
|
||||
|
||||
public ZipMemoryContainer ContainingZip { get; }
|
||||
public string RelativePath { get; set; }
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace MatterHackers.MatterControl.Library
|
|||
|
||||
this.url = url;
|
||||
this.Name = name ?? "Unknown".Localize();
|
||||
this.ID = agg_basics.GetLongHashCode(url).ToString();
|
||||
this.ID = Util.GetLongHashCode(url).ToString();
|
||||
}
|
||||
|
||||
public string ID { get; set; }
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ namespace MatterHackers.MatterControl.Library.Widgets
|
|||
{
|
||||
string printerName = treeView.SelectedNode.Tag.ToString();
|
||||
|
||||
printerNameInput.Text = agg_basics.GetNonCollidingName(printerName, this.ExistingPrinterNames);
|
||||
printerNameInput.Text = Util.GetNonCollidingName(printerName, this.ExistingPrinterNames);
|
||||
|
||||
this.SelectedPrinter = treeView.SelectedNode.Tag as MakeModelInfo;
|
||||
|
||||
|
|
|
|||
|
|
@ -786,8 +786,6 @@ namespace MatterHackers.MatterControl.Library.Widgets
|
|||
null,
|
||||
async (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
|
||||
// Change loaded scene to new context
|
||||
await printer.Bed.LoadContent(
|
||||
new EditContext()
|
||||
|
|
@ -796,12 +794,7 @@ namespace MatterHackers.MatterControl.Library.Widgets
|
|||
// No content store for GCode
|
||||
ContentStore = null
|
||||
},
|
||||
(progress, message) =>
|
||||
{
|
||||
progressStatus.Progress0To1 = progress;
|
||||
progressStatus.Status = message;
|
||||
reporter.Report(progressStatus);
|
||||
}).ConfigureAwait(false);
|
||||
reporter).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -611,7 +611,6 @@ namespace MatterHackers.MatterControl.CustomWidgets
|
|||
null,
|
||||
async (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
var editContext = new EditContext()
|
||||
{
|
||||
ContentStore = writableContainer,
|
||||
|
|
@ -619,9 +618,7 @@ namespace MatterHackers.MatterControl.CustomWidgets
|
|||
};
|
||||
await workspace.SceneContext.LoadContent(editContext, (progress, message) =>
|
||||
{
|
||||
progressStatus.Progress0To1 = progress;
|
||||
progressStatus.Status = message;
|
||||
reporter.Report(progressStatus);
|
||||
reporter?.Invoke(progress, message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,17 +93,17 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.17.1" />
|
||||
<PackageReference Include="AngleSharp" Version="1.0.1" />
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00005" />
|
||||
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00005" />
|
||||
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00005" />
|
||||
<PackageReference Include="Markdig" Version="0.30.4" />
|
||||
<PackageReference Include="MathParser.org-mXparser" Version="5.1.0" />
|
||||
<PackageReference Include="Markdig" Version="0.31.0" />
|
||||
<PackageReference Include="MathParser.org-mXparser" Version="5.2.1" />
|
||||
<PackageReference Include="MIConvexHull" Version="1.1.19.1019" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="PDFsharpNetStandard2" Version="1.51.4845" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="SocketIOSharp" Version="2.0.3" />
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
if (this.GradientDistance > 0)
|
||||
{
|
||||
gradientBackground = agg_basics.TrasparentToColorGradientX(
|
||||
gradientBackground = Util.TrasparentToColorGradientX(
|
||||
(int)this.LocalBounds.Width + this.GradientDistance,
|
||||
(int)this.LocalBounds.Height,
|
||||
this.BackgroundColor,
|
||||
|
|
|
|||
|
|
@ -282,9 +282,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
taskDetails.ProgressChanged -= TaskDetails_ProgressChanged;
|
||||
}
|
||||
|
||||
private void TaskDetails_ProgressChanged(object sender, ProgressStatus e)
|
||||
{
|
||||
if (expandButton.Text != e.Status
|
||||
private void TaskDetails_ProgressChanged(object sender, (double Progress0To1, string Status) e)
|
||||
{
|
||||
if (expandButton.Text != e.Status
|
||||
&& !string.IsNullOrEmpty(e.Status)
|
||||
&& !expandButton.Text.Contains(e.Status, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -88,24 +88,29 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
taskDetails.ProgressChanged -= TaskDetails_ProgressChanged;
|
||||
}
|
||||
|
||||
private void TaskDetails_ProgressChanged(object sender, ProgressStatus e)
|
||||
{
|
||||
if (e.Target == "terminal"
|
||||
&& sender is RunningTaskDetails details
|
||||
&& details.Owner is PrinterConfig printer)
|
||||
private void TaskDetails_ProgressChanged(object sender, (double progress0To1, string status) e)
|
||||
{
|
||||
if (e.status.StartsWith("[[send to terminal]]"))
|
||||
{
|
||||
printer.Connection.TerminalLog.WriteLine(e.Status);
|
||||
return;
|
||||
// strip of the prefix
|
||||
e.status = e.status.Substring("[[send to terminal]]".Length);
|
||||
if (sender is RunningTaskDetails details
|
||||
&& details.Owner is PrinterConfig printer)
|
||||
{
|
||||
// only write it to the terminal
|
||||
printer.Connection.TerminalLog.WriteLine(e.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (textWidget.Text != e.Status
|
||||
&& !string.IsNullOrEmpty(e.Status)
|
||||
&& !textWidget.Text.Contains(e.Status, StringComparison.OrdinalIgnoreCase))
|
||||
if (textWidget.Text != e.status
|
||||
&& !string.IsNullOrEmpty(e.status)
|
||||
&& !textWidget.Text.Contains(e.status, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
textWidget.Text = e.Status.Contains(taskDetails.Title, StringComparison.OrdinalIgnoreCase) ? e.Status : $"{taskDetails.Title} - {e.Status}";
|
||||
textWidget.Text = e.status.Contains(taskDetails.Title, StringComparison.OrdinalIgnoreCase) ? e.status : $"{taskDetails.Title} - {e.status}";
|
||||
}
|
||||
|
||||
double ratio = ((int)(e.Progress0To1 * Width)) / this.Width;
|
||||
double ratio = ((int)(e.progress0To1 * Width)) / this.Width;
|
||||
if (progressBar.RatioComplete != ratio)
|
||||
{
|
||||
progressBar.RatioComplete = ratio;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -44,141 +44,140 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[ShowUpdateButton]
|
||||
public class CombineObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier, IBuildsOnThread
|
||||
{
|
||||
[ShowUpdateButton]
|
||||
public class CombineObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier, IBuildsOnThread
|
||||
{
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public CombineObject3D_2()
|
||||
{
|
||||
Name = "Combine";
|
||||
}
|
||||
{
|
||||
Name = "Combine";
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#else
|
||||
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
private IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#endif
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
|
||||
public static void CheckManifoldData(CombineObject3D_2 item, IObject3D result)
|
||||
{
|
||||
bool IsManifold(Mesh mesh)
|
||||
{
|
||||
var meshEdgeList = mesh.NewMeshEdges();
|
||||
public static void CheckManifoldData(CombineObject3D_2 item, IObject3D result)
|
||||
{
|
||||
bool IsManifold(Mesh mesh)
|
||||
{
|
||||
var meshEdgeList = mesh.NewMeshEdges();
|
||||
|
||||
foreach (var meshEdge in meshEdgeList)
|
||||
{
|
||||
if (meshEdge.Faces.Count() != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach (var meshEdge in meshEdgeList)
|
||||
{
|
||||
if (meshEdge.Faces.Count() != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsManifold(result.Mesh))
|
||||
{
|
||||
// create a new combine of a and b and add it to the root
|
||||
var combine = new CombineObject3D_2();
|
||||
if (!IsManifold(result.Mesh))
|
||||
{
|
||||
// create a new combine of a and b and add it to the root
|
||||
var combine = new CombineObject3D_2();
|
||||
|
||||
var participants = item.SourceContainer.VisibleMeshes().Where(m => m.WorldOutputType(item.SourceContainer) != PrintOutputTypes.Hole);
|
||||
// all participants are manifold
|
||||
foreach (var participant in participants)
|
||||
{
|
||||
combine.SourceContainer.Children.Add(new Object3D()
|
||||
{
|
||||
Mesh = participant.Mesh.Copy(new CancellationToken()),
|
||||
Matrix = participant.Matrix
|
||||
});
|
||||
}
|
||||
var participants = item.SourceContainer.VisibleMeshes().Where(m => m.WorldOutputType(item.SourceContainer) != PrintOutputTypes.Hole);
|
||||
// all participants are manifold
|
||||
foreach (var participant in participants)
|
||||
{
|
||||
combine.SourceContainer.Children.Add(new Object3D()
|
||||
{
|
||||
Mesh = participant.Mesh.Copy(new CancellationToken()),
|
||||
Matrix = participant.Matrix
|
||||
});
|
||||
}
|
||||
|
||||
var scene = result.Parents().Last();
|
||||
scene.Children.Add(combine);
|
||||
}
|
||||
}
|
||||
var scene = result.Parents().Last();
|
||||
scene.Children.Add(combine);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Combine".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource;
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
"Combine".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource;
|
||||
reporter?.Invoke(0, null);
|
||||
|
||||
try
|
||||
{
|
||||
Combine(cancellationTokenSource.Token, reporter);
|
||||
try
|
||||
{
|
||||
Combine(cancellationTokenSource.Token, reporter);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// the combine was canceled set our children to the source object children
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
Children.Modify((list) =>
|
||||
{
|
||||
foreach (var child in SourceContainer.Children)
|
||||
{
|
||||
list.Add(child);
|
||||
}
|
||||
});
|
||||
// the combine was canceled set our children to the source object children
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
Children.Modify((list) =>
|
||||
{
|
||||
foreach (var child in SourceContainer.Children)
|
||||
{
|
||||
list.Add(child);
|
||||
}
|
||||
});
|
||||
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (!NameOverriden)
|
||||
{
|
||||
Name = NameFromChildren();
|
||||
NameOverriden = false;
|
||||
}
|
||||
if (!NameOverriden)
|
||||
{
|
||||
Name = NameFromChildren();
|
||||
NameOverriden = false;
|
||||
}
|
||||
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public void Combine()
|
||||
{
|
||||
Combine(CancellationToken.None, null);
|
||||
}
|
||||
public void Combine()
|
||||
{
|
||||
Combine(CancellationToken.None, null);
|
||||
}
|
||||
|
||||
public override string NameFromChildren()
|
||||
{
|
||||
return CalculateName(SourceContainer.Children, " + ");
|
||||
}
|
||||
return CalculateName(SourceContainer.Children, " + ");
|
||||
}
|
||||
|
||||
private void Combine(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
private void Combine(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
|
|
@ -238,27 +237,27 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
resultsItem.CopyProperties(participants.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
|
||||
this.Children.Add(resultsItem);
|
||||
#if DEBUG
|
||||
//resultsItem.Mesh.MergeVertices(.01);
|
||||
//resultsItem.Mesh.CleanAndMerge();
|
||||
//CheckManifoldData(this, resultsItem);
|
||||
//resultsItem.Mesh.MergeVertices(.01);
|
||||
//resultsItem.Mesh.CleanAndMerge();
|
||||
//CheckManifoldData(this, resultsItem);
|
||||
#endif
|
||||
SourceContainer.Visible = false;
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if(threadSafe != null)
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -42,25 +42,25 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[ShowUpdateButton]
|
||||
public class IntersectionObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier
|
||||
{
|
||||
public IntersectionObject3D_2()
|
||||
{
|
||||
Name = "Intersection";
|
||||
}
|
||||
[ShowUpdateButton]
|
||||
public class IntersectionObject3D_2 : OperationSourceContainerObject3D, IPropertyGridModifier
|
||||
{
|
||||
public IntersectionObject3D_2()
|
||||
{
|
||||
Name = "Intersection";
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#else
|
||||
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
|
|
@ -68,90 +68,88 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#endif
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Intersection".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
|
||||
var progressStatus = new ProgressStatus();
|
||||
reporter.Report(progressStatus);
|
||||
return ApplicationController.Instance.Tasks.Execute(
|
||||
"Intersection".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
|
||||
|
||||
try
|
||||
{
|
||||
Intersect(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
Intersect(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (!NameOverriden)
|
||||
{
|
||||
Name = NameFromChildren();
|
||||
NameOverriden = false;
|
||||
}
|
||||
if (!NameOverriden)
|
||||
{
|
||||
Name = NameFromChildren();
|
||||
NameOverriden = false;
|
||||
}
|
||||
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public override string NameFromChildren()
|
||||
{
|
||||
return CalculateName(SourceContainer.Children, " & ");
|
||||
}
|
||||
public override string NameFromChildren()
|
||||
{
|
||||
return CalculateName(SourceContainer.Children, " & ");
|
||||
}
|
||||
|
||||
public void Intersect()
|
||||
{
|
||||
Intersect(CancellationToken.None, null);
|
||||
}
|
||||
public void Intersect()
|
||||
{
|
||||
Intersect(CancellationToken.None, null);
|
||||
}
|
||||
|
||||
private void Intersect(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
private void Intersect(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
|
||||
var participants = SourceContainer.VisibleMeshes();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
if (participants.Count() == 1)
|
||||
{
|
||||
var newMesh = new Object3D();
|
||||
newMesh.CopyProperties(participants.First(), Object3DPropertyFlags.All);
|
||||
newMesh.Mesh = participants.First().Mesh;
|
||||
this.Children.Add(newMesh);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
var participants = SourceContainer.VisibleMeshes();
|
||||
if (participants.Count() < 2)
|
||||
{
|
||||
if (participants.Count() == 1)
|
||||
{
|
||||
var newMesh = new Object3D();
|
||||
newMesh.CopyProperties(participants.First(), Object3DPropertyFlags.All);
|
||||
newMesh.Mesh = participants.First().Mesh;
|
||||
this.Children.Add(newMesh);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var items = participants.Select(i => (i.Mesh, i.WorldMatrix(SourceContainer)));
|
||||
var items = participants.Select(i => (i.Mesh, i.WorldMatrix(SourceContainer)));
|
||||
#if false
|
||||
var resultsMesh = BooleanProcessing.DoArray(items,
|
||||
CsgModes.Intersect,
|
||||
|
|
@ -161,69 +159,65 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
reporter,
|
||||
cancellationToken);
|
||||
#else
|
||||
var totalOperations = items.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
var totalOperations = items.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
var resultsMesh = items.First().Item1;
|
||||
var keepWorldMatrix = items.First().Item2;
|
||||
|
||||
var resultsMesh = items.First().Item1;
|
||||
var keepWorldMatrix = items.First().Item2;
|
||||
bool first = true;
|
||||
foreach (var next in items)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
foreach (var next in items)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
resultsMesh = BooleanProcessing.Do(resultsMesh,
|
||||
keepWorldMatrix,
|
||||
// other mesh
|
||||
next.Item1,
|
||||
next.Item2,
|
||||
// operation type
|
||||
CsgModes.Intersect,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
resultsMesh = BooleanProcessing.Do(resultsMesh,
|
||||
keepWorldMatrix,
|
||||
// other mesh
|
||||
next.Item1,
|
||||
next.Item2,
|
||||
// operation type
|
||||
CsgModes.Intersect,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
// after the first time we get a result the results mesh is in the right coordinate space
|
||||
keepWorldMatrix = Matrix4X4.Identity;
|
||||
|
||||
// after the first time we get a result the results mesh is in the right coordinate space
|
||||
keepWorldMatrix = Matrix4X4.Identity;
|
||||
|
||||
// report our progress
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter?.Report(progressStatus);
|
||||
}
|
||||
// report our progress
|
||||
ratioCompleted += amountPerOperation;
|
||||
reporter?.Invoke(ratioCompleted, null);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (resultsMesh != null)
|
||||
{
|
||||
var resultsItem = new Object3D()
|
||||
{
|
||||
Mesh = resultsMesh
|
||||
};
|
||||
resultsItem.CopyProperties(participants.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
|
||||
this.Children.Add(resultsItem);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
if (resultsMesh != null)
|
||||
{
|
||||
var resultsItem = new Object3D()
|
||||
{
|
||||
Mesh = resultsMesh
|
||||
};
|
||||
resultsItem.CopyProperties(participants.First(), Object3DPropertyFlags.All & (~Object3DPropertyFlags.Matrix));
|
||||
this.Children.Add(resultsItem);
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
}
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
// first replace spaces with '_'
|
||||
var name = editSelectedName.Text.Replace(' ', '_');
|
||||
// next make sure we don't have the exact name already
|
||||
name = agg_basics.GetNonCollidingName(name, (name) =>
|
||||
name = Util.GetNonCollidingName(name, (name) =>
|
||||
{
|
||||
return !existingNames.Contains(name.ToLower());
|
||||
}, false);
|
||||
|
|
|
|||
|
|
@ -48,179 +48,175 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[Obsolete("Use SubtractAndReplaceObject3D_2 instead", false)]
|
||||
[ShowUpdateButton(SuppressPropertyChangeUpdates = true)]
|
||||
public class SubtractAndReplaceObject3D : MeshWrapperObject3D, ISelectableChildContainer
|
||||
{
|
||||
public SubtractAndReplaceObject3D()
|
||||
{
|
||||
Name = "Subtract and Replace";
|
||||
}
|
||||
[Obsolete("Use SubtractAndReplaceObject3D_2 instead", false)]
|
||||
[ShowUpdateButton(SuppressPropertyChangeUpdates = true)]
|
||||
public class SubtractAndReplaceObject3D : MeshWrapperObject3D, ISelectableChildContainer
|
||||
{
|
||||
public SubtractAndReplaceObject3D()
|
||||
{
|
||||
Name = "Subtract and Replace";
|
||||
}
|
||||
|
||||
public SelectedChildren ItemsToSubtract { get; set; } = new SelectedChildren();
|
||||
public SelectedChildren ItemsToSubtract { get; set; } = new SelectedChildren();
|
||||
|
||||
public SelectedChildren SelectedChildren => ItemsToSubtract;
|
||||
public SelectedChildren SelectedChildren => ItemsToSubtract;
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
|
||||
public void SubtractAndReplace()
|
||||
{
|
||||
SubtractAndReplace(CancellationToken.None, null);
|
||||
}
|
||||
public void SubtractAndReplace()
|
||||
{
|
||||
SubtractAndReplace(CancellationToken.None, null);
|
||||
}
|
||||
|
||||
public void SubtractAndReplace(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
public void SubtractAndReplace(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
ResetMeshWrapperMeshes(Object3DPropertyFlags.All, cancellationToken);
|
||||
|
||||
var paintObjects = this.Children
|
||||
.Where((i) => ItemsToSubtract.Contains(i.ID))
|
||||
.SelectMany((h) => h.DescendantsAndSelf())
|
||||
.Where((c) => c.OwnerID == this.ID).ToList();
|
||||
var keepObjects = this.Children
|
||||
.Where((i) => !ItemsToSubtract.Contains(i.ID))
|
||||
.SelectMany((h) => h.DescendantsAndSelf())
|
||||
.Where((c) => c.OwnerID == this.ID).ToList();
|
||||
var paintObjects = this.Children
|
||||
.Where((i) => ItemsToSubtract.Contains(i.ID))
|
||||
.SelectMany((h) => h.DescendantsAndSelf())
|
||||
.Where((c) => c.OwnerID == this.ID).ToList();
|
||||
var keepObjects = this.Children
|
||||
.Where((i) => !ItemsToSubtract.Contains(i.ID))
|
||||
.SelectMany((h) => h.DescendantsAndSelf())
|
||||
.Where((c) => c.OwnerID == this.ID).ToList();
|
||||
|
||||
if (paintObjects.Any()
|
||||
&& keepObjects.Any())
|
||||
{
|
||||
var totalOperations = paintObjects.Count * keepObjects.Count;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
if (paintObjects.Any()
|
||||
&& keepObjects.Any())
|
||||
{
|
||||
var totalOperations = paintObjects.Count * keepObjects.Count;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
foreach (var paint in paintObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
|
||||
{
|
||||
var transformedPaint = paint.obj3D.Mesh.Copy(cancellationToken);
|
||||
transformedPaint.Transform(paint.matrix);
|
||||
var inverseRemove = paint.matrix.Inverted;
|
||||
Mesh paintMesh = null;
|
||||
foreach (var paint in paintObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
|
||||
{
|
||||
var transformedPaint = paint.obj3D.Mesh.Copy(cancellationToken);
|
||||
transformedPaint.Transform(paint.matrix);
|
||||
var inverseRemove = paint.matrix.Inverted;
|
||||
Mesh paintMesh = null;
|
||||
|
||||
var progressStatus = new ProgressStatus();
|
||||
foreach (var keep in keepObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
|
||||
{
|
||||
var transformedKeep = keep.obj3D.Mesh.Copy(cancellationToken);
|
||||
transformedKeep.Transform(keep.matrix);
|
||||
foreach (var keep in keepObjects.Select((r) => (obj3D: r, matrix: r.WorldMatrix())).ToList())
|
||||
{
|
||||
var transformedKeep = keep.obj3D.Mesh.Copy(cancellationToken);
|
||||
transformedKeep.Transform(keep.matrix);
|
||||
|
||||
// remove the paint from the original
|
||||
var subtract = BooleanProcessing.Do(keep.obj3D.Mesh,
|
||||
keep.matrix,
|
||||
paint.obj3D.Mesh,
|
||||
paint.matrix,
|
||||
CsgModes.Subtract,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
|
||||
var intersect = BooleanProcessing.Do(keep.obj3D.Mesh,
|
||||
keep.matrix,
|
||||
paint.obj3D.Mesh,
|
||||
paint.matrix,
|
||||
CsgModes.Intersect,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
// remove the paint from the original
|
||||
var subtract = BooleanProcessing.Do(keep.obj3D.Mesh,
|
||||
keep.matrix,
|
||||
paint.obj3D.Mesh,
|
||||
paint.matrix,
|
||||
CsgModes.Subtract,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
var inverseKeep = keep.matrix.Inverted;
|
||||
subtract.Transform(inverseKeep);
|
||||
using (keep.obj3D.RebuildLock())
|
||||
{
|
||||
keep.obj3D.Mesh = subtract;
|
||||
}
|
||||
var intersect = BooleanProcessing.Do(keep.obj3D.Mesh,
|
||||
keep.matrix,
|
||||
paint.obj3D.Mesh,
|
||||
paint.matrix,
|
||||
CsgModes.Intersect,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
// keep all the intersections together
|
||||
if (paintMesh == null)
|
||||
{
|
||||
paintMesh = intersect;
|
||||
}
|
||||
else // union into the current paint
|
||||
{
|
||||
paintMesh = BooleanProcessing.Do(paintMesh,
|
||||
Matrix4X4.Identity,
|
||||
intersect,
|
||||
Matrix4X4.Identity,
|
||||
CsgModes.Subtract,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
}
|
||||
var inverseKeep = keep.matrix.Inverted;
|
||||
subtract.Transform(inverseKeep);
|
||||
using (keep.obj3D.RebuildLock())
|
||||
{
|
||||
keep.obj3D.Mesh = subtract;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// keep all the intersections together
|
||||
if (paintMesh == null)
|
||||
{
|
||||
paintMesh = intersect;
|
||||
}
|
||||
else // union into the current paint
|
||||
{
|
||||
paintMesh = BooleanProcessing.Do(paintMesh,
|
||||
Matrix4X4.Identity,
|
||||
intersect,
|
||||
Matrix4X4.Identity,
|
||||
CsgModes.Subtract,
|
||||
ProcessingModes.Polygons,
|
||||
ProcessingResolution._64,
|
||||
ProcessingResolution._64,
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// move the paint mesh back to its original coordinates
|
||||
paintMesh.Transform(inverseRemove);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
using (paint.obj3D.RebuildLock())
|
||||
{
|
||||
paint.obj3D.Mesh = paintMesh;
|
||||
}
|
||||
// move the paint mesh back to its original coordinates
|
||||
paintMesh.Transform(inverseRemove);
|
||||
|
||||
paint.obj3D.Color = paint.obj3D.WorldColor().WithContrast(keepObjects.First().WorldColor(), 2).ToColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
using (paint.obj3D.RebuildLock())
|
||||
{
|
||||
paint.obj3D.Mesh = paintMesh;
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
paint.obj3D.Color = paint.obj3D.WorldColor().WithContrast(keepObjects.First().WorldColor(), 2).ToColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spin up a task to calculate the paint
|
||||
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(), null, (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SubtractAndReplace(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
// spin up a task to calculate the paint
|
||||
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(), null, (reporter, cancellationTokenSource) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SubtractAndReplace(cancellationTokenSource.Token, reporter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (c) 2017, Lars Brubaker, John Lewin
|
||||
Copyright (c) 2023, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
@ -45,36 +45,36 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
||||
{
|
||||
[ShowUpdateButton]
|
||||
public class SubtractAndReplaceObject3D_2 : OperationSourceContainerObject3D, ISelectableChildContainer, ICustomEditorDraw, IPropertyGridModifier
|
||||
{
|
||||
public SubtractAndReplaceObject3D_2()
|
||||
{
|
||||
Name = "Subtract and Replace";
|
||||
}
|
||||
[ShowUpdateButton]
|
||||
public class SubtractAndReplaceObject3D_2 : OperationSourceContainerObject3D, ISelectableChildContainer, ICustomEditorDraw, IPropertyGridModifier
|
||||
{
|
||||
public SubtractAndReplaceObject3D_2()
|
||||
{
|
||||
Name = "Subtract and Replace";
|
||||
}
|
||||
|
||||
public bool DoEditorDraw(bool isSelected)
|
||||
{
|
||||
return isSelected;
|
||||
}
|
||||
public bool DoEditorDraw(bool isSelected)
|
||||
{
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
[HideFromEditor]
|
||||
public SelectedChildren ComputedChildren { get; set; } = new SelectedChildren();
|
||||
[HideFromEditor]
|
||||
public SelectedChildren ComputedChildren { get; set; } = new SelectedChildren();
|
||||
|
||||
[DisplayName("Part(s) to Subtract and Replace")]
|
||||
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
|
||||
[DisplayName("Part(s) to Subtract and Replace")]
|
||||
public SelectedChildren SelectedChildren { get; set; } = new SelectedChildren();
|
||||
|
||||
#if DEBUG
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
public ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public IplicitSurfaceMethod MeshAnalysis { get; set; }
|
||||
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
[EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)]
|
||||
public ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#else
|
||||
private ProcessingModes Processing { get; set; } = ProcessingModes.Polygons;
|
||||
private ProcessingResolution OutputResolution { get; set; } = ProcessingResolution._64;
|
||||
|
|
@ -82,255 +82,247 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
private ProcessingResolution InputResolution { get; set; } = ProcessingResolution._64;
|
||||
#endif
|
||||
|
||||
public void AddEditorTransparents(Object3DControlsLayer layer, List<Object3DView> transparentMeshes, DrawEventArgs e)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem == this)
|
||||
{
|
||||
var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
|
||||
public void AddEditorTransparents(Object3DControlsLayer layer, List<Object3DView> transparentMeshes, DrawEventArgs e)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem == this)
|
||||
{
|
||||
var parentOfSubtractTargets = this.SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
|
||||
|
||||
var removeObjects = parentOfSubtractTargets.Children
|
||||
.Where(i => SelectedChildren.Contains(i.ID))
|
||||
.SelectMany(c => c.VisibleMeshes())
|
||||
.ToList();
|
||||
var removeObjects = parentOfSubtractTargets.Children
|
||||
.Where(i => SelectedChildren.Contains(i.ID))
|
||||
.SelectMany(c => c.VisibleMeshes())
|
||||
.ToList();
|
||||
|
||||
foreach (var item in removeObjects)
|
||||
{
|
||||
var color = item.WorldColor(checkOutputType: true);
|
||||
transparentMeshes.Add(new Object3DView(item, color.WithAlpha(color.Alpha0To1 * .2)));
|
||||
}
|
||||
foreach (var item in removeObjects)
|
||||
{
|
||||
var color = item.WorldColor(checkOutputType: true);
|
||||
transparentMeshes.Add(new Object3DView(item, color.WithAlpha(color.Alpha0To1 * .2)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix)
|
||||
|| invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh))
|
||||
&& invalidateType.Source != this
|
||||
&& !RebuildLocked)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties)
|
||||
&& invalidateType.Source == this)
|
||||
{
|
||||
await Rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInvalidate(invalidateType);
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
public void CancelBuild()
|
||||
{
|
||||
var threadSafe = this.cancellationToken;
|
||||
if (threadSafe != null)
|
||||
{
|
||||
threadSafe.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
public override Task Rebuild()
|
||||
{
|
||||
var rebuildLocks = this.RebuilLockAll();
|
||||
|
||||
// spin up a task to calculate the paint
|
||||
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
|
||||
try
|
||||
{
|
||||
SubtractAndReplace(cancellationTokenSource.Token, reporter);
|
||||
var newComputedChildren = new SelectedChildren();
|
||||
// spin up a task to calculate the paint
|
||||
return ApplicationController.Instance.Tasks.Execute("Replacing".Localize(),
|
||||
null,
|
||||
(reporter, cancellationTokenSource) =>
|
||||
{
|
||||
this.cancellationToken = cancellationTokenSource as CancellationTokenSource;
|
||||
try
|
||||
{
|
||||
SubtractAndReplace(cancellationTokenSource.Token, reporter);
|
||||
var newComputedChildren = new SelectedChildren();
|
||||
|
||||
foreach (var id in SelectedChildren)
|
||||
{
|
||||
newComputedChildren.Add(id);
|
||||
}
|
||||
foreach (var id in SelectedChildren)
|
||||
{
|
||||
newComputedChildren.Add(id);
|
||||
}
|
||||
|
||||
ComputedChildren = newComputedChildren;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
ComputedChildren = newComputedChildren;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
this.cancellationToken = null;
|
||||
UiThread.RunOnIdle(() =>
|
||||
{
|
||||
rebuildLocks.Dispose();
|
||||
this.CancelAllParentBuilding();
|
||||
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public void SubtractAndReplace()
|
||||
{
|
||||
SubtractAndReplace(CancellationToken.None, null);
|
||||
}
|
||||
public void SubtractAndReplace()
|
||||
{
|
||||
SubtractAndReplace(CancellationToken.None, null);
|
||||
}
|
||||
|
||||
private void SubtractAndReplace(CancellationToken cancellationToken, IProgress<ProgressStatus> reporter)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
private void SubtractAndReplace(CancellationToken cancellationToken, Action<double, string> reporter)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
|
||||
var parentOfPaintTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
|
||||
var parentOfPaintTargets = SourceContainer.FirstWithMultipleChildrenDescendantsAndSelf();
|
||||
|
||||
if (parentOfPaintTargets.Children.Count() < 2)
|
||||
{
|
||||
if (parentOfPaintTargets.Children.Count() == 1)
|
||||
{
|
||||
this.Children.Add(SourceContainer.Clone());
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
if (parentOfPaintTargets.Children.Count() < 2)
|
||||
{
|
||||
if (parentOfPaintTargets.Children.Count() == 1)
|
||||
{
|
||||
this.Children.Add(SourceContainer.Clone());
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SubtractObject3D_2.CleanUpSelectedChildrenIDs(this);
|
||||
SubtractObject3D_2.CleanUpSelectedChildrenIDs(this);
|
||||
|
||||
var paintObjects = parentOfPaintTargets.Children
|
||||
.Where((i) => SelectedChildren
|
||||
.Contains(i.ID))
|
||||
.SelectMany(c => c.VisibleMeshes())
|
||||
.ToList();
|
||||
var paintObjects = parentOfPaintTargets.Children
|
||||
.Where((i) => SelectedChildren
|
||||
.Contains(i.ID))
|
||||
.SelectMany(c => c.VisibleMeshes())
|
||||
.ToList();
|
||||
|
||||
var keepItems = parentOfPaintTargets.Children
|
||||
.Where((i) => !SelectedChildren
|
||||
.Contains(i.ID));
|
||||
var keepItems = parentOfPaintTargets.Children
|
||||
.Where((i) => !SelectedChildren
|
||||
.Contains(i.ID));
|
||||
|
||||
var keepVisibleItems = keepItems.SelectMany(c => c.VisibleMeshes()).ToList();
|
||||
var keepVisibleItems = keepItems.SelectMany(c => c.VisibleMeshes()).ToList();
|
||||
|
||||
if (paintObjects.Any()
|
||||
&& keepVisibleItems.Any())
|
||||
{
|
||||
var totalOperations = paintObjects.Count * keepVisibleItems.Count;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
if (paintObjects.Any()
|
||||
&& keepVisibleItems.Any())
|
||||
{
|
||||
var totalOperations = paintObjects.Count * keepVisibleItems.Count;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
var progressStatus = new ProgressStatus
|
||||
{
|
||||
Status = "Do CSG"
|
||||
};
|
||||
foreach (var keep in keepVisibleItems)
|
||||
{
|
||||
var keepResultsMesh = keep.Mesh;
|
||||
var keepWorldMatrix = keep.WorldMatrix(SourceContainer);
|
||||
|
||||
foreach (var keep in keepVisibleItems)
|
||||
{
|
||||
var keepResultsMesh = keep.Mesh;
|
||||
var keepWorldMatrix = keep.WorldMatrix(SourceContainer);
|
||||
foreach (var paint in paintObjects)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var paint in paintObjects)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
SourceContainer.Visible = true;
|
||||
RemoveAllButSource();
|
||||
return;
|
||||
}
|
||||
Mesh paintMesh = BooleanProcessing.Do(keepResultsMesh,
|
||||
keepWorldMatrix,
|
||||
// paint data
|
||||
paint.Mesh,
|
||||
paint.WorldMatrix(SourceContainer),
|
||||
// operation type
|
||||
CsgModes.Intersect,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting data
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
Mesh paintMesh = BooleanProcessing.Do(keepResultsMesh,
|
||||
keepWorldMatrix,
|
||||
// paint data
|
||||
paint.Mesh,
|
||||
paint.WorldMatrix(SourceContainer),
|
||||
// operation type
|
||||
CsgModes.Intersect,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting data
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
keepResultsMesh = BooleanProcessing.Do(keepResultsMesh,
|
||||
keepWorldMatrix,
|
||||
// point data
|
||||
paint.Mesh,
|
||||
paint.WorldMatrix(SourceContainer),
|
||||
// operation type
|
||||
CsgModes.Subtract,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting data
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
cancellationToken);
|
||||
|
||||
keepResultsMesh = BooleanProcessing.Do(keepResultsMesh,
|
||||
keepWorldMatrix,
|
||||
// point data
|
||||
paint.Mesh,
|
||||
paint.WorldMatrix(SourceContainer),
|
||||
// operation type
|
||||
CsgModes.Subtract,
|
||||
Processing,
|
||||
InputResolution,
|
||||
OutputResolution,
|
||||
// reporting data
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
// after the first time we get a result the results mesh is in the right coordinate space
|
||||
keepWorldMatrix = Matrix4X4.Identity;
|
||||
|
||||
// after the first time we get a result the results mesh is in the right coordinate space
|
||||
keepWorldMatrix = Matrix4X4.Identity;
|
||||
// store our intersection (paint) results mesh
|
||||
var paintResultsItem = new Object3D()
|
||||
{
|
||||
Mesh = paintMesh,
|
||||
Visible = false,
|
||||
OwnerID = paint.ID
|
||||
};
|
||||
// copy all the properties but the matrix
|
||||
paintResultsItem.CopyWorldProperties(paint, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
|
||||
// and add it to this
|
||||
this.Children.Add(paintResultsItem);
|
||||
|
||||
// store our intersection (paint) results mesh
|
||||
var paintResultsItem = new Object3D()
|
||||
{
|
||||
Mesh = paintMesh,
|
||||
Visible = false,
|
||||
OwnerID = paint.ID
|
||||
};
|
||||
// copy all the properties but the matrix
|
||||
paintResultsItem.CopyWorldProperties(paint, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
|
||||
// and add it to this
|
||||
this.Children.Add(paintResultsItem);
|
||||
// report our progress
|
||||
ratioCompleted += amountPerOperation;
|
||||
reporter?.Invoke(ratioCompleted, "Do CSG".Localize());
|
||||
}
|
||||
|
||||
// report our progress
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter?.Report(progressStatus);
|
||||
}
|
||||
// store our results mesh
|
||||
var keepResultsItem = new Object3D()
|
||||
{
|
||||
Mesh = keepResultsMesh,
|
||||
Visible = false,
|
||||
OwnerID = keep.ID
|
||||
};
|
||||
// copy all the properties but the matrix
|
||||
keepResultsItem.CopyWorldProperties(keep, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
|
||||
// and add it to this
|
||||
this.Children.Add(keepResultsItem);
|
||||
}
|
||||
|
||||
// store our results mesh
|
||||
var keepResultsItem = new Object3D()
|
||||
{
|
||||
Mesh = keepResultsMesh,
|
||||
Visible = false,
|
||||
OwnerID = keep.ID
|
||||
};
|
||||
// copy all the properties but the matrix
|
||||
keepResultsItem.CopyWorldProperties(keep, SourceContainer, Object3DPropertyFlags.All & (~(Object3DPropertyFlags.Matrix | Object3DPropertyFlags.Visible)));
|
||||
// and add it to this
|
||||
this.Children.Add(keepResultsItem);
|
||||
}
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Visible = true;
|
||||
}
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Visible = true;
|
||||
}
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
SourceContainer.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
}
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(OutputResolution), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(MeshAnalysis), () => Processing != ProcessingModes.Polygons);
|
||||
change.SetRowVisible(nameof(InputResolution), () => Processing != ProcessingModes.Polygons && MeshAnalysis == IplicitSurfaceMethod.Grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue