|
@@ -45,9 +45,9 @@ namespace TeamAAS_VP.Views.Home
|
|
|
private readonly IProductService _productService;
|
|
private readonly IProductService _productService;
|
|
|
private readonly Prism.Services.Dialogs.IDialogService _dialogService;
|
|
private readonly Prism.Services.Dialogs.IDialogService _dialogService;
|
|
|
|
|
|
|
|
- // ====== 异步保存控制 ======
|
|
|
|
|
private CancellationTokenSource _saveImageCts = new CancellationTokenSource();
|
|
private CancellationTokenSource _saveImageCts = new CancellationTokenSource();
|
|
|
private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(5, 5);
|
|
private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(5, 5);
|
|
|
|
|
+ private List<Models.DelaySaveImage> delaySaveImages = new List<Models.DelaySaveImage>();
|
|
|
|
|
|
|
|
public ShowVisionRender()
|
|
public ShowVisionRender()
|
|
|
{
|
|
{
|
|
@@ -62,22 +62,13 @@ namespace TeamAAS_VP.Views.Home
|
|
|
_dialogService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<Prism.Services.Dialogs.IDialogService>();
|
|
_dialogService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<Prism.Services.Dialogs.IDialogService>();
|
|
|
|
|
|
|
|
viewModel.UpdateLayout += ViewModel_UpdateLayout;
|
|
viewModel.UpdateLayout += ViewModel_UpdateLayout;
|
|
|
- // ThreadOption.BackgroundThread:Publish() 立即返回,图像处理不等待 UI 更新和文件 I/O
|
|
|
|
|
- _eventAggregator.GetEvent<RenderUpdateNotification>().Subscribe(UpdateRenderModuleSource, ThreadOption.BackgroundThread);
|
|
|
|
|
|
|
+ // ThreadOption.BackgroundThread:让订阅者在后台线程执行,Publish() 立即返回,
|
|
|
|
|
+ // 图像处理流程不再等待 UI 更新和保存完成才回复完成信号
|
|
|
|
|
+ _eventAggregator.GetEvent<RenderUpdateNotification>().Subscribe(
|
|
|
|
|
+ UpdateRenderModuleSource,
|
|
|
|
|
+ ThreadOption.BackgroundThread);
|
|
|
_eventAggregator.GetEvent<ProductChangedNotification>().Subscribe(ProductChanged);
|
|
_eventAggregator.GetEvent<ProductChangedNotification>().Subscribe(ProductChanged);
|
|
|
-
|
|
|
|
|
- // 控件卸载时取消所有保存任务,防止页面关闭后仍在写文件导致闪退
|
|
|
|
|
- this.Unloaded += ShowVisionRender_Unloaded;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e)
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- _saveImageCts.Cancel();
|
|
|
|
|
- _saveImageCts.Dispose();
|
|
|
|
|
- }
|
|
|
|
|
- catch { }
|
|
|
|
|
|
|
+ _eventAggregator.GetEvent<DelaySaveImageNotification>().Subscribe(UpdateDelaySaveImage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private bool GetView3DUse()
|
|
private bool GetView3DUse()
|
|
@@ -101,15 +92,12 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 更新 2D/3D 显示并触发异步保存。
|
|
|
|
|
- /// 运行在后台线程(ThreadOption.BackgroundThread),不阻塞图像处理主流程。
|
|
|
|
|
- /// </summary>
|
|
|
|
|
private void UpdateRenderModuleSource(ShowRender render)
|
|
private void UpdateRenderModuleSource(ShowRender render)
|
|
|
{
|
|
{
|
|
|
- if (render == null) return;
|
|
|
|
|
|
|
+ if (render == null)
|
|
|
|
|
+ return;
|
|
|
|
|
|
|
|
- // 【关键】在后台线程深拷贝 ICogImage,防止下一次 ToolBlock.Run() 覆盖像素数据
|
|
|
|
|
|
|
+ // 【关键】在后台线程深拷贝 ICogImage,防止下一次视觉流程运行覆盖像素数据
|
|
|
ICogImage imageCopy = null;
|
|
ICogImage imageCopy = null;
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
@@ -120,139 +108,84 @@ namespace TeamAAS_VP.Views.Home
|
|
|
LogHelper.WriteLogError("深拷贝 ICogImage 失败", ex);
|
|
LogHelper.WriteLogError("深拷贝 ICogImage 失败", ex);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var token = _saveImageCts.Token;
|
|
|
|
|
|
|
+ // ICogRecord 和 Graphic 通常每次运行新创建,引用不会被覆盖,无需深拷贝
|
|
|
|
|
+ var graphic = render.Graphic;
|
|
|
|
|
+ var record = render.Record;
|
|
|
|
|
+ var rangeImage = render.RangeImage;
|
|
|
|
|
+ var greyImage = render.GreyImage;
|
|
|
|
|
|
|
|
- // 派发 UI 更新到 WPF UI 线程(fire-and-forget)
|
|
|
|
|
|
|
+ // 派发 UI 更新到 WPF UI 线程(fire-and-forget,不等待完成)
|
|
|
this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
|
|
this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
|
|
|
{
|
|
{
|
|
|
- try
|
|
|
|
|
|
|
+ if (render.Is3DModel == true && GetView3DUse() == true)
|
|
|
{
|
|
{
|
|
|
- if (token.IsCancellationRequested) return;
|
|
|
|
|
-
|
|
|
|
|
- // ==== 3D 显示 ====
|
|
|
|
|
- bool is3D = render.Is3DModel && GetView3DUse();
|
|
|
|
|
- if (is3D)
|
|
|
|
|
- {
|
|
|
|
|
- if (vmRenders.TryGetValue(Get3DKey(render.Id), out var host3D))
|
|
|
|
|
- {
|
|
|
|
|
- if (host3D.Child is Cognex.VisionPro3D.Cog3DDisplayV2WF display3D)
|
|
|
|
|
- {
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- display3D.Clear();
|
|
|
|
|
- if (render.RangeImage != null && render.GreyImage != null)
|
|
|
|
|
- {
|
|
|
|
|
- var rImgG = new Cog3DRangeImageGraphic(render.RangeImage, render.GreyImage);
|
|
|
|
|
- display3D.Add(rImgG, "__default");
|
|
|
|
|
- display3D.FitView();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ==== 2D 显示 ====
|
|
|
|
|
- if (!vmRenders.TryGetValue(Get2DKey(render.Id), out var host2D))
|
|
|
|
|
|
|
+ if (!vmRenders.TryGetValue(Get3DKey(render.Id), out var host3D))
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
- if (!(host2D.Child is CogRecordDisplay recordDisplay))
|
|
|
|
|
|
|
+ var display3D = host3D.Child as Cognex.VisionPro3D.Cog3DDisplayV2WF;
|
|
|
|
|
+ if (display3D == null)
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
- recordDisplay.Record = null;
|
|
|
|
|
- recordDisplay.Image = imageCopy; // 深拷贝后的图像
|
|
|
|
|
-
|
|
|
|
|
- recordDisplay.StaticGraphics.Clear();
|
|
|
|
|
- if (render.Graphic != null)
|
|
|
|
|
- recordDisplay.StaticGraphics.AddList(render.Graphic, "");
|
|
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ display3D.Clear();
|
|
|
|
|
|
|
|
- if (render.Record != null)
|
|
|
|
|
- recordDisplay.Record = render.Record;
|
|
|
|
|
|
|
+ if (rangeImage == null || greyImage == null)
|
|
|
|
|
+ return;
|
|
|
|
|
|
|
|
- // ==== 异步保存(fire-and-forget,不阻塞 UI 线程) ====
|
|
|
|
|
- if (render.IsSaveImage)
|
|
|
|
|
|
|
+ var rImgG = new Cog3DRangeImageGraphic(rangeImage, greyImage);
|
|
|
|
|
+ display3D.Add(rImgG, "__default");
|
|
|
|
|
+ display3D.FitView();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
|
|
|
{
|
|
{
|
|
|
- RequestSaveImage(recordDisplay, imageCopy, render, token);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { }
|
|
|
|
|
- }));
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 在 WinForms 控件线程上截图,然后后台异步保存。
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- private void RequestSaveImage(
|
|
|
|
|
- CogRecordDisplay recordDisplay,
|
|
|
|
|
- ICogImage imageCopy,
|
|
|
|
|
- ShowRender render,
|
|
|
|
|
- CancellationToken token)
|
|
|
|
|
- {
|
|
|
|
|
- recordDisplay.BeginInvoke(new Action(() =>
|
|
|
|
|
- {
|
|
|
|
|
- Bitmap clonedBitmap = null;
|
|
|
|
|
|
|
+ if (!vmRenders.TryGetValue(Get2DKey(render.Id), out var host2D))
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ var recordDisplay = host2D.Child as Cognex.VisionPro.CogRecordDisplay;
|
|
|
|
|
+ if (recordDisplay == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
- if (token.IsCancellationRequested) return;
|
|
|
|
|
- if (recordDisplay.IsDisposed || !recordDisplay.IsHandleCreated) return;
|
|
|
|
|
|
|
+ recordDisplay.Record = null;
|
|
|
|
|
+ recordDisplay.Image = imageCopy; // ✅ 深拷贝,安全
|
|
|
|
|
|
|
|
- Bitmap uiBitmap = (Bitmap)recordDisplay.CreateContentBitmap(
|
|
|
|
|
- Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
|
|
|
|
|
- clonedBitmap = (Bitmap)uiBitmap.Clone();
|
|
|
|
|
- uiBitmap.Dispose();
|
|
|
|
|
|
|
+ recordDisplay.StaticGraphics.Clear();
|
|
|
|
|
+ if (graphic != null)
|
|
|
|
|
+ recordDisplay.StaticGraphics.AddList(graphic, "");
|
|
|
|
|
|
|
|
- var capturedBitmap = clonedBitmap;
|
|
|
|
|
- var capturedRender = render;
|
|
|
|
|
- var capturedImage = imageCopy;
|
|
|
|
|
|
|
+ if (record != null)
|
|
|
|
|
+ recordDisplay.Record = record;
|
|
|
|
|
|
|
|
- Task.Run(async () =>
|
|
|
|
|
|
|
+ if (render.IsSaveImage)
|
|
|
{
|
|
{
|
|
|
- await _saveSemaphore.WaitAsync(token);
|
|
|
|
|
- try
|
|
|
|
|
|
|
+ // 传递深拷贝后的图像引用给保存逻辑
|
|
|
|
|
+ var renderForSave = new ShowRender
|
|
|
{
|
|
{
|
|
|
- if (token.IsCancellationRequested) return;
|
|
|
|
|
-
|
|
|
|
|
- if (string.IsNullOrEmpty(_productService.CurrentProductCode))
|
|
|
|
|
- {
|
|
|
|
|
- _eventAggregator.GetEvent<TaskMessageNotification>()
|
|
|
|
|
- .Publish(new Models.MessageStruct()
|
|
|
|
|
- {
|
|
|
|
|
- Message = "当前产品SN不存在,无法保存图片!",
|
|
|
|
|
- level = MessageLevel.Alarm
|
|
|
|
|
- });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- LogHelper.WriteLogInfo("开始异步保存图像");
|
|
|
|
|
- await SaveImageAsync(
|
|
|
|
|
- capturedRender.SaveImageModel,
|
|
|
|
|
- capturedRender.SaveImagePathModel,
|
|
|
|
|
- capturedRender.SavePath,
|
|
|
|
|
- capturedImage,
|
|
|
|
|
- capturedBitmap,
|
|
|
|
|
- capturedRender.Result,
|
|
|
|
|
- capturedRender.IsCompress,
|
|
|
|
|
- capturedRender.ImageFileName,
|
|
|
|
|
- capturedRender.Is3DModel,
|
|
|
|
|
- token
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- catch (OperationCanceledException) { }
|
|
|
|
|
- catch (Exception ex)
|
|
|
|
|
- {
|
|
|
|
|
- LogHelper.WriteLogError("后台保存图像任务出错!", ex);
|
|
|
|
|
- }
|
|
|
|
|
- finally
|
|
|
|
|
- {
|
|
|
|
|
- try { capturedBitmap?.Dispose(); } catch { }
|
|
|
|
|
- _saveSemaphore.Release();
|
|
|
|
|
- }
|
|
|
|
|
- }, token);
|
|
|
|
|
|
|
+ Id = render.Id,
|
|
|
|
|
+ Image = imageCopy,
|
|
|
|
|
+ Record = record,
|
|
|
|
|
+ Graphic = graphic,
|
|
|
|
|
+ Result = render.Result,
|
|
|
|
|
+ IsSaveImage = render.IsSaveImage,
|
|
|
|
|
+ SaveImageModel = render.SaveImageModel,
|
|
|
|
|
+ SaveImagePathModel = render.SaveImagePathModel,
|
|
|
|
|
+ SavePath = render.SavePath,
|
|
|
|
|
+ IsCompress = render.IsCompress,
|
|
|
|
|
+ IsDelaySaveImage = render.IsDelaySaveImage,
|
|
|
|
|
+ ImageFileName = render.ImageFileName,
|
|
|
|
|
+ ProceductName = render.ProceductName,
|
|
|
|
|
+ Is3DModel = render.Is3DModel,
|
|
|
|
|
+ };
|
|
|
|
|
+ RequestSaveImage(renderForSave, recordDisplay);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
|
|
|
|
+ catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
|
|
|
{
|
|
{
|
|
|
- LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex);
|
|
|
|
|
- try { clonedBitmap?.Dispose(); } catch { }
|
|
|
|
|
}
|
|
}
|
|
|
}));
|
|
}));
|
|
|
}
|
|
}
|
|
@@ -370,15 +303,6 @@ namespace TeamAAS_VP.Views.Home
|
|
|
Grid grid20 = new Grid();
|
|
Grid grid20 = new Grid();
|
|
|
private void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection<Models.ShowRender> obj)
|
|
private void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection<Models.ShowRender> obj)
|
|
|
{
|
|
{
|
|
|
- // 布局刷新前取消旧保存任务
|
|
|
|
|
- try
|
|
|
|
|
- {
|
|
|
|
|
- _saveImageCts.Cancel();
|
|
|
|
|
- _saveImageCts.Dispose();
|
|
|
|
|
- }
|
|
|
|
|
- catch { }
|
|
|
|
|
- _saveImageCts = new CancellationTokenSource();
|
|
|
|
|
-
|
|
|
|
|
System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily;
|
|
System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily;
|
|
|
|
|
|
|
|
if (vmRenders.Count > 0)
|
|
if (vmRenders.Count > 0)
|
|
@@ -717,22 +641,135 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// 保存图像(保持你原有逻辑不变,补充 Dispose 兜底,防止压缩分支不释放导致闪退)
|
|
|
|
|
|
|
+ /// 请求保存图像(在 WinForms 消息循环线程截图,然后派发到后台线程保存)
|
|
|
|
|
+ /// 使用 SemaphoreSlim 控制并发,支持 CancellationToken 取消
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
|
|
+ private void RequestSaveImage(ShowRender render, Cognex.VisionPro.CogRecordDisplay disp)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (render == null) return;
|
|
|
|
|
+ if (disp == null) return;
|
|
|
|
|
+ if (!render.IsSaveImage) return;
|
|
|
|
|
+
|
|
|
|
|
+ bool needRecordedBitmap =
|
|
|
|
|
+ render.SaveImageModel == ProcedureSaveImageModel.Recorded ||
|
|
|
|
|
+ render.SaveImageModel == ProcedureSaveImageModel.OriginalAndRecorded;
|
|
|
|
|
+
|
|
|
|
|
+ var token = _saveImageCts.Token;
|
|
|
|
|
+
|
|
|
|
|
+ disp.BeginInvoke(new Action(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Bitmap clonedBitmap = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ if (token.IsCancellationRequested) return;
|
|
|
|
|
+ if (needRecordedBitmap)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (disp.IsDisposed || !disp.IsHandleCreated) return;
|
|
|
|
|
+ Bitmap uiBitmap = (Bitmap)disp.CreateContentBitmap(
|
|
|
|
|
+ Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
|
|
|
|
|
+ clonedBitmap = (Bitmap)uiBitmap.Clone();
|
|
|
|
|
+ uiBitmap.Dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var capturedBitmap = clonedBitmap;
|
|
|
|
|
+ var capturedRender = render;
|
|
|
|
|
+
|
|
|
|
|
+ Task.Run(async () =>
|
|
|
|
|
+ {
|
|
|
|
|
+ await _saveSemaphore.WaitAsync(token);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ if (token.IsCancellationRequested) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (capturedRender.IsDelaySaveImage)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 延迟保存:存入列表,等 DelaySaveImageNotification 时统一写入
|
|
|
|
|
+ ICogImage imageDeepCopy = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ imageDeepCopy = capturedRender.Image?
|
|
|
|
|
+ .CopyBase(CogImageCopyModeConstants.CopyPixels);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ LogHelper.WriteLogError("延迟保存深拷贝 ICogImage 失败", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var delayItem = new Models.DelaySaveImage()
|
|
|
|
|
+ {
|
|
|
|
|
+ SaveImageModel = capturedRender.SaveImageModel,
|
|
|
|
|
+ SaveImagePathModel = capturedRender.SaveImagePathModel,
|
|
|
|
|
+ SavePath = capturedRender.SavePath,
|
|
|
|
|
+ Image = imageDeepCopy,
|
|
|
|
|
+ clonedBitmap = capturedBitmap != null
|
|
|
|
|
+ ? (Bitmap)capturedBitmap.Clone() : null,
|
|
|
|
|
+ Result = capturedRender.Result,
|
|
|
|
|
+ IsCompress = capturedRender.IsCompress,
|
|
|
|
|
+ ImageFileName = capturedRender.ImageFileName,
|
|
|
|
|
+ Is3DModel = capturedRender.Is3DModel,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ lock (delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ delaySaveImages.Add(delayItem);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(_productService.CurrentProductCode))
|
|
|
|
|
+ {
|
|
|
|
|
+ _eventAggregator.GetEvent<TaskMessageNotification>()
|
|
|
|
|
+ .Publish(new MessageStruct()
|
|
|
|
|
+ {
|
|
|
|
|
+ Message = "当前产品SN不存在,无法保存图片!",
|
|
|
|
|
+ level = MessageLevel.Alarm
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ LogHelper.WriteLogInfo("开始异步保存图像");
|
|
|
|
|
+ await SaveImageAsync(
|
|
|
|
|
+ capturedRender.SaveImageModel,
|
|
|
|
|
+ capturedRender.SaveImagePathModel,
|
|
|
|
|
+ capturedRender.SavePath,
|
|
|
|
|
+ capturedRender.Image,
|
|
|
|
|
+ capturedBitmap,
|
|
|
|
|
+ capturedRender.Result,
|
|
|
|
|
+ capturedRender.IsCompress,
|
|
|
|
|
+ capturedRender.ImageFileName,
|
|
|
|
|
+ capturedRender.Is3DModel,
|
|
|
|
|
+ token
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (OperationCanceledException) { }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ LogHelper.WriteLogError("后台保存图像任务出错!", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ try { capturedBitmap?.Dispose(); } catch { }
|
|
|
|
|
+ _saveSemaphore.Release();
|
|
|
|
|
+ }
|
|
|
|
|
+ }, token);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex);
|
|
|
|
|
+ try { clonedBitmap?.Dispose(); } catch { }
|
|
|
|
|
+ }
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// 异步保存图像(调用者已在后台线程,不再嵌套 Task.Run)
|
|
|
|
|
|
|
+ /// 异步保存图像(调用者已在后台线程,不再嵌套 Task.Run;通过 CancellationToken 支持取消)
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
private async Task SaveImageAsync(
|
|
private async Task SaveImageAsync(
|
|
|
ProcedureSaveImageModel saveImageModel,
|
|
ProcedureSaveImageModel saveImageModel,
|
|
|
ProcedureSaveImagePathModel saveImagePathModel,
|
|
ProcedureSaveImagePathModel saveImagePathModel,
|
|
|
- string path,
|
|
|
|
|
- ICogImage image,
|
|
|
|
|
- Bitmap reimage,
|
|
|
|
|
- bool result,
|
|
|
|
|
- bool isCompress,
|
|
|
|
|
- string imageFileName,
|
|
|
|
|
- bool is3Dmodel,
|
|
|
|
|
- CancellationToken cancellationToken)
|
|
|
|
|
|
|
+ string path, ICogImage image, Bitmap reimage,
|
|
|
|
|
+ bool result, bool isCompress, string imageFileName,
|
|
|
|
|
+ bool is3Dmodel, CancellationToken cancellationToken)
|
|
|
{
|
|
{
|
|
|
if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path))
|
|
if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path))
|
|
|
{
|
|
{
|
|
@@ -746,6 +783,7 @@ namespace TeamAAS_VP.Views.Home
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
DateTime now = DateTime.Now;
|
|
DateTime now = DateTime.Now;
|
|
|
|
|
+ //图片名
|
|
|
string imgName = now.ToString("yyyy-MM-dd HH_mm_ss");
|
|
string imgName = now.ToString("yyyy-MM-dd HH_mm_ss");
|
|
|
if (!string.IsNullOrEmpty(imageFileName))
|
|
if (!string.IsNullOrEmpty(imageFileName))
|
|
|
{
|
|
{
|
|
@@ -778,10 +816,12 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
|
|
else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
|
|
|
{
|
|
{
|
|
|
|
|
+ // 当前产品配方名(过滤非法字符)
|
|
|
string formulaName = _productService.GetCurrentProduct().Name;
|
|
string formulaName = _productService.GetCurrentProduct().Name;
|
|
|
foreach (char c in Path.GetInvalidFileNameChars())
|
|
foreach (char c in Path.GetInvalidFileNameChars())
|
|
|
formulaName = formulaName.Replace(c, '_');
|
|
formulaName = formulaName.Replace(c, '_');
|
|
|
|
|
|
|
|
|
|
+ // 产品SN(过滤非法字符)
|
|
|
string productSN = _productService.CurrentProductCode;
|
|
string productSN = _productService.CurrentProductCode;
|
|
|
foreach (char c in Path.GetInvalidFileNameChars())
|
|
foreach (char c in Path.GetInvalidFileNameChars())
|
|
|
productSN = productSN.Replace(c, '_');
|
|
productSN = productSN.Replace(c, '_');
|
|
@@ -856,26 +896,27 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded)
|
|
else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded)
|
|
|
{
|
|
{
|
|
|
- string Originalpath = is3Dmodel
|
|
|
|
|
- ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.idb"
|
|
|
|
|
- : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
|
|
|
|
|
-
|
|
|
|
|
|
|
+ string Originalpath = "";
|
|
|
|
|
+ if (is3Dmodel == true)
|
|
|
|
|
+ {
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.idb";
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
|
|
|
|
|
+ }
|
|
|
string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
|
|
string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
|
|
|
|
|
|
|
|
if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
|
|
if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
|
|
|
{
|
|
{
|
|
|
if (result)
|
|
if (result)
|
|
|
{
|
|
{
|
|
|
- Originalpath = is3Dmodel
|
|
|
|
|
- ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.idb"
|
|
|
|
|
- : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- Originalpath = is3Dmodel
|
|
|
|
|
- ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.idb"
|
|
|
|
|
- : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -883,9 +924,7 @@ namespace TeamAAS_VP.Views.Home
|
|
|
{
|
|
{
|
|
|
if (result)
|
|
if (result)
|
|
|
{
|
|
{
|
|
|
- Originalpath = is3Dmodel
|
|
|
|
|
- ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.idb"
|
|
|
|
|
- : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
@@ -897,9 +936,7 @@ namespace TeamAAS_VP.Views.Home
|
|
|
{
|
|
{
|
|
|
if (!result)
|
|
if (!result)
|
|
|
{
|
|
{
|
|
|
- Originalpath = is3Dmodel
|
|
|
|
|
- ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.idb"
|
|
|
|
|
- : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
@@ -921,8 +958,14 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
string resultStr = result ? "OK" : "NG";
|
|
string resultStr = result ? "OK" : "NG";
|
|
|
- string ext = is3Dmodel ? ".idb" : ".bmp";
|
|
|
|
|
- Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}{ext}";
|
|
|
|
|
|
|
+ if (is3Dmodel == true)
|
|
|
|
|
+ {
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.idb";
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
|
|
|
|
|
+ }
|
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
|
|
Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -953,10 +996,7 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- catch (OperationCanceledException)
|
|
|
|
|
- {
|
|
|
|
|
- // 正常取消,不记录日志
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ catch (OperationCanceledException) { }
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
LogHelper.WriteLogError("保存流程运行结果的图片时出错!", ex);
|
|
LogHelper.WriteLogError("保存流程运行结果的图片时出错!", ex);
|
|
@@ -967,6 +1007,93 @@ namespace TeamAAS_VP.Views.Home
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 延迟保存图像处理:isSave=true 时批量保存所有待保存项;false 时丢弃并清理
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private async void UpdateDelaySaveImage(bool isSave)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!isSave)
|
|
|
|
|
+ {
|
|
|
|
|
+ lock (delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var item in delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ try { item.clonedBitmap?.Dispose(); } catch { }
|
|
|
|
|
+ try { item.Image = null; } catch { }
|
|
|
|
|
+ }
|
|
|
|
|
+ delaySaveImages.Clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<Models.DelaySaveImage> itemsToSave;
|
|
|
|
|
+ lock (delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ itemsToSave = new List<Models.DelaySaveImage>(delaySaveImages);
|
|
|
|
|
+ delaySaveImages.Clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var token = _saveImageCts.Token;
|
|
|
|
|
+ foreach (var item in itemsToSave)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ if (token.IsCancellationRequested) break;
|
|
|
|
|
+
|
|
|
|
|
+ if (string.IsNullOrEmpty(_productService.CurrentProductCode))
|
|
|
|
|
+ {
|
|
|
|
|
+ _eventAggregator.GetEvent<TaskMessageNotification>()
|
|
|
|
|
+ .Publish(new MessageStruct()
|
|
|
|
|
+ {
|
|
|
|
|
+ Message = "当前产品SN不存在,无法保存图片!",
|
|
|
|
|
+ level = MessageLevel.Alarm
|
|
|
|
|
+ });
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LogHelper.WriteLogInfo("开始异步保存延迟图像");
|
|
|
|
|
+ await SaveImageAsync(
|
|
|
|
|
+ item.SaveImageModel,
|
|
|
|
|
+ item.SaveImagePathModel,
|
|
|
|
|
+ item.SavePath,
|
|
|
|
|
+ item.Image,
|
|
|
|
|
+ item.clonedBitmap,
|
|
|
|
|
+ item.Result,
|
|
|
|
|
+ item.IsCompress,
|
|
|
|
|
+ item.ImageFileName,
|
|
|
|
|
+ item.Is3DModel,
|
|
|
|
|
+ token
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (OperationCanceledException) { break; }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ LogHelper.WriteLogError("延迟保存图像任务出错!", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ try { item.clonedBitmap?.Dispose(); } catch { }
|
|
|
|
|
+ try { item.Image = null; } catch { }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ try { _saveImageCts.Cancel(); _saveImageCts.Dispose(); } catch { }
|
|
|
|
|
+
|
|
|
|
|
+ // 释放延迟保存列表中的 Bitmap 资源
|
|
|
|
|
+ lock (delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var item in delaySaveImages)
|
|
|
|
|
+ {
|
|
|
|
|
+ try { item.clonedBitmap?.Dispose(); } catch { }
|
|
|
|
|
+ try { item.Image = null; } catch { }
|
|
|
|
|
+ }
|
|
|
|
|
+ delaySaveImages.Clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void ProductChanged(ProductModel product)
|
|
private void ProductChanged(ProductModel product)
|
|
|
{
|
|
{
|
|
|
this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
|
|
this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
|