From d52c844b5d2241e8de549929ee00ab8ad82b7691 Mon Sep 17 00:00:00 2001 From: meelstorm <8780022+TesAnti@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:30:52 +0200 Subject: [PATCH] long candy support --- .../BaseOperation.cs | 2 + .../PralinenPLC/NetworkResultInformer.cs | 118 +++ Plugins/Pralinen/PralinenPLC/Plugin.cs | 20 + .../Pralinen/PralinenPLC/PralinenModule.cs | 33 + .../Pralinen/PralinenPLC/PralinenPLC.csproj | 21 + Plugins/Pralinen/PralinenPLC/TCP/Buffer.cs | 182 +++++ Plugins/Pralinen/PralinenPLC/TCP/TcpServer.cs | 525 +++++++++++++ .../Pralinen/PralinenPLC/TCP/TcpSession.cs | 722 ++++++++++++++++++ PythonModelAPI/PythonModelAPI.py | 4 + PythonModelAPI/PythonModelAPI.pyproj | 3 + PythonModelAPI/docker start.txt | 1 + PythonModelAPI/dockerfile | 26 + PythonModelAPI/test.py | 5 + .../Plugins/PluginLoader.cs | 7 +- .../Plugins/PluginSettings.cs | 24 +- .../ViewModel/StatisticsVM.cs | 4 +- .../HawkeyeRecognitionControl.cs | 4 +- .../OpenCVCanvas.cs | 6 +- .../WorkflowRecipeHelper.cs | 2 +- VisionBuilder.UI.Windows.Test/Form1.cs | 1 + VisionBuilder.UI.Windows.Test/Program.cs | 2 +- .../Dialogs/RecipeSelectionDialog.cs | 2 +- .../WindowsRecipeSelectionDialogService.cs | 9 +- VisionBuilder.UI.sln | 7 + 24 files changed, 1714 insertions(+), 16 deletions(-) create mode 100644 Plugins/Pralinen/PralinenPLC/NetworkResultInformer.cs create mode 100644 Plugins/Pralinen/PralinenPLC/Plugin.cs create mode 100644 Plugins/Pralinen/PralinenPLC/PralinenModule.cs create mode 100644 Plugins/Pralinen/PralinenPLC/PralinenPLC.csproj create mode 100644 Plugins/Pralinen/PralinenPLC/TCP/Buffer.cs create mode 100644 Plugins/Pralinen/PralinenPLC/TCP/TcpServer.cs create mode 100644 Plugins/Pralinen/PralinenPLC/TCP/TcpSession.cs create mode 100644 PythonModelAPI/docker start.txt create mode 100644 PythonModelAPI/dockerfile create mode 100644 PythonModelAPI/test.py diff --git a/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs b/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs index 45ffd1d..446e9df 100644 --- a/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs +++ b/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; +using Serilog; using RectangleElement = Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle.RectangleElement; namespace Hawkeye.VisionBuilder.Workflow @@ -300,6 +301,7 @@ namespace Hawkeye.VisionBuilder.Workflow public void SetError(string eMessage) { + Log.Information($"Processing error on {Label}:{eMessage}"); Status=eMessage; Result = false; } diff --git a/Plugins/Pralinen/PralinenPLC/NetworkResultInformer.cs b/Plugins/Pralinen/PralinenPLC/NetworkResultInformer.cs new file mode 100644 index 0000000..b3b9818 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/NetworkResultInformer.cs @@ -0,0 +1,118 @@ +using System.Collections; +using PralinenPLC.TCP; + +namespace PralinenPLC; + +public class NetworkResultInformer : TcpServer, IDisposable +{ + + private bool _systemReady = false; + private bool _dataReady = false; + private bool _dataOk = false; + private bool _firstConnection = false; + private static bool _isStarted = false; + public NetworkResultInformer() : base("0.0.0.0", 42010) + { + Console.WriteLine("------"); + Console.WriteLine("NetworkResultInformer"); + Console.WriteLine("------"); + if (!_isStarted) + { + Start(); + _isStarted = true; + } + _systemReady = true; + _dataReady = false; + _dataOk = false; + + } + List _sessions = new List(); + protected override void OnConnected(TcpSession session) + { + Console.WriteLine("Client connected"); + Console.WriteLine("------"); + + _sessions.Add(session); + _firstConnection = true; + base.OnConnected(session); + SendStatus(); + } + + protected override void OnDisconnected(TcpSession session) + { + Console.WriteLine("Client disconnected"); + Console.WriteLine("------"); + _sessions.Remove(session); + base.OnDisconnected(session); + } + byte ConvertToByte(BitArray bits) + { + if (bits.Count != 8) + { + throw new ArgumentException("bits"); + } + byte[] bytes = new byte[1]; + bits.CopyTo(bytes, 0); + return bytes[0]; + } + public void SendStatus() + { + try + { + Console.WriteLine($"Sessions:{_sessions.Count}"); + foreach (TcpSession session in _sessions) + { + + byte[] data = new byte[10]; + + BitArray arr = new BitArray(8); + + arr.Set(0, _systemReady); + arr.Set(1, _dataReady); + arr.Set(2, _dataOk); + arr.Set(7, _firstConnection); + + Console.WriteLine($"Sending status: system:{_systemReady} data:{_dataReady} ok:{_dataOk} init:{_firstConnection}"); + + + + data[0] = ConvertToByte(arr); + + session.Send(data); + + + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + _firstConnection = false; + } + + public void Reset() + { + _dataReady = true; + _dataOk = true; + SendStatus(); + } + + public int Delay { get; } = 0; + + public void SignalBad() + { + _dataReady = true; + _dataOk = false; + SendStatus(); + } + + public void Dispose() + { + _systemReady = false; + _dataReady = false; + _dataOk = false; + SendStatus(); + + } +} \ No newline at end of file diff --git a/Plugins/Pralinen/PralinenPLC/Plugin.cs b/Plugins/Pralinen/PralinenPLC/Plugin.cs new file mode 100644 index 0000000..3b2de97 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/Plugin.cs @@ -0,0 +1,20 @@ +using Ninject; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Plugins; + +namespace PralinenPLC +{ + public class Plugin: IPlugin + { + public void RegisterGlobalModules(IKernel kernel) + { + + } + + public void RegisterCameraModules(IKernel kernel, string cameraName) + { + // ONLY ONE CAMERA !!! + kernel.RegisterModule(); + } + } +} diff --git a/Plugins/Pralinen/PralinenPLC/PralinenModule.cs b/Plugins/Pralinen/PralinenPLC/PralinenModule.cs new file mode 100644 index 0000000..3532c57 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/PralinenModule.cs @@ -0,0 +1,33 @@ +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.RecipeProcessing; + +namespace PralinenPLC; + +public class PralinenModule:IVisionBuilderModule +{ + private readonly IRecognitionControl _recognitionControl; + private NetworkResultInformer _networkResultInformer; + + public PralinenModule(IRecognitionControl recognitionControl) + { + _recognitionControl = recognitionControl; + } + + public void InitializeModule() + { + _networkResultInformer = new NetworkResultInformer(); + _recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed; + } + + private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj) + { + if (obj.HasError) + { + _networkResultInformer.SignalBad(); + } + else + { + _networkResultInformer.Reset(); + } + } +} \ No newline at end of file diff --git a/Plugins/Pralinen/PralinenPLC/PralinenPLC.csproj b/Plugins/Pralinen/PralinenPLC/PralinenPLC.csproj new file mode 100644 index 0000000..e092a86 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/PralinenPLC.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + true + D:\Inspectron\Hawkeye\code\VisionBuilder5\VisionBuilder.UI\VisionBuilder.UI.Windows.Test\bin\Debug\Data\Plugins\PralinenPLC + + + + + False + runtime + + + false + runtime + + + diff --git a/Plugins/Pralinen/PralinenPLC/TCP/Buffer.cs b/Plugins/Pralinen/PralinenPLC/TCP/Buffer.cs new file mode 100644 index 0000000..57a73ce --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/TCP/Buffer.cs @@ -0,0 +1,182 @@ +using System.Diagnostics; +using System.Text; + +namespace PralinenPLC.TCP +{ + /// + /// Dynamic byte buffer + /// + public class Buffer + { + private byte[] _data; + private long _size; + private long _offset; + + /// + /// Is the buffer empty? + /// + public bool IsEmpty => (_data == null) || (_size == 0); + /// + /// Bytes memory buffer + /// + public byte[] Data => _data; + /// + /// Bytes memory buffer capacity + /// + public long Capacity => _data.Length; + /// + /// Bytes memory buffer size + /// + public long Size => _size; + /// + /// Bytes memory buffer offset + /// + public long Offset => _offset; + + /// + /// Buffer indexer operator + /// + public byte this[int index] => _data[index]; + + /// + /// Initialize a new expandable buffer with zero capacity + /// + public Buffer() { _data = new byte[0]; _size = 0; _offset = 0; } + /// + /// Initialize a new expandable buffer with the given capacity + /// + public Buffer(long capacity) { _data = new byte[capacity]; _size = 0; _offset = 0; } + /// + /// Initialize a new expandable buffer with the given data + /// + public Buffer(byte[] data) { _data = data; _size = data.Length; _offset = 0; } + + #region Memory buffer methods + + /// + /// Get string from the current buffer + /// + public override string ToString() + { + return ExtractString(0, _size); + } + + // Clear the current buffer and its offset + public void Clear() + { + _size = 0; + _offset = 0; + } + + /// + /// Extract the string from buffer of the given offset and size + /// + public string ExtractString(long offset, long size) + { + Debug.Assert(((offset + size) <= Size), "Invalid offset & size!"); + if ((offset + size) > Size) + throw new ArgumentException("Invalid offset & size!", nameof(offset)); + + return Encoding.UTF8.GetString(_data, (int)offset, (int)size); + } + + /// + /// Remove the buffer of the given offset and size + /// + public void Remove(long offset, long size) + { + Debug.Assert(((offset + size) <= Size), "Invalid offset & size!"); + if ((offset + size) > Size) + throw new ArgumentException("Invalid offset & size!", nameof(offset)); + + Array.Copy(_data, offset + size, _data, offset, _size - size - offset); + _size -= size; + if (_offset >= (offset + size)) + _offset -= size; + else if (_offset >= offset) + { + _offset -= _offset - offset; + if (_offset > Size) + _offset = Size; + } + } + + /// + /// Reserve the buffer of the given capacity + /// + public void Reserve(long capacity) + { + Debug.Assert((capacity >= 0), "Invalid reserve capacity!"); + if (capacity < 0) + throw new ArgumentException("Invalid reserve capacity!", nameof(capacity)); + + if (capacity > Capacity) + { + byte[] data = new byte[Math.Max(capacity, 2 * Capacity)]; + Array.Copy(_data, 0, data, 0, _size); + _data = data; + } + } + + // Resize the current buffer + public void Resize(long size) + { + Reserve(size); + _size = size; + if (_offset > _size) + _offset = _size; + } + + // Shift the current buffer offset + public void Shift(long offset) { _offset += offset; } + // Unshift the current buffer offset + public void Unshift(long offset) { _offset -= offset; } + + #endregion + + #region Buffer I/O methods + + /// + /// Append the given buffer + /// + /// Buffer to append + /// Count of append bytes + public long Append(byte[] buffer) + { + Reserve(_size + buffer.Length); + Array.Copy(buffer, 0, _data, _size, buffer.Length); + _size += buffer.Length; + return buffer.Length; + } + + /// + /// Append the given buffer fragment + /// + /// Buffer to append + /// Buffer offset + /// Buffer size + /// Count of append bytes + public long Append(byte[] buffer, long offset, long size) + { + Reserve(_size + size); + Array.Copy(buffer, offset, _data, _size, size); + _size += size; + return size; + } + + /// + /// Append the given text in UTF-8 encoding + /// + /// Text to append + /// Count of append bytes + public long Append(string text) + { + Reserve(_size + Encoding.UTF8.GetMaxByteCount(text.Length)); + long result = Encoding.UTF8.GetBytes(text, 0, text.Length, _data, (int)_size); + _size += result; + return result; + } + + #endregion + } +} diff --git a/Plugins/Pralinen/PralinenPLC/TCP/TcpServer.cs b/Plugins/Pralinen/PralinenPLC/TCP/TcpServer.cs new file mode 100644 index 0000000..015eba8 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/TCP/TcpServer.cs @@ -0,0 +1,525 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace PralinenPLC.TCP +{ + /// + /// TCP server is used to connect, disconnect and manage TCP sessions + /// + /// Thread-safe + public class TcpServer : IDisposable + { + /// + /// Initialize TCP server with a given IP address and port number + /// + /// IP address + /// Port number + public TcpServer(IPAddress address, int port) : this(new IPEndPoint(address, port)) {} + /// + /// Initialize TCP server with a given IP address and port number + /// + /// IP address + /// Port number + public TcpServer(string address, int port) : this(new IPEndPoint(IPAddress.Parse(address), port)) {} + /// + /// Initialize TCP server with a given IP endpoint + /// + /// IP endpoint + public TcpServer(IPEndPoint endpoint) + { + Id = Guid.NewGuid(); + Endpoint = endpoint; + } + + /// + /// Server Id + /// + public Guid Id { get; } + + /// + /// IP endpoint + /// + public IPEndPoint Endpoint { get; private set; } + + /// + /// Number of sessions connected to the server + /// + public long ConnectedSessions { get { return Sessions.Count; } } + /// + /// Number of bytes pending sent by the server + /// + public long BytesPending { get { return _bytesPending; } } + /// + /// Number of bytes sent by the server + /// + public long BytesSent { get { return _bytesSent; } } + /// + /// Number of bytes received by the server + /// + public long BytesReceived { get { return _bytesReceived; } } + + /// + /// Option: acceptor backlog size + /// + /// + /// This option will set the listening socket's backlog size + /// + public int OptionAcceptorBacklog { get; set; } = 1024; + /// + /// Option: dual mode socket + /// + /// + /// Specifies whether the Socket is a dual-mode socket used for both IPv4 and IPv6. + /// Will work only if socket is bound on IPv6 address. + /// + public bool OptionDualMode { get; set; } + /// + /// Option: keep alive + /// + /// + /// This option will setup SO_KEEPALIVE if the OS support this feature + /// + public bool OptionKeepAlive { get; set; } + /// + /// Option: no delay + /// + /// + /// This option will enable/disable Nagle's algorithm for TCP protocol + /// + public bool OptionNoDelay { get; set; } + /// + /// Option: reuse address + /// + /// + /// This option will enable/disable SO_REUSEADDR if the OS support this feature + /// + public bool OptionReuseAddress { get; set; } + /// + /// Option: enables a socket to be bound for exclusive access + /// + /// + /// This option will enable/disable SO_EXCLUSIVEADDRUSE if the OS support this feature + /// + public bool OptionExclusiveAddressUse { get; set; } + /// + /// Option: receive buffer size + /// + public int OptionReceiveBufferSize { get; set; } = 8192; + /// + /// Option: send buffer size + /// + public int OptionSendBufferSize { get; set; } = 8192; + + #region Start/Stop server + + // Server acceptor + private Socket _acceptorSocket; + private SocketAsyncEventArgs _acceptorEventArg; + + // Server statistic + internal long _bytesPending; + internal long _bytesSent; + internal long _bytesReceived; + + /// + /// Is the server started? + /// + public bool IsStarted { get; private set; } + /// + /// Is the server accepting new clients? + /// + public bool IsAccepting { get; private set; } + + /// + /// Create a new socket object + /// + /// + /// Method may be override if you need to prepare some specific socket object in your implementation. + /// + /// Socket object + protected virtual Socket CreateSocket() + { + return new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + } + + /// + /// Start the server + /// + /// 'true' if the server was successfully started, 'false' if the server failed to start + public virtual bool Start() + { + Debug.Assert(!IsStarted, "TCP server is already started!"); + if (IsStarted) + return false; + + // Setup acceptor event arg + _acceptorEventArg = new SocketAsyncEventArgs(); + _acceptorEventArg.Completed += OnAsyncCompleted; + + // Create a new acceptor socket + _acceptorSocket = CreateSocket(); + + // Update the acceptor socket disposed flag + IsSocketDisposed = false; + + // Apply the option: reuse address + _acceptorSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, OptionReuseAddress); + // Apply the option: exclusive address use + _acceptorSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, OptionExclusiveAddressUse); + // Apply the option: dual mode (this option must be applied before listening) + if (_acceptorSocket.AddressFamily == AddressFamily.InterNetworkV6) + _acceptorSocket.DualMode = OptionDualMode; + + // Bind the acceptor socket to the IP endpoint + _acceptorSocket.Bind(Endpoint); + // Refresh the endpoint property based on the actual endpoint created + Endpoint = (IPEndPoint)_acceptorSocket.LocalEndPoint; + // Start listen to the acceptor socket with the given accepting backlog size + _acceptorSocket.Listen(OptionAcceptorBacklog); + + // Reset statistic + _bytesPending = 0; + _bytesSent = 0; + _bytesReceived = 0; + + // Update the started flag + IsStarted = true; + + // Call the server started handler + OnStarted(); + + // Perform the first server accept + IsAccepting = true; + StartAccept(_acceptorEventArg); + + return true; + } + + /// + /// Stop the server + /// + /// 'true' if the server was successfully stopped, 'false' if the server is already stopped + public virtual bool Stop() + { + Debug.Assert(IsStarted, "TCP server is not started!"); + if (!IsStarted) + return false; + + // Stop accepting new clients + IsAccepting = false; + + // Reset acceptor event arg + _acceptorEventArg.Completed -= OnAsyncCompleted; + + // Close the acceptor socket + _acceptorSocket.Close(); + + // Dispose the acceptor socket + _acceptorSocket.Dispose(); + + // Dispose event arguments + _acceptorEventArg.Dispose(); + + // Update the acceptor socket disposed flag + IsSocketDisposed = true; + + // Disconnect all sessions + DisconnectAll(); + + // Update the started flag + IsStarted = false; + + // Call the server stopped handler + OnStopped(); + + return true; + } + + /// + /// Restart the server + /// + /// 'true' if the server was successfully restarted, 'false' if the server failed to restart + public virtual bool Restart() + { + if (!Stop()) + return false; + + while (IsStarted) + Thread.Yield(); + + return Start(); + } + + #endregion + + #region Accepting clients + + /// + /// Start accept a new client connection + /// + private void StartAccept(SocketAsyncEventArgs e) + { + // Socket must be cleared since the context object is being reused + e.AcceptSocket = null; + + // Async accept a new client connection + if (!_acceptorSocket.AcceptAsync(e)) + ProcessAccept(e); + } + + /// + /// Process accepted client connection + /// + private void ProcessAccept(SocketAsyncEventArgs e) + { + if (e.SocketError == SocketError.Success) + { + // Create a new session to register + var session = CreateSession(); + + // Register the session + RegisterSession(session); + + // Connect new session + session.Connect(e.AcceptSocket); + } + else + SendError(e.SocketError); + + // Accept the next client connection + if (IsAccepting) + StartAccept(e); + } + + /// + /// This method is the callback method associated with Socket.AcceptAsync() + /// operations and is invoked when an accept operation is complete + /// + private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e) + { + ProcessAccept(e); + } + + #endregion + + #region Session factory + + /// + /// Create TCP session factory method + /// + /// TCP session + protected virtual TcpSession CreateSession() { return new TcpSession(this); } + + #endregion + + #region Session management + + // Server sessions + protected readonly ConcurrentDictionary Sessions = new ConcurrentDictionary(); + + /// + /// Disconnect all connected sessions + /// + /// 'true' if all sessions were successfully disconnected, 'false' if the server is not started + public virtual bool DisconnectAll() + { + if (!IsStarted) + return false; + + // Disconnect all sessions + foreach (var session in Sessions.Values) + session.Disconnect(); + + return true; + } + + /// + /// Find a session with a given Id + /// + /// Session Id + /// Session with a given Id or null if the session it not connected + public TcpSession FindSession(Guid id) + { + // Try to find the required session + return Sessions.TryGetValue(id, out TcpSession result) ? result : null; + } + + /// + /// Register a new session + /// + /// Session to register + internal void RegisterSession(TcpSession session) + { + // Register a new session + Sessions.TryAdd(session.Id, session); + } + + /// + /// Unregister session by Id + /// + /// Session Id + internal void UnregisterSession(Guid id) + { + // Unregister session by Id + Sessions.TryRemove(id, out TcpSession temp); + } + + #endregion + + #region Multicasting + + /// + /// Multicast data to all connected sessions + /// + /// Buffer to multicast + /// 'true' if the data was successfully multicasted, 'false' if the data was not multicasted + public virtual bool Multicast(byte[] buffer) { return Multicast(buffer, 0, buffer.Length); } + + /// + /// Multicast data to all connected clients + /// + /// Buffer to multicast + /// Buffer offset + /// Buffer size + /// 'true' if the data was successfully multicasted, 'false' if the data was not multicasted + public virtual bool Multicast(byte[] buffer, long offset, long size) + { + if (!IsStarted) + return false; + + if (size == 0) + return true; + + // Multicast data to all sessions + foreach (var session in Sessions.Values) + session.SendAsync(buffer, offset, size); + + return true; + } + + /// + /// Multicast text to all connected clients + /// + /// Text string to multicast + /// 'true' if the text was successfully multicasted, 'false' if the text was not multicasted + public virtual bool Multicast(string text) { return Multicast(Encoding.UTF8.GetBytes(text)); } + + #endregion + + #region Server handlers + + /// + /// Handle server started notification + /// + protected virtual void OnStarted() {} + /// + /// Handle server stopped notification + /// + protected virtual void OnStopped() {} + + /// + /// Handle session connected notification + /// + /// Connected session + protected virtual void OnConnected(TcpSession session) {} + /// + /// Handle session disconnected notification + /// + /// Disconnected session + protected virtual void OnDisconnected(TcpSession session) {} + + /// + /// Handle error notification + /// + /// Socket error code + protected virtual void OnError(SocketError error) {} + + internal void OnConnectedInternal(TcpSession session) { OnConnected(session); } + internal void OnDisconnectedInternal(TcpSession session) { OnDisconnected(session); } + + #endregion + + #region Error handling + + /// + /// Send error notification + /// + /// Socket error code + private void SendError(SocketError error) + { + // Skip disconnect errors + if ((error == SocketError.ConnectionAborted) || + (error == SocketError.ConnectionRefused) || + (error == SocketError.ConnectionReset) || + (error == SocketError.OperationAborted) || + (error == SocketError.Shutdown)) + return; + + OnError(error); + } + + #endregion + + #region IDisposable implementation + + /// + /// Disposed flag + /// + public bool IsDisposed { get; private set; } + + /// + /// Acceptor socket disposed flag + /// + public bool IsSocketDisposed { get; private set; } = true; + + // Implement IDisposable. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposingManagedResources) + { + // The idea here is that Dispose(Boolean) knows whether it is + // being called to do explicit cleanup (the Boolean is true) + // versus being called due to a garbage collection (the Boolean + // is false). This distinction is useful because, when being + // disposed explicitly, the Dispose(Boolean) method can safely + // execute code using reference type fields that refer to other + // objects knowing for sure that these other objects have not been + // finalized or disposed of yet. When the Boolean is false, + // the Dispose(Boolean) method should not execute code that + // refer to reference type fields because those objects may + // have already been finalized." + + if (!IsDisposed) + { + if (disposingManagedResources) + { + // Dispose managed resources here... + Stop(); + } + + // Dispose unmanaged resources here... + + // Set large fields to null here... + + // Mark as disposed. + IsDisposed = true; + } + } + + // Use C# destructor syntax for finalization code. + ~TcpServer() + { + // Simply call Dispose(false). + Dispose(false); + } + + #endregion + } +} diff --git a/Plugins/Pralinen/PralinenPLC/TCP/TcpSession.cs b/Plugins/Pralinen/PralinenPLC/TCP/TcpSession.cs new file mode 100644 index 0000000..9333fc2 --- /dev/null +++ b/Plugins/Pralinen/PralinenPLC/TCP/TcpSession.cs @@ -0,0 +1,722 @@ +using System.Net.Sockets; +using System.Text; + +namespace PralinenPLC.TCP +{ + /// + /// TCP session is used to read and write data from the connected TCP client + /// + /// Thread-safe + public class TcpSession : IDisposable + { + /// + /// Initialize the session with a given server + /// + /// TCP server + public TcpSession(TcpServer server) + { + Id = Guid.NewGuid(); + Server = server; + OptionReceiveBufferSize = server.OptionReceiveBufferSize; + OptionSendBufferSize = server.OptionSendBufferSize; + } + + /// + /// Session Id + /// + public Guid Id { get; } + + /// + /// Server + /// + public TcpServer Server { get; } + /// + /// Socket + /// + public Socket Socket { get; private set; } + + /// + /// Number of bytes pending sent by the session + /// + public long BytesPending { get; private set; } + /// + /// Number of bytes sending by the session + /// + public long BytesSending { get; private set; } + /// + /// Number of bytes sent by the session + /// + public long BytesSent { get; private set; } + /// + /// Number of bytes received by the session + /// + public long BytesReceived { get; private set; } + + /// + /// Option: receive buffer size + /// + public int OptionReceiveBufferSize { get; set; } = 8192; + /// + /// Option: send buffer size + /// + public int OptionSendBufferSize { get; set; } = 8192; + + #region Connect/Disconnect session + + /// + /// Is the session connected? + /// + public bool IsConnected { get; private set; } + + /// + /// Connect the session + /// + /// Session socket + internal void Connect(Socket socket) + { + Socket = socket; + + // Update the session socket disposed flag + IsSocketDisposed = false; + + // Setup buffers + _receiveBuffer = new Buffer(); + _sendBufferMain = new Buffer(); + _sendBufferFlush = new Buffer(); + + // Setup event args + _receiveEventArg = new SocketAsyncEventArgs(); + _receiveEventArg.Completed += OnAsyncCompleted; + _sendEventArg = new SocketAsyncEventArgs(); + _sendEventArg.Completed += OnAsyncCompleted; + + // Apply the option: keep alive + if (Server.OptionKeepAlive) + Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + // Apply the option: no delay + if (Server.OptionNoDelay) + Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + + // Prepare receive & send buffers + _receiveBuffer.Reserve(OptionReceiveBufferSize); + _sendBufferMain.Reserve(OptionSendBufferSize); + _sendBufferFlush.Reserve(OptionSendBufferSize); + + // Reset statistic + BytesPending = 0; + BytesSending = 0; + BytesSent = 0; + BytesReceived = 0; + + // Update the connected flag + IsConnected = true; + + // Try to receive something from the client + TryReceive(); + + // Call the session connected handler + OnConnected(); + + // Call the session connected handler in the server + Server.OnConnectedInternal(this); + + // Call the empty send buffer handler + if (_sendBufferMain.IsEmpty) + OnEmpty(); + } + + /// + /// Disconnect the session + /// + /// 'true' if the section was successfully disconnected, 'false' if the section is already disconnected + public virtual bool Disconnect() + { + if (!IsConnected) + return false; + + // Reset event args + _receiveEventArg.Completed -= OnAsyncCompleted; + _sendEventArg.Completed -= OnAsyncCompleted; + + try + { + try + { + // Shutdown the socket associated with the client + Socket.Shutdown(SocketShutdown.Both); + } + catch (SocketException) {} + + // Close the session socket + Socket.Close(); + + // Dispose the session socket + Socket.Dispose(); + + // Dispose event arguments + _receiveEventArg.Dispose(); + _sendEventArg.Dispose(); + + // Update the session socket disposed flag + IsSocketDisposed = true; + } + catch (ObjectDisposedException) {} + + // Update the connected flag + IsConnected = false; + + // Update sending/receiving flags + _receiving = false; + _sending = false; + + // Clear send/receive buffers + ClearBuffers(); + + // Call the session disconnected handler + OnDisconnected(); + + // Call the session disconnected handler in the server + Server.OnDisconnectedInternal(this); + + // Unregister session + Server.UnregisterSession(Id); + + return true; + } + + #endregion + + #region Send/Recieve data + + // Receive buffer + private bool _receiving; + private Buffer _receiveBuffer; + private SocketAsyncEventArgs _receiveEventArg; + // Send buffer + private readonly object _sendLock = new object(); + private bool _sending; + private Buffer _sendBufferMain; + private Buffer _sendBufferFlush; + private SocketAsyncEventArgs _sendEventArg; + private long _sendBufferFlushOffset; + + /// + /// Send data to the client (synchronous) + /// + /// Buffer to send + /// Size of sent data + public virtual long Send(byte[] buffer) { return Send(buffer, 0, buffer.Length); } + + /// + /// Send data to the client (synchronous) + /// + /// Buffer to send + /// Buffer offset + /// Buffer size + /// Size of sent data + public virtual long Send(byte[] buffer, long offset, long size) + { + if (!IsConnected) + return 0; + + if (size == 0) + return 0; + + // Sent data to the client + long sent = Socket.Send(buffer, (int)offset, (int)size, SocketFlags.None, out SocketError ec); + if (sent > 0) + { + // Update statistic + BytesSent += sent; + Interlocked.Add(ref Server._bytesSent, size); + + // Call the buffer sent handler + OnSent(sent, BytesPending + BytesSending); + } + + // Check for socket error + if (ec != SocketError.Success) + { + SendError(ec); + Disconnect(); + } + + return sent; + } + + /// + /// Send text to the client (synchronous) + /// + /// Text string to send + /// Size of sent data + public virtual long Send(string text) { return Send(Encoding.UTF8.GetBytes(text)); } + + /// + /// Send data to the client (asynchronous) + /// + /// Buffer to send + /// 'true' if the data was successfully sent, 'false' if the session is not connected + public virtual bool SendAsync(byte[] buffer) { return SendAsync(buffer, 0, buffer.Length); } + + /// + /// Send data to the client (asynchronous) + /// + /// Buffer to send + /// Buffer offset + /// Buffer size + /// 'true' if the data was successfully sent, 'false' if the session is not connected + public virtual bool SendAsync(byte[] buffer, long offset, long size) + { + if (!IsConnected) + return false; + + if (size == 0) + return true; + + lock (_sendLock) + { + // Fill the main send buffer + _sendBufferMain.Append(buffer, offset, size); + + // Update statistic + BytesPending = _sendBufferMain.Size; + + // Avoid multiple send handlers + if (_sending) + return true; + else + _sending = true; + + // Try to send the main buffer + Task.Factory.StartNew(TrySend); + } + + return true; + } + + /// + /// Send text to the client (asynchronous) + /// + /// Text string to send + /// 'true' if the text was successfully sent, 'false' if the session is not connected + public virtual bool SendAsync(string text) { return SendAsync(Encoding.UTF8.GetBytes(text)); } + + /// + /// Receive data from the client (synchronous) + /// + /// Buffer to receive + /// Size of received data + public virtual long Receive(byte[] buffer) { return Receive(buffer, 0, buffer.Length); } + + /// + /// Receive data from the client (synchronous) + /// + /// Buffer to receive + /// Buffer offset + /// Buffer size + /// Size of received data + public virtual long Receive(byte[] buffer, long offset, long size) + { + if (!IsConnected) + return 0; + + if (size == 0) + return 0; + + // Receive data from the client + long received = Socket.Receive(buffer, (int)offset, (int)size, SocketFlags.None, out SocketError ec); + if (received > 0) + { + // Update statistic + BytesReceived += received; + Interlocked.Add(ref Server._bytesReceived, received); + + // Call the buffer received handler + OnReceived(buffer, 0, received); + } + + // Check for socket error + if (ec != SocketError.Success) + { + SendError(ec); + Disconnect(); + } + + return received; + } + + /// + /// Receive text from the client (synchronous) + /// + /// Text size to receive + /// Received text + public virtual string Receive(long size) + { + var buffer = new byte[size]; + var length = Receive(buffer); + return Encoding.UTF8.GetString(buffer, 0, (int)length); + } + + /// + /// Receive data from the client (asynchronous) + /// + public virtual void ReceiveAsync() + { + // Try to receive data from the client + TryReceive(); + } + + /// + /// Try to receive new data + /// + private void TryReceive() + { + if (_receiving) + return; + + if (!IsConnected) + return; + + bool process = true; + + while (process) + { + process = false; + + try + { + // Async receive with the receive handler + _receiving = true; + _receiveEventArg.SetBuffer(_receiveBuffer.Data, 0, (int)_receiveBuffer.Capacity); + if (!Socket.ReceiveAsync(_receiveEventArg)) + process = ProcessReceive(_receiveEventArg); + } + catch (ObjectDisposedException) {} + } + } + + /// + /// Try to send pending data + /// + private void TrySend() + { + if (!IsConnected) + return; + + bool empty = false; + bool process = true; + + while (process) + { + process = false; + + lock (_sendLock) + { + // Is previous socket send in progress? + if (_sendBufferFlush.IsEmpty) + { + // Swap flush and main buffers + _sendBufferFlush = Interlocked.Exchange(ref _sendBufferMain, _sendBufferFlush); + _sendBufferFlushOffset = 0; + + // Update statistic + BytesPending = 0; + BytesSending += _sendBufferFlush.Size; + + // Check if the flush buffer is empty + if (_sendBufferFlush.IsEmpty) + { + // Need to call empty send buffer handler + empty = true; + + // End sending process + _sending = false; + } + } + else + return; + } + + // Call the empty send buffer handler + if (empty) + { + OnEmpty(); + return; + } + + try + { + // Async write with the write handler + _sendEventArg.SetBuffer(_sendBufferFlush.Data, (int)_sendBufferFlushOffset, (int)(_sendBufferFlush.Size - _sendBufferFlushOffset)); + if (!Socket.SendAsync(_sendEventArg)) + process = ProcessSend(_sendEventArg); + } + catch (ObjectDisposedException) {} + } + } + + /// + /// Clear send/receive buffers + /// + private void ClearBuffers() + { + lock (_sendLock) + { + // Clear send buffers + _sendBufferMain.Clear(); + _sendBufferFlush.Clear(); + _sendBufferFlushOffset= 0; + + // Update statistic + BytesPending = 0; + BytesSending = 0; + } + } + + #endregion + + #region IO processing + + /// + /// This method is called whenever a receive or send operation is completed on a socket + /// + private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e) + { + // Determine which type of operation just completed and call the associated handler + switch (e.LastOperation) + { + case SocketAsyncOperation.Receive: + if (ProcessReceive(e)) + TryReceive(); + break; + case SocketAsyncOperation.Send: + if (ProcessSend(e)) + TrySend(); + break; + default: + throw new ArgumentException("The last operation completed on the socket was not a receive or send"); + } + + } + + /// + /// This method is invoked when an asynchronous receive operation completes + /// + private bool ProcessReceive(SocketAsyncEventArgs e) + { + if (!IsConnected) + return false; + + long size = e.BytesTransferred; + + // Received some data from the client + if (size > 0) + { + // Update statistic + BytesReceived += size; + Interlocked.Add(ref Server._bytesReceived, size); + + // Call the buffer received handler + OnReceived(_receiveBuffer.Data, 0, size); + + // If the receive buffer is full increase its size + if (_receiveBuffer.Capacity == size) + _receiveBuffer.Reserve(2 * size); + } + + _receiving = false; + + // Try to receive again if the session is valid + if (e.SocketError == SocketError.Success) + { + // If zero is returned from a read operation, the remote end has closed the connection + if (size > 0) + return true; + else + Disconnect(); + } + else + { + SendError(e.SocketError); + Disconnect(); + } + + return false; + } + + /// + /// This method is invoked when an asynchronous send operation completes + /// + private bool ProcessSend(SocketAsyncEventArgs e) + { + if (!IsConnected) + return false; + + long size = e.BytesTransferred; + + // Send some data to the client + if (size > 0) + { + // Update statistic + BytesSending -= size; + BytesSent += size; + Interlocked.Add(ref Server._bytesSent, size); + + // Increase the flush buffer offset + _sendBufferFlushOffset += size; + + // Successfully send the whole flush buffer + if (_sendBufferFlushOffset == _sendBufferFlush.Size) + { + // Clear the flush buffer + _sendBufferFlush.Clear(); + _sendBufferFlushOffset = 0; + } + + // Call the buffer sent handler + OnSent(size, BytesPending + BytesSending); + } + + // Try to send again if the session is valid + if (e.SocketError == SocketError.Success) + return true; + else + { + SendError(e.SocketError); + Disconnect(); + return false; + } + } + + #endregion + + #region Session handlers + + /// + /// Handle client connected notification + /// + protected virtual void OnConnected() {} + /// + /// Handle client disconnected notification + /// + protected virtual void OnDisconnected() {} + + /// + /// Handle buffer received notification + /// + /// Received buffer + /// Received buffer offset + /// Received buffer size + /// + /// Notification is called when another chunk of buffer was received from the client + /// + protected virtual void OnReceived(byte[] buffer, long offset, long size) {} + /// + /// Handle buffer sent notification + /// + /// Size of sent buffer + /// Size of pending buffer + /// + /// Notification is called when another chunk of buffer was sent to the client. + /// This handler could be used to send another buffer to the client for instance when the pending size is zero. + /// + protected virtual void OnSent(long sent, long pending) {} + + /// + /// Handle empty send buffer notification + /// + /// + /// Notification is called when the send buffer is empty and ready for a new data to send. + /// This handler could be used to send another buffer to the client. + /// + protected virtual void OnEmpty() {} + + /// + /// Handle error notification + /// + /// Socket error code + protected virtual void OnError(SocketError error) {} + + #endregion + + #region Error handling + + /// + /// Send error notification + /// + /// Socket error code + private void SendError(SocketError error) + { + // Skip disconnect errors + if ((error == SocketError.ConnectionAborted) || + (error == SocketError.ConnectionRefused) || + (error == SocketError.ConnectionReset) || + (error == SocketError.OperationAborted) || + (error == SocketError.Shutdown)) + return; + + OnError(error); + } + + #endregion + + #region IDisposable implementation + + /// + /// Disposed flag + /// + public bool IsDisposed { get; private set; } + + /// + /// Session socket disposed flag + /// + public bool IsSocketDisposed { get; private set; } = true; + + // Implement IDisposable. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposingManagedResources) + { + // The idea here is that Dispose(Boolean) knows whether it is + // being called to do explicit cleanup (the Boolean is true) + // versus being called due to a garbage collection (the Boolean + // is false). This distinction is useful because, when being + // disposed explicitly, the Dispose(Boolean) method can safely + // execute code using reference type fields that refer to other + // objects knowing for sure that these other objects have not been + // finalized or disposed of yet. When the Boolean is false, + // the Dispose(Boolean) method should not execute code that + // refer to reference type fields because those objects may + // have already been finalized." + + if (!IsDisposed) + { + if (disposingManagedResources) + { + // Dispose managed resources here... + Disconnect(); + } + + // Dispose unmanaged resources here... + + // Set large fields to null here... + + // Mark as disposed. + IsDisposed = true; + } + } + + // Use C# destructor syntax for finalization code. + ~TcpSession() + { + // Simply call Dispose(false). + Dispose(false); + } + + #endregion + } +} diff --git a/PythonModelAPI/PythonModelAPI.py b/PythonModelAPI/PythonModelAPI.py index 94c8e34..9ddd5ff 100644 --- a/PythonModelAPI/PythonModelAPI.py +++ b/PythonModelAPI/PythonModelAPI.py @@ -43,6 +43,9 @@ class Service: return 0 def load_model(self, path, name): + if name in self.models: + # Model already loaded, skip loading + return 0 self.models[name] = tf.keras.models.load_model(path) return 0 @@ -156,3 +159,4 @@ async def get_output_size(): raise HTTPException(status_code=500, detail=str(e)) + diff --git a/PythonModelAPI/PythonModelAPI.pyproj b/PythonModelAPI/PythonModelAPI.pyproj index e2d8c4e..329b3ee 100644 --- a/PythonModelAPI/PythonModelAPI.pyproj +++ b/PythonModelAPI/PythonModelAPI.pyproj @@ -12,6 +12,8 @@ PythonModelAPI PythonModelAPI D:\Inspectron\Hawkeye\code\VisionBuilder5\VisionBuilder.UI\Hawkeye.VisionBuilder\bin\Debug\Data\Models + True + true true @@ -22,6 +24,7 @@ false + diff --git a/PythonModelAPI/docker start.txt b/PythonModelAPI/docker start.txt new file mode 100644 index 0000000..972f46b --- /dev/null +++ b/PythonModelAPI/docker start.txt @@ -0,0 +1 @@ +docker run --gpus all -it -p 8000:8000 -v $(pwd):/app -w /app cv uvicorn --host 0.0.0.0 PythonModelAPI:app \ No newline at end of file diff --git a/PythonModelAPI/dockerfile b/PythonModelAPI/dockerfile new file mode 100644 index 0000000..2ef38d7 --- /dev/null +++ b/PythonModelAPI/dockerfile @@ -0,0 +1,26 @@ +# CUDA 11.2.2 + cuDNN 8 on Ubuntu 20.04 +FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + LANG=C.UTF-8 + +# Python 3.10 + pip +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3.10 python3-pip build-essential \ + && ln -sf /usr/bin/python3.10 /usr/bin/python \ + && ln -sf /usr/bin/pip3 /usr/bin/pip \ + && rm -rf /var/lib/apt/lists/* + +# Python deps +RUN pip install --no-cache-dir \ + fastapi \ + uvicorn \ + pillow \ + tensorflow==2.19.0 \ + python-multipart + +EXPOSE 8000 + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/PythonModelAPI/test.py b/PythonModelAPI/test.py new file mode 100644 index 0000000..0e4267b --- /dev/null +++ b/PythonModelAPI/test.py @@ -0,0 +1,5 @@ +import tensorflow as tf +import time +tf.keras.models.load_model("office4_tr.h5") +# sleep for 5 seconds to simulate long loading time +time.sleep(5) \ No newline at end of file diff --git a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs index fe85f2a..0b71d97 100644 --- a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs +++ b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs @@ -40,9 +40,8 @@ public class PluginLoader:IVisionBuilderModule if (_settings.Plugins.All(p => p.Name != pluginName)) { - Log.Warning("Discovered new plugin:{PluginName}. Adding to settings and skipping.", pluginName); - _settings.Plugins.Add(new PluginSettings.PluginFlag { Name = pluginName, Enabled = false }); - continue; + Log.Warning("Discovered new plugin:{PluginName}. Adding to settings.", pluginName); + _settings.AddPlugin(new PluginSettings.PluginFlag { Name = pluginName, Enabled = false }); } if (_settings.Plugins.Where(p => p.Name == pluginName).All(x=>!x.Enabled)) @@ -82,7 +81,7 @@ public class PluginLoader:IVisionBuilderModule if (type.IsAssignableTo(typeof(IPlugin)) && !type.IsAbstract) { Plugins.Add((IPlugin)Activator.CreateInstance(type)); - Log.Information("Loaded plugin: {PluginName}", type.Name); + Log.Information("Loaded plugin: {PluginName}", type.FullName); } diff --git a/VisionBuilder.UI.Common/Plugins/PluginSettings.cs b/VisionBuilder.UI.Common/Plugins/PluginSettings.cs index 33f1eed..7d2c9cf 100644 --- a/VisionBuilder.UI.Common/Plugins/PluginSettings.cs +++ b/VisionBuilder.UI.Common/Plugins/PluginSettings.cs @@ -7,6 +7,8 @@ namespace VisionBuilder.UI.Common.Plugins; public class PluginSettings:ISettings { + private List _plugins = new List(); + public class PluginFlag { public bool Enabled { get; set; } @@ -18,7 +20,27 @@ public class PluginSettings:ISettings } } - public List Plugins { get; set; } = new List(); + public void AddPlugin(PluginFlag plugin) + { + if (_plugins.All(p => p.Name != plugin.Name)) + { + _plugins.Add(plugin); + } + } + + public List Plugins + { + get => _plugins; + set + { + // add unique only + foreach (var plugin in value) + { + AddPlugin(plugin); + } + } + } + public void RegisterSettings(InspectronSettings settings) { settings.RegisterSimple(this, () => this.Plugins,"Plugins",nameof(Plugins)); diff --git a/VisionBuilder.UI.Common/ViewModel/StatisticsVM.cs b/VisionBuilder.UI.Common/ViewModel/StatisticsVM.cs index 381579a..c2802b1 100644 --- a/VisionBuilder.UI.Common/ViewModel/StatisticsVM.cs +++ b/VisionBuilder.UI.Common/ViewModel/StatisticsVM.cs @@ -31,7 +31,7 @@ public partial class StatisticsVM: ObservableObject,IEventHandler $"{e.Key}: {e.Value}")); - ProcessingTime = (int)@event.AnalysisTime.TotalMilliseconds + "ms"; + } public void Handle(SessionStartedEvent @event) diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs index f260cad..715681b 100644 --- a/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs +++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs @@ -34,7 +34,7 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe List recipes = new List(); foreach (var file in files) { - var workFlow = WorkflowList.LoadFromFile(file); + var workFlow = WorkflowList.LoadJSONFromFile(file); var recipe = new RecipeData { @@ -82,7 +82,7 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe throw new InvalidOperationException("Recognition is already running."); } _loadingService.StartLoading($"Starting recipe {_currentRecipe.RecipeName} on {_recognitionConfiguration.CameraName}..."); - _workflow = WorkflowList.LoadFromFile("..\\Data\\Recipes\\" + _currentRecipe.RecipeName + ".hrcp"); + _workflow = WorkflowList.LoadJSONFromFile("..\\Data\\Recipes\\" + _currentRecipe.RecipeName + ".jhrcp"); _startTime = DateTime.Now; IsRunning = true; _cts = new CancellationTokenSource(); diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/OpenCVCanvas.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/OpenCVCanvas.cs index 30e1fb0..d4d12a2 100644 --- a/VisionBuilder.UI.Recipes.HawkeyeRecipe/OpenCVCanvas.cs +++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/OpenCVCanvas.cs @@ -48,16 +48,16 @@ public class OpenCVCanvas:ICanvas public void DrawCross(Vector2 location) { - throw new NotImplementedException(); + } public void DrawNode(Vector2 location) { - throw new NotImplementedException(); + } public void DrawArrow(Vector2 location, Vector2 direction) { - throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/WorkflowRecipeHelper.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/WorkflowRecipeHelper.cs index 629d853..77e97bc 100644 --- a/VisionBuilder.UI.Recipes.HawkeyeRecipe/WorkflowRecipeHelper.cs +++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/WorkflowRecipeHelper.cs @@ -10,7 +10,7 @@ public static class WorkflowRecipeHelper public static List ListRecipeFiles(string nameFilter) { Directory.CreateDirectory(RECIPES_DIR); - var recipes = Directory.GetFiles(RECIPES_DIR, nameFilter+".hrcp"); + var recipes = Directory.GetFiles(RECIPES_DIR, nameFilter+".jhrcp"); return recipes.ToList(); } diff --git a/VisionBuilder.UI.Windows.Test/Form1.cs b/VisionBuilder.UI.Windows.Test/Form1.cs index afac4b7..42a6cfa 100644 --- a/VisionBuilder.UI.Windows.Test/Form1.cs +++ b/VisionBuilder.UI.Windows.Test/Form1.cs @@ -38,6 +38,7 @@ namespace VisionBuilder.UI.Windows.Test private void Form1_Load(object sender, EventArgs e) { _mainWindowVm.OnProgramStarted(); + BringToFront(); } private void btnExit_Click(object sender, EventArgs e) diff --git a/VisionBuilder.UI.Windows.Test/Program.cs b/VisionBuilder.UI.Windows.Test/Program.cs index eb69786..6b6a510 100644 --- a/VisionBuilder.UI.Windows.Test/Program.cs +++ b/VisionBuilder.UI.Windows.Test/Program.cs @@ -38,7 +38,7 @@ namespace VisionBuilder.UI.Windows.Test // In your application startup code MaterialSkin.MaterialSkinManager.ConfigureForInspectron(); - InspectronSettings settings = new InspectronSettings("..\\Config"); + InspectronSettings settings = new InspectronSettings("..\\Data\\Config"); var mainKernel = Common.VisionBuilder.CreateMainKernel(settings); mainKernel diff --git a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs index 056e8fe..4f40b87 100644 --- a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs +++ b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs @@ -22,7 +22,7 @@ namespace VisionBuilder.UI.Windows.Dialogs InitializeComponent(); ReloadRecipes(); - if (!_recipeCreationTool.Enabled) + if (_recipeCreationTool.Enabled) { btnCreateNewRecipe.Visible = true; } diff --git a/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs index 023ba2e..f8b6194 100644 --- a/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs +++ b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs @@ -6,9 +6,16 @@ namespace VisionBuilder.UI.Windows.Settings; public class WindowsRecipeSelectionDialogService: IRecipeSelectionDialogService { + private readonly IRecipeCreationTool _recipeCreationTool; + + public WindowsRecipeSelectionDialogService(IRecipeCreationTool recipeCreationTool) + { + _recipeCreationTool = recipeCreationTool; + } + public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm) { - RecipeSelectionDialog dialog = new RecipeSelectionDialog(recipeSelectionVm); + RecipeSelectionDialog dialog = new RecipeSelectionDialog(recipeSelectionVm, _recipeCreationTool); if(dialog.ShowDialog() == DialogResult.OK) { return true; diff --git a/VisionBuilder.UI.sln b/VisionBuilder.UI.sln index 3f08cb0..fba4fc1 100644 --- a/VisionBuilder.UI.sln +++ b/VisionBuilder.UI.sln @@ -85,6 +85,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Python", "Python", "{9FA47F EndProject Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "PythonModelAPI", "PythonModelAPI\PythonModelAPI.pyproj", "{B4949493-C2CD-4786-860D-DAACD39A662D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PralinenPLC", "Plugins\Pralinen\PralinenPLC\PralinenPLC.csproj", "{19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -217,6 +219,10 @@ Global {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|Any CPU.Build.0 = Release|Any CPU {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -249,6 +255,7 @@ Global {C55BE2DA-C60B-491C-8668-9517C7F6FF2F} = {D05689E3-04C6-4E3B-ACA7-3F4507CED4CC} {44D1BA17-FB52-40A2-9D99-E49DA56C10C2} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1} {B4949493-C2CD-4786-860D-DAACD39A662D} = {9FA47F4A-8F44-4F55-B6F7-99C962E9F18F} + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A} = {583A77DF-A293-4F3E-AB96-7310BC495822} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}