recipe name filter
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using Serilog;
|
||||
using VisionBuilder.UI.Common.Plugins;
|
||||
|
||||
namespace VisionBuilder.UI.Common;
|
||||
|
||||
@@ -23,6 +24,8 @@ public static class Extensions
|
||||
self.Bind<T, IVisionBuilderModule>().To<T>().InSingletonScope();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void InitializeModules(this IKernel self)
|
||||
{
|
||||
|
||||
@@ -45,6 +48,42 @@ public static class Extensions
|
||||
}
|
||||
}
|
||||
|
||||
public static IKernel LoadGlobalPlugins(this IKernel self)
|
||||
{
|
||||
var plugins = PluginLoader.Instance.Plugins;
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information($"Initializing global plugin {plugin.GetType().Name}");
|
||||
plugin.RegisterGlobalModules(self);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error initializing global plugin {plugin.GetType().Name}: {e}", e);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
public static IKernel LoadCameraPlugins(this IChildKernel self, string cameraName)
|
||||
{
|
||||
var plugins = PluginLoader.Instance.Plugins;
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information($"Initializing camera plugin {plugin.GetType().Name} for camera {cameraName}");
|
||||
plugin.RegisterCameraModules(self,cameraName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error initializing camera plugin {plugin.GetType().Name} for camera {cameraName}: {e}", e);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
10
VisionBuilder.UI.Common/Plugins/IPlugin.cs
Normal file
10
VisionBuilder.UI.Common/Plugins/IPlugin.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Plugins;
|
||||
|
||||
public interface IPlugin
|
||||
{
|
||||
public void RegisterGlobalModules(IKernel kernel);
|
||||
public void RegisterCameraModules(IKernel kernel, string cameraName);
|
||||
}
|
||||
37
VisionBuilder.UI.Common/Plugins/PluginLoadContext.cs
Normal file
37
VisionBuilder.UI.Common/Plugins/PluginLoadContext.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Plugins;
|
||||
|
||||
class PluginLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private AssemblyDependencyResolver _resolver;
|
||||
|
||||
public PluginLoadContext(string pluginPath)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
if (libraryPath != null)
|
||||
{
|
||||
return LoadUnmanagedDllFromPath(libraryPath);
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
}
|
||||
67
VisionBuilder.UI.Common/Plugins/PluginLoader.cs
Normal file
67
VisionBuilder.UI.Common/Plugins/PluginLoader.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Reflection;
|
||||
using Inspectron.Settings;
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Plugins;
|
||||
|
||||
public class PluginLoader
|
||||
{
|
||||
private static PluginLoader _instance;
|
||||
public static PluginLoader Instance => _instance ??= new PluginLoader();
|
||||
|
||||
const string PLUGINS_DIRECTORY = "Plugins";
|
||||
private PluginLoader()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public List<IPlugin> Plugins { get; } = new List<IPlugin>();
|
||||
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Log.Information("Loading plugins...");
|
||||
var pluginsPath = Path.Combine("..", "Data", PLUGINS_DIRECTORY);
|
||||
Directory.CreateDirectory(pluginsPath);
|
||||
var pluginFolders = Directory.GetDirectories(pluginsPath);
|
||||
|
||||
foreach (string folder in pluginFolders)
|
||||
{
|
||||
var assemblyPath = Path.Combine(folder, Path.GetFileNameWithoutExtension(folder) + ".dll");
|
||||
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
Log.Warning("Plugin assembly not found: {AssemblyPath}", assemblyPath);
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
LoadPluginAssembly(assemblyPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to load plugin from {AssemblyPath}", assemblyPath);
|
||||
}
|
||||
}
|
||||
Log.Information("Plugin loading completed.");
|
||||
}
|
||||
|
||||
private void LoadPluginAssembly(string assemblyPath)
|
||||
{
|
||||
var absPath = Path.GetFullPath(assemblyPath);
|
||||
var loadContext = new PluginLoadContext(absPath);
|
||||
var assembly = loadContext.LoadFromAssemblyPath(absPath);
|
||||
Log.Information("Loaded plugin assembly: {AssemblyName}", assembly.GetName().Name);
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.IsAssignableTo(typeof(IPlugin)) && !type.IsAbstract)
|
||||
{
|
||||
Plugins.Add((IPlugin)Activator.CreateInstance(type));
|
||||
Log.Information("Discovered plugin: {PluginName}", type.Name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ public interface IRecognitionControl
|
||||
void SetRecipe(RecipeData recipe);
|
||||
void Start();
|
||||
void Stop();
|
||||
void Pause();
|
||||
void Resume();
|
||||
event Action<ImageProcessedEvent> ImageProcessed;
|
||||
event Action<SessionStartedEvent> SessionStarted;
|
||||
event Action<SessionEndedEvent> SessionEnded;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Inspectron.Settings;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using VisionBuilder.UI.Common.Utils;
|
||||
|
||||
namespace VisionBuilder.UI.Common;
|
||||
|
||||
@@ -11,11 +14,55 @@ public class UIConfiguration: ISettings
|
||||
|
||||
public string AdminPassword { get; set; } = "";
|
||||
|
||||
public List<ErrorShortName> ErrorShortNames { get; set; } = new List<ErrorShortName>();
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => this.MaxErrorCount, "Errors", "Max errors count", "UI");
|
||||
settings.RegisterSimple(this, () => this.AdminPassword, "System", "Password", "UI");
|
||||
settings.RegisterSimple(this, () => this.ErrorShortNames, "Errors", "Error short names", "UI");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ErrorShortName
|
||||
{
|
||||
public string FullName { get; set; }
|
||||
public string ShortName { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{FullName} -> {ShortName}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorShortNameConverter : GeneralListTypeConverter<ErrorShortName>
|
||||
{
|
||||
|
||||
protected override ErrorShortName ConvertFromString(ITypeDescriptorContext context, CultureInfo culture, string stringValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
return null;
|
||||
|
||||
var parts = stringValue.Split(',');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
return new ErrorShortName
|
||||
{
|
||||
FullName = parts[0].Trim(),
|
||||
ShortName = parts[1].Trim()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string ConvertToString(ITypeDescriptorContext context, CultureInfo culture, ErrorShortName errorShortName)
|
||||
{
|
||||
if (errorShortName == null)
|
||||
return null;
|
||||
|
||||
return $"{errorShortName.FullName},{errorShortName.ShortName}";
|
||||
}
|
||||
}
|
||||
85
VisionBuilder.UI.Common/Utils/GeneralListTypeConverter.cs
Normal file
85
VisionBuilder.UI.Common/Utils/GeneralListTypeConverter.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Utils;
|
||||
|
||||
public abstract class GeneralListTypeConverter<T>:TypeConverter
|
||||
where T: class
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string stringValue)
|
||||
{
|
||||
// Check if we're converting to a single ErrorShortName
|
||||
if (context?.PropertyDescriptor?.PropertyType == typeof(T))
|
||||
{
|
||||
return ConvertFromString(context, culture, stringValue);
|
||||
}
|
||||
|
||||
// Otherwise, convert to a list of ErrorShortName
|
||||
var errorShortNames = new List<T>();
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
var lines = stringValue.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var errorShortName = ConvertFromString(context, culture, line.Trim());
|
||||
if (errorShortName != null)
|
||||
errorShortNames.Add(errorShortName);
|
||||
}
|
||||
}
|
||||
|
||||
return errorShortNames;
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
|
||||
Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
// Handle single ErrorShortName
|
||||
if (value is T errorShortName)
|
||||
{
|
||||
return this.ConvertToString(context, culture, errorShortName);
|
||||
}
|
||||
|
||||
// Handle list of ErrorShortName
|
||||
if (value is List<T> errorShortNames)
|
||||
{
|
||||
var lines = errorShortNames.Select(e =>
|
||||
ConvertToString(context, culture, e)
|
||||
).Where(line => !string.IsNullOrEmpty(line));
|
||||
|
||||
return string.Join("\n", lines);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a single string to ErrorShortName
|
||||
/// </summary>
|
||||
protected abstract T ConvertFromString(ITypeDescriptorContext context, CultureInfo culture,
|
||||
string stringValue);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a single ErrorShortName to string
|
||||
/// </summary>
|
||||
protected abstract string ConvertToString(ITypeDescriptorContext context, CultureInfo culture,
|
||||
T errorShortName);
|
||||
}
|
||||
@@ -37,12 +37,21 @@ public class ErrorsVM:IEventHandler<ImageProcessedEvent>, IEventHandler<SessionS
|
||||
{
|
||||
if(!@event.HasError)return;
|
||||
|
||||
string additionalInfo = string.Empty;
|
||||
var errorShortName = _uiConfiguration.ErrorShortNames
|
||||
.FirstOrDefault(x => x.FullName == @event.ErrorNames[0]);
|
||||
|
||||
if (errorShortName != null)
|
||||
{
|
||||
additionalInfo += " ,"+errorShortName.ShortName;
|
||||
}
|
||||
|
||||
ErrorData errorData = new ErrorData
|
||||
{
|
||||
RecipeName = @event.RecipeName,
|
||||
ImageOriginal = @event.ImageOriginal,
|
||||
ImageAnalysis = @event.ImageAnalysis,
|
||||
Title = @event.ErrorNames +" "+ DateTime.Now.ToString("T")
|
||||
Title =DateTime.Now.ToString("G")+ additionalInfo
|
||||
};
|
||||
|
||||
if (Errors.Count >= _uiConfiguration.MaxErrorCount)
|
||||
|
||||
@@ -6,5 +6,5 @@ namespace VisionBuilder.UI.Common.ViewModel;
|
||||
public class RecipeSelectionVM
|
||||
{
|
||||
public ObservableCollection<RecipeData> Recipes { get; set; }
|
||||
public RecipeData SelectedRecipe { get; set; }
|
||||
public RecipeData? SelectedRecipe { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.Commands.Interfaces;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
@@ -12,6 +13,8 @@ namespace VisionBuilder.UI.Common
|
||||
{
|
||||
public partial class SingleCameraVM : ObservableObject, IEventHandler<ImageProcessedEvent>
|
||||
{
|
||||
public SynchronizationContext? SynchronizationContext { get; set; }
|
||||
|
||||
private readonly IRecipeSelectionDialogService _recipeSelectionDialogService;
|
||||
private readonly IRecognitionControl _recognitionControl;
|
||||
|
||||
@@ -29,21 +32,38 @@ namespace VisionBuilder.UI.Common
|
||||
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(StopCommand))]
|
||||
private bool _isRunning;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CameraLabelAndStatus))]
|
||||
private string _cameraLabel;
|
||||
|
||||
|
||||
public string CameraLabelAndStatus=>
|
||||
$"{CameraLabel}{(IsPaused ? "(PAUSED)" : "")}";
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CameraLabelAndStatus))]
|
||||
private bool _isPaused;
|
||||
|
||||
public bool IsResumed => !IsPaused;
|
||||
|
||||
public bool CanStop => IsRunning;
|
||||
public bool CanStart => !IsRunning && SelectedRecipe!=null;
|
||||
|
||||
public bool IsNotRunning => !IsRunning;
|
||||
private bool _handlingEnabled = true;
|
||||
|
||||
public bool RecipeSelected => SelectedRecipe != null;
|
||||
public bool RecipeNotSelected => SelectedRecipe == null;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanStop))]
|
||||
[NotifyPropertyChangedFor(nameof(CanStart))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SelectRecipeCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(StopCommand))]
|
||||
[NotifyPropertyChangedFor(nameof(RecipeSelected))]
|
||||
[NotifyPropertyChangedFor(nameof(RecipeNotSelected))]
|
||||
private RecipeData? _selectedRecipe;
|
||||
|
||||
|
||||
@@ -69,11 +89,24 @@ namespace VisionBuilder.UI.Common
|
||||
[RelayCommand(CanExecute = nameof(IsNotRunning))]
|
||||
public void SelectRecipe()
|
||||
{
|
||||
var vm = new RecipeSelectionVM();
|
||||
vm.Recipes = new ObservableCollection<RecipeData>(_recognitionControl.GetRecipesData());
|
||||
var vm = GetRecipeSelectionVm();
|
||||
if (!_recipeSelectionDialogService.SelectRecipe(vm))return;
|
||||
ProcessRecipeSelectionVm(vm);
|
||||
}
|
||||
|
||||
public RecipeSelectionVM GetRecipeSelectionVm()
|
||||
{
|
||||
var vm= new RecipeSelectionVM();
|
||||
vm.Recipes = new ObservableCollection<RecipeData>(_recognitionControl.GetRecipesData());
|
||||
vm.SelectedRecipe = SelectedRecipe;
|
||||
return vm;
|
||||
}
|
||||
|
||||
public void ProcessRecipeSelectionVm(RecipeSelectionVM vm)
|
||||
{
|
||||
if(!SelectRecipeCommand.CanExecute(null))return;
|
||||
SelectedRecipe = vm.SelectedRecipe;
|
||||
_recognitionControl.SetRecipe(SelectedRecipe);
|
||||
_recognitionControl.SetRecipe(SelectedRecipe!);
|
||||
CurrentRecipeName = SelectedRecipe?.RecipeName ?? "Select recipe";
|
||||
}
|
||||
|
||||
@@ -93,6 +126,22 @@ namespace VisionBuilder.UI.Common
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(IsResumed))]
|
||||
public void Pause()
|
||||
{
|
||||
_recognitionControl.Pause();
|
||||
IsPaused = true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(IsPaused))]
|
||||
public void Resume()
|
||||
{
|
||||
_recognitionControl.Resume();
|
||||
IsPaused = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Handle(ImageProcessedEvent @event)
|
||||
{
|
||||
|
||||
@@ -17,10 +17,14 @@ public partial class StatisticsVM: ObservableObject,IEventHandler<ImageProcessed
|
||||
|
||||
[ObservableProperty] private int _good;
|
||||
[ObservableProperty] private int _bad;
|
||||
[ObservableProperty] private int _total;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ErrorRate))]
|
||||
private int _total;
|
||||
|
||||
[ObservableProperty] private string _statisticsDetails="";
|
||||
|
||||
public string ErrorRate => _bad/(float)_total * 100 + "%";
|
||||
|
||||
private readonly Dictionary<string, int> _errorTypes = new();
|
||||
|
||||
public void Handle(ImageProcessedEvent @event)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Inspectron.Settings;
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using System.ComponentModel;
|
||||
using VisionBuilder.UI.Common.Plugins;
|
||||
using VisionBuilder.UI.Common.ViewModel;
|
||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
|
||||
@@ -10,6 +12,8 @@ public static class VisionBuilder
|
||||
{
|
||||
public static IKernel CreateMainKernel(InspectronSettings settings)
|
||||
{
|
||||
TypeDescriptor.AddAttributes(typeof(List<ErrorShortName>), new TypeConverterAttribute(typeof(ErrorShortNameConverter)));
|
||||
|
||||
StandardKernel mainKernel = new StandardKernel();
|
||||
|
||||
// GLOBAL SETTINGS //
|
||||
@@ -22,4 +26,6 @@ public static class VisionBuilder
|
||||
return mainKernel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user