integrating offline changes

This commit is contained in:
Lars Brubaker 2023-03-10 17:15:55 -08:00
parent 121623bad3
commit 3f8eeda65b
125 changed files with 5442 additions and 5434 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -120,7 +120,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
Rebuild();
}

View file

@ -160,7 +160,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
{
Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
Rebuild();
}

View file

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

View file

@ -69,7 +69,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -194,7 +194,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

@ -156,7 +156,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -201,7 +201,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -88,7 +88,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -86,7 +86,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -74,7 +74,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

@ -75,7 +75,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

@ -121,7 +121,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

@ -106,7 +106,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

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

View file

@ -97,7 +97,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -85,7 +85,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

@ -88,7 +88,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
await Rebuild();
}
else if (SheetObject3D.NeedsRebuild(this, invalidateArgs))
else if (Expressions.NeedRebuild(this, invalidateArgs))
{
await Rebuild();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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