diff --git a/DH.Commons/Base/VisualLocalization.cs b/DH.Commons/Base/VisualLocalization.cs new file mode 100644 index 0000000..c6be68c --- /dev/null +++ b/DH.Commons/Base/VisualLocalization.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace DH.Commons.Base +{ + [Serializable] + public class VisualLocalization + { + // 必须包含公共无参构造函数 + public VisualLocalization() { } + + public string CameraName { get; set; } + public string ModelPath { get; set; } + public string ImgPath { get; set; } + public string Threshold { get; set; } + public string Direction { get; set; } + public string Speed { get; set; } + + // 保存到文件 + public void SaveToFile(string filePath) + { + try + { + var options = new JsonSerializerOptions + { + WriteIndented = true, // 美化格式 + IgnoreNullValues = true // 忽略空值 + }; + + string json = JsonSerializer.Serialize(this, options); + File.WriteAllText(filePath, json); + } + catch (Exception ex) + { + throw new InvalidOperationException($"保存失败: {ex.Message}"); + } + } + + // 从文件加载 + public static VisualLocalization LoadFromFile(string filePath) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException("文件不存在", filePath); + + try + { + string json = File.ReadAllText(filePath); + return JsonSerializer.Deserialize(json); + } + catch (JsonException ex) + { + throw new InvalidOperationException($"JSON解析错误: {ex.Message}"); + } + catch (IOException ex) + { + throw new InvalidOperationException($"文件读取失败: {ex.Message}"); + } + } + } +} diff --git a/DH.Commons/DH.Commons.csproj b/DH.Commons/DH.Commons.csproj index 0954b95..05fc12a 100644 --- a/DH.Commons/DH.Commons.csproj +++ b/DH.Commons/DH.Commons.csproj @@ -14,6 +14,7 @@ + diff --git a/DH.Devices.Camera/Do3ThinkCamera.cs b/DH.Devices.Camera/Do3ThinkCamera.cs index 7e3d93e..df225db 100644 --- a/DH.Devices.Camera/Do3ThinkCamera.cs +++ b/DH.Devices.Camera/Do3ThinkCamera.cs @@ -557,6 +557,12 @@ namespace DH.Devices.Camera } + public void Snapshot() + { + DVPCamera.dvpTriggerFire(m_handle); + + } + public void LogAsync(LogMsg msg) { msg.MsgSource = CameraName; diff --git a/DH.Devices.PLC/XinJEPLCTcpNet.cs b/DH.Devices.PLC/XinJEPLCTcpNet.cs index 5576a0b..a39b290 100644 --- a/DH.Devices.PLC/XinJEPLCTcpNet.cs +++ b/DH.Devices.PLC/XinJEPLCTcpNet.cs @@ -817,6 +817,30 @@ namespace DH.Devices.PLC WriteBool(pLCItem.Address, b); } + /// + /// 转盘方向 + /// + /// + public void TurnDirection(bool b) + { + PLCItem? pLCItem = PLCItemList.FirstOrDefault(u => u.Name == "转盘方向"); + if (pLCItem == null) + return; + WriteBool(pLCItem.Address, b); + } + + /// + /// 转盘速度 + /// + /// + public void TurnSpeed(int speed) + { + PLCItem? pLCItem = PLCItemList.FirstOrDefault(u => u.Name == "转盘速度"); + if (pLCItem == null) + return; + WriteUInt16(pLCItem.Address, (ushort)speed); + } + /// /// 转盘启停 /// @@ -1028,6 +1052,16 @@ namespace DH.Devices.PLC } + public int ReadVisionPos() + { + PLCItem? pLCItem = PLCItemList.FirstOrDefault(u => u.Name == "定位完成脉冲值"); + if (pLCItem == null) + return 0; + int value = ReadInt32(pLCItem.Address); + Thread.Sleep(10); + return Math.Abs(value); + } + public void LogAsync(LogMsg msg) { msg.MsgSource = "PLC"; diff --git a/DHSoftware/DHSoftware.csproj b/DHSoftware/DHSoftware.csproj index 5b047a0..a272571 100644 --- a/DHSoftware/DHSoftware.csproj +++ b/DHSoftware/DHSoftware.csproj @@ -12,6 +12,9 @@ WinExe + + + @@ -27,6 +30,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/DHSoftware/MainWindow.Designer.cs b/DHSoftware/MainWindow.Designer.cs index 5e66a11..a92a4ab 100644 --- a/DHSoftware/MainWindow.Designer.cs +++ b/DHSoftware/MainWindow.Designer.cs @@ -36,6 +36,7 @@ AntdUI.SegmentedItem segmentedItem3 = new AntdUI.SegmentedItem(); AntdUI.SegmentedItem segmentedItem4 = new AntdUI.SegmentedItem(); AntdUI.SegmentedItem segmentedItem5 = new AntdUI.SegmentedItem(); + AntdUI.SegmentedItem segmentedItem6 = new AntdUI.SegmentedItem(); titlebar = new AntdUI.PageHeader(); lbName = new AntdUI.Label(); tabsStas = new AntdUI.Tabs(); @@ -64,6 +65,7 @@ label7 = new Label(); panel2 = new AntdUI.Panel(); panel1 = new AntdUI.Panel(); + segmented2 = new AntdUI.Segmented(); panel3 = new AntdUI.Panel(); textBoxBatchNO = new AntdUI.Input(); btnCreateBatchNO = new AntdUI.Button(); @@ -456,6 +458,7 @@ // panel1 // panel1.Back = Color.FromArgb(46, 108, 227); + panel1.Controls.Add(segmented2); panel1.Controls.Add(panel3); panel1.Controls.Add(segmented1); panel1.Dock = DockStyle.Top; @@ -467,6 +470,33 @@ panel1.TabIndex = 0; panel1.Text = "panel1"; // + // segmented2 + // + segmented2.BackActive = Color.FromArgb(100, 255, 87, 34); + segmented2.BackColor = Color.Transparent; + segmented2.Dock = DockStyle.Left; + segmented2.Font = new Font("Microsoft YaHei UI", 9F); + segmented2.ForeColor = Color.White; + segmented2.Full = true; + segmentedItem1.Badge = null; + segmentedItem1.BadgeAlign = AntdUI.TAlignFrom.TR; + segmentedItem1.BadgeBack = null; + segmentedItem1.BadgeMode = false; + segmentedItem1.BadgeOffsetX = 0; + segmentedItem1.BadgeOffsetY = 0; + segmentedItem1.BadgeSize = 0.6F; + segmentedItem1.BadgeSvg = null; + segmentedItem1.IconActiveSvg = resources.GetString("segmentedItem1.IconActiveSvg"); + segmentedItem1.IconSvg = resources.GetString("segmentedItem1.IconSvg"); + segmentedItem1.Text = "定位"; + segmented2.Items.Add(segmentedItem1); + segmented2.Location = new Point(439, 0); + segmented2.Name = "segmented2"; + segmented2.Size = new Size(96, 83); + segmented2.TabIndex = 17; + segmented2.Text = "segmented2"; + segmented2.SelectIndexChanged += segmented2_SelectIndexChanged; + // // panel3 // panel3.Back = Color.FromArgb(46, 108, 227); @@ -477,13 +507,13 @@ panel3.Controls.Add(btnLoadProject); panel3.Controls.Add(sltProjects); panel3.Dock = DockStyle.Right; - panel3.Location = new Point(553, 0); + panel3.Location = new Point(554, 0); panel3.Name = "panel3"; panel3.Padding = new Padding(30); panel3.Radius = 0; panel3.ShadowOpacity = 0F; panel3.ShadowOpacityHover = 0F; - panel3.Size = new Size(471, 83); + panel3.Size = new Size(470, 83); panel3.TabIndex = 16; panel3.Text = "panel3"; // @@ -553,17 +583,6 @@ segmented1.Font = new Font("Microsoft YaHei UI", 9F); segmented1.ForeColor = Color.White; segmented1.Full = true; - segmentedItem1.Badge = null; - segmentedItem1.BadgeAlign = AntdUI.TAlignFrom.TR; - segmentedItem1.BadgeBack = null; - segmentedItem1.BadgeMode = false; - segmentedItem1.BadgeOffsetX = 0; - segmentedItem1.BadgeOffsetY = 0; - segmentedItem1.BadgeSize = 0.6F; - segmentedItem1.BadgeSvg = null; - segmentedItem1.IconActiveSvg = resources.GetString("segmentedItem1.IconActiveSvg"); - segmentedItem1.IconSvg = resources.GetString("segmentedItem1.IconSvg"); - segmentedItem1.Text = "启动"; segmentedItem2.Badge = null; segmentedItem2.BadgeAlign = AntdUI.TAlignFrom.TR; segmentedItem2.BadgeBack = null; @@ -574,7 +593,7 @@ segmentedItem2.BadgeSvg = null; segmentedItem2.IconActiveSvg = resources.GetString("segmentedItem2.IconActiveSvg"); segmentedItem2.IconSvg = resources.GetString("segmentedItem2.IconSvg"); - segmentedItem2.Text = "停止"; + segmentedItem2.Text = "启动"; segmentedItem3.Badge = null; segmentedItem3.BadgeAlign = AntdUI.TAlignFrom.TR; segmentedItem3.BadgeBack = null; @@ -585,7 +604,7 @@ segmentedItem3.BadgeSvg = null; segmentedItem3.IconActiveSvg = resources.GetString("segmentedItem3.IconActiveSvg"); segmentedItem3.IconSvg = resources.GetString("segmentedItem3.IconSvg"); - segmentedItem3.Text = "复位"; + segmentedItem3.Text = "停止"; segmentedItem4.Badge = null; segmentedItem4.BadgeAlign = AntdUI.TAlignFrom.TR; segmentedItem4.BadgeBack = null; @@ -596,7 +615,7 @@ segmentedItem4.BadgeSvg = null; segmentedItem4.IconActiveSvg = resources.GetString("segmentedItem4.IconActiveSvg"); segmentedItem4.IconSvg = resources.GetString("segmentedItem4.IconSvg"); - segmentedItem4.Text = "登录"; + segmentedItem4.Text = "复位"; segmentedItem5.Badge = null; segmentedItem5.BadgeAlign = AntdUI.TAlignFrom.TR; segmentedItem5.BadgeBack = null; @@ -607,16 +626,27 @@ segmentedItem5.BadgeSvg = null; segmentedItem5.IconActiveSvg = resources.GetString("segmentedItem5.IconActiveSvg"); segmentedItem5.IconSvg = resources.GetString("segmentedItem5.IconSvg"); - segmentedItem5.ID = "sys-setbutton"; - segmentedItem5.Text = "设置"; - segmented1.Items.Add(segmentedItem1); + segmentedItem5.Text = "登录"; + segmentedItem6.Badge = null; + segmentedItem6.BadgeAlign = AntdUI.TAlignFrom.TR; + segmentedItem6.BadgeBack = null; + segmentedItem6.BadgeMode = false; + segmentedItem6.BadgeOffsetX = 0; + segmentedItem6.BadgeOffsetY = 0; + segmentedItem6.BadgeSize = 0.6F; + segmentedItem6.BadgeSvg = null; + segmentedItem6.IconActiveSvg = resources.GetString("segmentedItem6.IconActiveSvg"); + segmentedItem6.IconSvg = resources.GetString("segmentedItem6.IconSvg"); + segmentedItem6.ID = "sys-setbutton"; + segmentedItem6.Text = "设置"; segmented1.Items.Add(segmentedItem2); segmented1.Items.Add(segmentedItem3); segmented1.Items.Add(segmentedItem4); segmented1.Items.Add(segmentedItem5); + segmented1.Items.Add(segmentedItem6); segmented1.Location = new Point(0, 0); segmented1.Name = "segmented1"; - segmented1.Size = new Size(491, 83); + segmented1.Size = new Size(439, 83); segmented1.TabIndex = 1; segmented1.Text = "segmented1"; segmented1.SelectIndexChanged += segmented1_SelectIndexChanged; @@ -752,5 +782,6 @@ private TableLayoutPanel tableLayoutPanel3; private AntdUI.Button btnCreateBatchNO; private AntdUI.Input textBoxBatchNO; + private AntdUI.Segmented segmented2; } } \ No newline at end of file diff --git a/DHSoftware/MainWindow.cs b/DHSoftware/MainWindow.cs index 211ade4..ae6e3f9 100644 --- a/DHSoftware/MainWindow.cs +++ b/DHSoftware/MainWindow.cs @@ -222,7 +222,7 @@ namespace DHSoftware continue; } - if (control is AntdUI.Segmented segmented) + if (control is AntdUI.Segmented segmented && control.Name == "segmented1") { HandleSegmentedItems(segmented, list); } @@ -1160,6 +1160,27 @@ namespace DHSoftware } segmented1.SelectIndex = -1; } + + private void segmented2_SelectIndexChanged(object sender, IntEventArgs e) + { + + int selectedIndex = segmented2.SelectIndex; + + + switch (selectedIndex) + { + case 0: + HandleVisualLocalizationButton(); + break; + + default: + break; + } + segmented2.SelectIndex = -1; + } + + + public string BatchNO { get; set; } public bool CurrentMachine = false; public volatile int ProductNum_Total = 0; @@ -1176,7 +1197,7 @@ namespace DHSoftware private void PrepareBatchNO() { BatchNO = string.IsNullOrEmpty(BatchNO) ? SystemModel.CurrentScheme + "-" + DateTime.Now.ToString("yyyyMMddHHmmss") : BatchNO; - // DataSavePath = string.IsNullOrEmpty(DataSavePath) ? Path.Combine(X018PLCConfig.ImgDirectory, DateTime.Now.ToString("yyyyMMdd"), BatchNO) : DataSavePath; + // DataSavePath = string.IsNullOrEmpty(DataSavePath) ? Path.Combine(X018PLCConfig.ImgDirectory, DateTime.Now.ToString("yyyyMMdd"), BatchNO) : DataSavePath; } @@ -1202,7 +1223,7 @@ namespace DHSoftware lblStartTime.Text = ProcessstartTime.ToString("yyyy-MM-dd HH:mm:ss"); //计数清零 PieceCount = 0; - + //CameraSummaries.Clear(); if (PLC?.Enable == true) @@ -1794,10 +1815,15 @@ namespace DHSoftware LoginWindow.Instance.parentForm = this; LoginWindow.Instance.Show(); } - + private void HandleVisualLocalizationButton() + { + VisualLocalizationWindow.Instance.Show(); + } private void btnCreateBatchNO_Click(object sender, EventArgs e) { textBoxBatchNO.Text = SystemModel.CurrentScheme + "-" + DateTime.Now.ToString("yyyyMMddHHmmss"); } + + } } \ No newline at end of file diff --git a/DHSoftware/MainWindow.resx b/DHSoftware/MainWindow.resx index 1a56e4c..77eed50 100644 --- a/DHSoftware/MainWindow.resx +++ b/DHSoftware/MainWindow.resx @@ -118,33 +118,39 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M731.818667 500.280889L386.844444 239.729778a14.677333 14.677333 0 0 0-23.495111 11.719111v521.159111a14.677333 14.677333 0 0 0 23.495111 11.662222l344.860445-260.608a14.677333 14.677333 0 0 0 0.113778-23.381333z" fill="#FFFFFF"/><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512z m0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512zm0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/><path d="M199.555 528.063h293.116v278.095L710.638 300.43 199.555 528.063zm148.53-39.044 252.043 112.252L531.715 616.92V489.019H383.185z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M731.818667 500.280889L386.844444 239.729778a14.677333 14.677333 0 0 0-23.495111 11.719111v521.159111a14.677333 14.677333 0 0 0 23.495111 11.662222l344.860445-260.608a14.677333 14.677333 0 0 0 0.113778-23.381333z" fill="#FFFFFF"/><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512z m0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512zm0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/><path d="M199.555 528.063h293.116v278.095L710.638 300.43 199.555 528.063zm148.53-39.044 252.043 112.252L531.715 616.92V489.019H383.185z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M365.014704 657.815846H657.084939V365.74561H365.014704V657.815846zm584.140471-146.035118c0-240.906781-197.125482-438.105353-438.105353-438.105353-240.979872 0-438.105353 197.198572-438.105354 438.105353 0 240.979872 197.125482 438.178444 438.105354 438.178444 240.979872 0 438.105353-197.198572 438.105353-438.178444zM511.634547 0.730906c281.399001 0 511.634547 230.235546 511.634547 511.634547s-230.235546 511.634547-511.634547 511.634547-511.634547-230.235546-511.634547-511.634547 230.235546-511.634547 511.634547-511.634547z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M731.818667 500.280889L386.844444 239.729778a14.677333 14.677333 0 0 0-23.495111 11.719111v521.159111a14.677333 14.677333 0 0 0 23.495111 11.662222l344.860445-260.608a14.677333 14.677333 0 0 0 0.113778-23.381333z" fill="#FFFFFF"/><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512z m0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M365.014704 657.815846H657.084939V365.74561H365.014704V657.815846zm584.140471-146.035118c0-240.906781-197.125482-438.105353-438.105353-438.105353-240.979872 0-438.105353 197.198572-438.105354 438.105353 0 240.979872 197.125482 438.178444 438.105354 438.178444 240.979872 0 438.105353-197.198572 438.105353-438.178444zM511.634547 0.730906c281.399001 0 511.634547 230.235546 511.634547 511.634547s-230.235546 511.634547-511.634547 511.634547-511.634547-230.235546-511.634547-511.634547 230.235546-511.634547 511.634547-511.634547z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M731.818667 500.280889L386.844444 239.729778a14.677333 14.677333 0 0 0-23.495111 11.719111v521.159111a14.677333 14.677333 0 0 0 23.495111 11.662222l344.860445-260.608a14.677333 14.677333 0 0 0 0.113778-23.381333z" fill="#FFFFFF"/><path d="M512 1024a512 512 0 1 1 512-512 512.568889 512.568889 0 0 1-512 512z m0-946.915556A434.915556 434.915556 0 1 0 946.915556 512 435.427556 435.427556 0 0 0 512 77.084444z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M527.36 351.744V292.864L410.624 380.416 527.36 468.48V410.624c72.192 8.192 124.416 73.216 116.224 145.408-8.192 72.192-73.216 124.416-145.408 116.224-66.56-7.168-117.248-64-117.248-131.072-0.512-5.12-0.512-9.728 0-14.848H323.584c-0.512 5.12-0.512 9.728 0 14.848 0 104.96 85.504 189.952 190.464 189.952s189.952-85.504 189.952-190.464c-0.512-99.328-77.312-181.76-176.64-188.928z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M365.014704 657.815846H657.084939V365.74561H365.014704V657.815846zm584.140471-146.035118c0-240.906781-197.125482-438.105353-438.105353-438.105353-240.979872 0-438.105353 197.198572-438.105354 438.105353 0 240.979872 197.125482 438.178444 438.105354 438.178444 240.979872 0 438.105353-197.198572 438.105353-438.178444zM511.634547 0.730906c281.399001 0 511.634547 230.235546 511.634547 511.634547s-230.235546 511.634547-511.634547 511.634547-511.634547-230.235546-511.634547-511.634547 230.235546-511.634547 511.634547-511.634547z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M527.36 351.744V292.864L410.624 380.416 527.36 468.48V410.624c72.192 8.192 124.416 73.216 116.224 145.408-8.192 72.192-73.216 124.416-145.408 116.224-66.56-7.168-117.248-64-117.248-131.072-0.512-5.12-0.512-9.728 0-14.848H323.584c-0.512 5.12-0.512 9.728 0 14.848 0 104.96 85.504 189.952 190.464 189.952s189.952-85.504 189.952-190.464c-0.512-99.328-77.312-181.76-176.64-188.928z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M365.014704 657.815846H657.084939V365.74561H365.014704V657.815846zm584.140471-146.035118c0-240.906781-197.125482-438.105353-438.105353-438.105353-240.979872 0-438.105353 197.198572-438.105354 438.105353 0 240.979872 197.125482 438.178444 438.105354 438.178444 240.979872 0 438.105353-197.198572 438.105353-438.178444zM511.634547 0.730906c281.399001 0 511.634547 230.235546 511.634547 511.634547s-230.235546 511.634547-511.634547 511.634547-511.634547-230.235546-511.634547-511.634547 230.235546-511.634547 511.634547-511.634547z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path transform="scale(0.8) translate(128,128)" d="M960.853333 903.816533a463.633067 463.633067 0 0 0-11.264-39.185066c-1.536-4.539733-3.413333-8.942933-5.051733-13.448534a484.078933 484.078933 0 0 0-9.557333-24.4736c-2.2528-5.188267-4.881067-10.274133-7.338667-15.394133-3.413333-7.099733-6.8608-14.165333-10.6496-21.0944-2.901333-5.3248-6.075733-10.513067-9.181867-15.701333-2.423467-4.061867-4.573867-8.226133-7.133866-12.219734-1.604267-2.4576-3.413333-4.778667-5.0176-7.202133-1.501867-2.218667-2.730667-4.608-4.266667-6.792533-0.4096-0.6144-1.058133-0.887467-1.501867-1.4336a461.482667 461.482667 0 0 0-90.385066-96.768c-13.5168-10.786133-27.7504-20.48-42.257067-29.5936-0.477867-0.341333-0.7168-0.8192-1.194667-1.1264-3.6864-2.286933-7.509333-4.3008-11.264-6.485334-4.266667-2.491733-8.4992-5.051733-12.868266-7.441066-6.826667-3.6864-13.789867-7.099733-20.753067-10.478934-3.618133-1.7408-7.202133-3.618133-10.8544-5.290666a449.194667 449.194667 0 0 0-31.607467-12.731734c-0.7168-0.273067-1.365333-0.6144-2.082133-0.8192-3.140267-1.1264-6.417067-1.911467-9.557333-2.935466-4.164267-1.399467-8.328533-2.833067-12.561067-4.096a259.9936 259.9936 0 0 0 129.194667-225.450667 260.061867 260.061867 0 0 0-76.629334-185.002667 259.9936 259.9936 0 0 0-185.002666-76.629333H512h-0.034133a259.857067 259.857067 0 0 0-185.002667 76.629333 259.925333 259.925333 0 0 0-76.629333 185.002667 259.584 259.584 0 0 0 76.629333 185.002667c15.906133 15.940267 33.655467 29.2864 52.565333 40.448-4.266667 1.262933-8.430933 2.730667-12.663466 4.096-3.140267 1.058133-6.3488 1.8432-9.489067 2.935466-0.7168 0.238933-1.365333 0.580267-2.048 0.8192-10.683733 3.822933-21.265067 8.0896-31.675733 12.765867-3.584 1.604267-7.0656 3.4816-10.615467 5.154133-7.099733 3.413333-14.165333 6.826667-21.0944 10.615467-4.266667 2.321067-8.3968 4.8128-12.561067 7.2704-3.822933 2.218667-7.748267 4.266667-11.502933 6.621867-0.512 0.3072-0.750933 0.8192-1.2288 1.160533-14.506667 9.147733-28.706133 18.807467-42.222933 29.559467a459.6736 459.6736 0 0 0-90.385067 96.768c-0.443733 0.546133-1.092267 0.8192-1.501867 1.4336-1.536 2.184533-2.7648 4.573867-4.266666 6.792533-1.604267 2.423467-3.447467 4.744533-5.0176 7.202133-2.56 3.9936-4.7104 8.157867-7.133867 12.219734-3.106133 5.188267-6.280533 10.376533-9.181867 15.701333-3.7888 6.929067-7.202133 13.994667-10.6496 21.0944-2.4576 5.12-5.051733 10.205867-7.338666 15.394133-3.515733 8.021333-6.519467 16.247467-9.557334 24.4736-1.672533 4.5056-3.549867 8.9088-5.051733 13.448534-4.3008 12.868267-8.0896 25.941333-11.264 39.185066-3.072 12.970667 2.594133 25.770667 13.073067 32.802134a31.3344 31.3344 0 0 0 9.966933 4.608 30.9248 30.9248 0 0 0 34.030933-15.2576 30.446933 30.446933 0 0 0 3.345067-7.7824c2.833067-11.844267 6.178133-23.483733 10.0352-34.9184 0.6144-1.8432 1.399467-3.549867 2.013867-5.358934 3.447467-9.762133 7.133867-19.456 11.332266-28.945066 0.512-1.160533 1.1264-2.2528 1.6384-3.447467 4.7104-10.308267 9.728-20.48 15.291734-30.344533l0.068266-0.1024a402.773333 402.773333 0 0 1 19.694934-31.4368l0.136533-0.375467a397.4144 397.4144 0 0 1 116.599467-111.2064c0.136533-0.1024 0.3072-0.068267 0.443733-0.170667a397.824 397.824 0 0 1 94.993067-42.973866c2.7648-0.8192 5.495467-1.7408 8.2944-2.491734 5.7344-1.604267 11.5712-3.003733 17.373866-4.334933a367.8208 367.8208 0 0 1 47.342934-7.953067c3.8912-0.443733 7.7824-0.9216 11.6736-1.2288 10.410667-0.785067 20.8896-1.3312 31.505066-1.3312s21.060267 0.546133 31.505067 1.3312c3.8912 0.3072 7.816533 0.785067 11.707733 1.2288a361.3696 361.3696 0 0 1 47.240534 7.953067c5.870933 1.3312 11.707733 2.730667 17.5104 4.334933 2.696533 0.750933 5.358933 1.6384 8.021333 2.4576 33.348267 10.103467 65.365333 24.405333 95.197867 43.008 0.136533 0.1024 0.3072 0.068267 0.443733 0.170667a396.151467 396.151467 0 0 1 116.599467 111.2064c0.1024 0.136533 0.1024 0.273067 0.170666 0.375467 13.687467 19.7632 25.3952 40.5504 35.191467 62.1568l1.467733 3.037866c4.3008 9.659733 8.055467 19.592533 11.605334 29.5936 0.546133 1.604267 1.2288 3.106133 1.774933 4.7104 3.822933 11.4688 7.236267 23.176533 10.0352 35.0208a31.061333 31.061333 0 0 0 60.450133-14.336zm-249.275733-560.2304A199.850667 199.850667 0 0 1 512 543.197867a199.850667 199.850667 0 0 1-199.5776-199.611734A199.816533 199.816533 0 0 1 512 144.008533a199.816533 199.816533 0 0 1 199.5776 199.5776z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M527.36 351.744V292.864L410.624 380.416 527.36 468.48V410.624c72.192 8.192 124.416 73.216 116.224 145.408-8.192 72.192-73.216 124.416-145.408 116.224-66.56-7.168-117.248-64-117.248-131.072-0.512-5.12-0.512-9.728 0-14.848H323.584c-0.512 5.12-0.512 9.728 0 14.848 0 104.96 85.504 189.952 190.464 189.952s189.952-85.504 189.952-190.464c-0.512-99.328-77.312-181.76-176.64-188.928z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path transform="scale(0.8) translate(128,128)" d="M960.853333 903.816533a463.633067 463.633067 0 0 0-11.264-39.185066c-1.536-4.539733-3.413333-8.942933-5.051733-13.448534a484.078933 484.078933 0 0 0-9.557333-24.4736c-2.2528-5.188267-4.881067-10.274133-7.338667-15.394133-3.413333-7.099733-6.8608-14.165333-10.6496-21.0944-2.901333-5.3248-6.075733-10.513067-9.181867-15.701333-2.423467-4.061867-4.573867-8.226133-7.133866-12.219734-1.604267-2.4576-3.413333-4.778667-5.0176-7.202133-1.501867-2.218667-2.730667-4.608-4.266667-6.792533-0.4096-0.6144-1.058133-0.887467-1.501867-1.4336a461.482667 461.482667 0 0 0-90.385066-96.768c-13.5168-10.786133-27.7504-20.48-42.257067-29.5936-0.477867-0.341333-0.7168-0.8192-1.194667-1.1264-3.6864-2.286933-7.509333-4.3008-11.264-6.485334-4.266667-2.491733-8.4992-5.051733-12.868266-7.441066-6.826667-3.6864-13.789867-7.099733-20.753067-10.478934-3.618133-1.7408-7.202133-3.618133-10.8544-5.290666a449.194667 449.194667 0 0 0-31.607467-12.731734c-0.7168-0.273067-1.365333-0.6144-2.082133-0.8192-3.140267-1.1264-6.417067-1.911467-9.557333-2.935466-4.164267-1.399467-8.328533-2.833067-12.561067-4.096a259.9936 259.9936 0 0 0 129.194667-225.450667 260.061867 260.061867 0 0 0-76.629334-185.002667 259.9936 259.9936 0 0 0-185.002666-76.629333H512h-0.034133a259.857067 259.857067 0 0 0-185.002667 76.629333 259.925333 259.925333 0 0 0-76.629333 185.002667 259.584 259.584 0 0 0 76.629333 185.002667c15.906133 15.940267 33.655467 29.2864 52.565333 40.448-4.266667 1.262933-8.430933 2.730667-12.663466 4.096-3.140267 1.058133-6.3488 1.8432-9.489067 2.935466-0.7168 0.238933-1.365333 0.580267-2.048 0.8192-10.683733 3.822933-21.265067 8.0896-31.675733 12.765867-3.584 1.604267-7.0656 3.4816-10.615467 5.154133-7.099733 3.413333-14.165333 6.826667-21.0944 10.615467-4.266667 2.321067-8.3968 4.8128-12.561067 7.2704-3.822933 2.218667-7.748267 4.266667-11.502933 6.621867-0.512 0.3072-0.750933 0.8192-1.2288 1.160533-14.506667 9.147733-28.706133 18.807467-42.222933 29.559467a459.6736 459.6736 0 0 0-90.385067 96.768c-0.443733 0.546133-1.092267 0.8192-1.501867 1.4336-1.536 2.184533-2.7648 4.573867-4.266666 6.792533-1.604267 2.423467-3.447467 4.744533-5.0176 7.202133-2.56 3.9936-4.7104 8.157867-7.133867 12.219734-3.106133 5.188267-6.280533 10.376533-9.181867 15.701333-3.7888 6.929067-7.202133 13.994667-10.6496 21.0944-2.4576 5.12-5.051733 10.205867-7.338666 15.394133-3.515733 8.021333-6.519467 16.247467-9.557334 24.4736-1.672533 4.5056-3.549867 8.9088-5.051733 13.448534-4.3008 12.868267-8.0896 25.941333-11.264 39.185066-3.072 12.970667 2.594133 25.770667 13.073067 32.802134a31.3344 31.3344 0 0 0 9.966933 4.608 30.9248 30.9248 0 0 0 34.030933-15.2576 30.446933 30.446933 0 0 0 3.345067-7.7824c2.833067-11.844267 6.178133-23.483733 10.0352-34.9184 0.6144-1.8432 1.399467-3.549867 2.013867-5.358934 3.447467-9.762133 7.133867-19.456 11.332266-28.945066 0.512-1.160533 1.1264-2.2528 1.6384-3.447467 4.7104-10.308267 9.728-20.48 15.291734-30.344533l0.068266-0.1024a402.773333 402.773333 0 0 1 19.694934-31.4368l0.136533-0.375467a397.4144 397.4144 0 0 1 116.599467-111.2064c0.136533-0.1024 0.3072-0.068267 0.443733-0.170667a397.824 397.824 0 0 1 94.993067-42.973866c2.7648-0.8192 5.495467-1.7408 8.2944-2.491734 5.7344-1.604267 11.5712-3.003733 17.373866-4.334933a367.8208 367.8208 0 0 1 47.342934-7.953067c3.8912-0.443733 7.7824-0.9216 11.6736-1.2288 10.410667-0.785067 20.8896-1.3312 31.505066-1.3312s21.060267 0.546133 31.505067 1.3312c3.8912 0.3072 7.816533 0.785067 11.707733 1.2288a361.3696 361.3696 0 0 1 47.240534 7.953067c5.870933 1.3312 11.707733 2.730667 17.5104 4.334933 2.696533 0.750933 5.358933 1.6384 8.021333 2.4576 33.348267 10.103467 65.365333 24.405333 95.197867 43.008 0.136533 0.1024 0.3072 0.068267 0.443733 0.170667a396.151467 396.151467 0 0 1 116.599467 111.2064c0.1024 0.136533 0.1024 0.273067 0.170666 0.375467 13.687467 19.7632 25.3952 40.5504 35.191467 62.1568l1.467733 3.037866c4.3008 9.659733 8.055467 19.592533 11.605334 29.5936 0.546133 1.604267 1.2288 3.106133 1.774933 4.7104 3.822933 11.4688 7.236267 23.176533 10.0352 35.0208a31.061333 31.061333 0 0 0 60.450133-14.336zm-249.275733-560.2304A199.850667 199.850667 0 0 1 512 543.197867a199.850667 199.850667 0 0 1-199.5776-199.611734A199.816533 199.816533 0 0 1 512 144.008533a199.816533 199.816533 0 0 1 199.5776 199.5776z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M527.36 351.744V292.864L410.624 380.416 527.36 468.48V410.624c72.192 8.192 124.416 73.216 116.224 145.408-8.192 72.192-73.216 124.416-145.408 116.224-66.56-7.168-117.248-64-117.248-131.072-0.512-5.12-0.512-9.728 0-14.848H323.584c-0.512 5.12-0.512 9.728 0 14.848 0 104.96 85.504 189.952 190.464 189.952s189.952-85.504 189.952-190.464c-0.512-99.328-77.312-181.76-176.64-188.928z" fill="#FFFFFF"/></svg> - <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M437.314 840.84l-18.967-5.795c-43.935-13.425-84.182-35.551-119.623-65.767l-15.203-12.962 11.199-16.544c17.376-25.668 17.938-59.158 1.433-85.319-14.356-22.787-39.028-36.385-66.006-36.385-4.102 0-8.229 0.328-12.267 0.974l-19.752 3.158-5.301-19.288c-8.196-29.823-12.353-59.896-12.353-89.381 0-19.675 1.863-39.491 5.694-60.582l3.652-20.105 20.349 1.862c2.343 0.214 4.726 0.323 7.081 0.323 29.007 0 55.436-15.908 68.974-41.516 14.941-28.2 11.264-62.223-9.356-86.694l-13.166-15.625L278.1 276.7c38.694-38.954 86.677-68.095 138.76-84.273l19.741-6.132 7.631 19.211c11.88 29.908 40.312 49.234 72.432 49.234 32.097 0 60.521-19.328 72.413-49.241l7.632-19.197 19.73 6.122c43.968 13.642 84.295 36.164 119.862 66.938l15.414 13.337-11.883 16.561c-18.636 25.975-19.684 60.166-2.671 87.105 14.369 22.78 39.055 36.373 66.04 36.372 4.344 0 8.71-0.366 12.978-1.087l20.143-3.403 5.176 19.762c7.539 28.792 11.362 57.566 11.362 85.522 0 21.328-2.143 43.048-6.365 64.554l-3.859 19.65-19.952-1.709a77.999 77.999 0 0 0-6.612-0.281c-28.998 0-55.44 15.917-69.009 41.542-14.47 27.405-11.311 60.816 8.063 85.095l12.496 15.661-14.222 14.111c-38.674 38.378-86.551 67.041-138.455 82.892l-18.968 5.792-7.988-18.152c-12.462-28.318-40.459-46.617-71.325-46.617-30.883 0-58.893 18.299-71.36 46.619l-7.99 18.152zm-95.455-94.18c22.324 16.82 46.59 30.174 72.469 39.881 22.445-34.023 60.731-55.125 102.336-55.125 41.59 0 79.862 21.1 102.303 55.12 32.745-12.298 63.249-30.557 89.663-53.667-19.709-35.774-20.525-79.555-1.04-116.455 19.699-37.203 56.634-61.386 98.053-64.883 1.705-12.731 2.565-25.453 2.565-38 0-18.339-1.923-37.155-5.729-56.144-42.123-0.241-80.616-21.581-103.077-57.189-22.944-36.331-25.024-81.029-6.697-118.768-22.165-16.932-46.203-30.4-71.788-40.221-8.847 14.328-20.577 26.719-34.618 36.447-20.522 14.219-44.602 21.735-69.635 21.735-25.044 0-49.131-7.516-69.657-21.734-14.042-9.727-25.773-22.116-34.618-36.441-32.551 12.503-62.856 30.935-89.106 54.196 21.198 36.233 22.547 80.974 2.407 118.987-19.71 37.285-56.808 61.499-98.402 64.875-1.45 11.713-2.161 23.035-2.161 34.255 0 19.715 2.166 39.792 6.449 59.894 41.851 0.474 80.029 21.785 102.35 57.214 22.218 35.217 24.782 78.871 7.933 116.023z" fill="#FFFFFF"/><path d="M516.664 633.864c-66.246 0-120.141-53.897-120.141-120.147 0-66.249 53.895-120.146 120.141-120.146 66.237 0 120.127 53.897 120.127 120.146 0 66.25-53.89 120.147-120.127 120.147zm0-195.641c-41.625 0-75.488 33.866-75.488 75.494s33.863 75.495 75.488 75.495c41.617 0 75.475-33.867 75.475-75.495s-33.858-75.494-75.475-75.494z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path transform="scale(0.8) translate(128,128)" d="M960.853333 903.816533a463.633067 463.633067 0 0 0-11.264-39.185066c-1.536-4.539733-3.413333-8.942933-5.051733-13.448534a484.078933 484.078933 0 0 0-9.557333-24.4736c-2.2528-5.188267-4.881067-10.274133-7.338667-15.394133-3.413333-7.099733-6.8608-14.165333-10.6496-21.0944-2.901333-5.3248-6.075733-10.513067-9.181867-15.701333-2.423467-4.061867-4.573867-8.226133-7.133866-12.219734-1.604267-2.4576-3.413333-4.778667-5.0176-7.202133-1.501867-2.218667-2.730667-4.608-4.266667-6.792533-0.4096-0.6144-1.058133-0.887467-1.501867-1.4336a461.482667 461.482667 0 0 0-90.385066-96.768c-13.5168-10.786133-27.7504-20.48-42.257067-29.5936-0.477867-0.341333-0.7168-0.8192-1.194667-1.1264-3.6864-2.286933-7.509333-4.3008-11.264-6.485334-4.266667-2.491733-8.4992-5.051733-12.868266-7.441066-6.826667-3.6864-13.789867-7.099733-20.753067-10.478934-3.618133-1.7408-7.202133-3.618133-10.8544-5.290666a449.194667 449.194667 0 0 0-31.607467-12.731734c-0.7168-0.273067-1.365333-0.6144-2.082133-0.8192-3.140267-1.1264-6.417067-1.911467-9.557333-2.935466-4.164267-1.399467-8.328533-2.833067-12.561067-4.096a259.9936 259.9936 0 0 0 129.194667-225.450667 260.061867 260.061867 0 0 0-76.629334-185.002667 259.9936 259.9936 0 0 0-185.002666-76.629333H512h-0.034133a259.857067 259.857067 0 0 0-185.002667 76.629333 259.925333 259.925333 0 0 0-76.629333 185.002667 259.584 259.584 0 0 0 76.629333 185.002667c15.906133 15.940267 33.655467 29.2864 52.565333 40.448-4.266667 1.262933-8.430933 2.730667-12.663466 4.096-3.140267 1.058133-6.3488 1.8432-9.489067 2.935466-0.7168 0.238933-1.365333 0.580267-2.048 0.8192-10.683733 3.822933-21.265067 8.0896-31.675733 12.765867-3.584 1.604267-7.0656 3.4816-10.615467 5.154133-7.099733 3.413333-14.165333 6.826667-21.0944 10.615467-4.266667 2.321067-8.3968 4.8128-12.561067 7.2704-3.822933 2.218667-7.748267 4.266667-11.502933 6.621867-0.512 0.3072-0.750933 0.8192-1.2288 1.160533-14.506667 9.147733-28.706133 18.807467-42.222933 29.559467a459.6736 459.6736 0 0 0-90.385067 96.768c-0.443733 0.546133-1.092267 0.8192-1.501867 1.4336-1.536 2.184533-2.7648 4.573867-4.266666 6.792533-1.604267 2.423467-3.447467 4.744533-5.0176 7.202133-2.56 3.9936-4.7104 8.157867-7.133867 12.219734-3.106133 5.188267-6.280533 10.376533-9.181867 15.701333-3.7888 6.929067-7.202133 13.994667-10.6496 21.0944-2.4576 5.12-5.051733 10.205867-7.338666 15.394133-3.515733 8.021333-6.519467 16.247467-9.557334 24.4736-1.672533 4.5056-3.549867 8.9088-5.051733 13.448534-4.3008 12.868267-8.0896 25.941333-11.264 39.185066-3.072 12.970667 2.594133 25.770667 13.073067 32.802134a31.3344 31.3344 0 0 0 9.966933 4.608 30.9248 30.9248 0 0 0 34.030933-15.2576 30.446933 30.446933 0 0 0 3.345067-7.7824c2.833067-11.844267 6.178133-23.483733 10.0352-34.9184 0.6144-1.8432 1.399467-3.549867 2.013867-5.358934 3.447467-9.762133 7.133867-19.456 11.332266-28.945066 0.512-1.160533 1.1264-2.2528 1.6384-3.447467 4.7104-10.308267 9.728-20.48 15.291734-30.344533l0.068266-0.1024a402.773333 402.773333 0 0 1 19.694934-31.4368l0.136533-0.375467a397.4144 397.4144 0 0 1 116.599467-111.2064c0.136533-0.1024 0.3072-0.068267 0.443733-0.170667a397.824 397.824 0 0 1 94.993067-42.973866c2.7648-0.8192 5.495467-1.7408 8.2944-2.491734 5.7344-1.604267 11.5712-3.003733 17.373866-4.334933a367.8208 367.8208 0 0 1 47.342934-7.953067c3.8912-0.443733 7.7824-0.9216 11.6736-1.2288 10.410667-0.785067 20.8896-1.3312 31.505066-1.3312s21.060267 0.546133 31.505067 1.3312c3.8912 0.3072 7.816533 0.785067 11.707733 1.2288a361.3696 361.3696 0 0 1 47.240534 7.953067c5.870933 1.3312 11.707733 2.730667 17.5104 4.334933 2.696533 0.750933 5.358933 1.6384 8.021333 2.4576 33.348267 10.103467 65.365333 24.405333 95.197867 43.008 0.136533 0.1024 0.3072 0.068267 0.443733 0.170667a396.151467 396.151467 0 0 1 116.599467 111.2064c0.1024 0.136533 0.1024 0.273067 0.170666 0.375467 13.687467 19.7632 25.3952 40.5504 35.191467 62.1568l1.467733 3.037866c4.3008 9.659733 8.055467 19.592533 11.605334 29.5936 0.546133 1.604267 1.2288 3.106133 1.774933 4.7104 3.822933 11.4688 7.236267 23.176533 10.0352 35.0208a31.061333 31.061333 0 0 0 60.450133-14.336zm-249.275733-560.2304A199.850667 199.850667 0 0 1 512 543.197867a199.850667 199.850667 0 0 1-199.5776-199.611734A199.816533 199.816533 0 0 1 512 144.008533a199.816533 199.816533 0 0 1 199.5776 199.5776z" fill="#FFFFFF"/></svg> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path transform="scale(0.8) translate(128,128)" d="M960.853333 903.816533a463.633067 463.633067 0 0 0-11.264-39.185066c-1.536-4.539733-3.413333-8.942933-5.051733-13.448534a484.078933 484.078933 0 0 0-9.557333-24.4736c-2.2528-5.188267-4.881067-10.274133-7.338667-15.394133-3.413333-7.099733-6.8608-14.165333-10.6496-21.0944-2.901333-5.3248-6.075733-10.513067-9.181867-15.701333-2.423467-4.061867-4.573867-8.226133-7.133866-12.219734-1.604267-2.4576-3.413333-4.778667-5.0176-7.202133-1.501867-2.218667-2.730667-4.608-4.266667-6.792533-0.4096-0.6144-1.058133-0.887467-1.501867-1.4336a461.482667 461.482667 0 0 0-90.385066-96.768c-13.5168-10.786133-27.7504-20.48-42.257067-29.5936-0.477867-0.341333-0.7168-0.8192-1.194667-1.1264-3.6864-2.286933-7.509333-4.3008-11.264-6.485334-4.266667-2.491733-8.4992-5.051733-12.868266-7.441066-6.826667-3.6864-13.789867-7.099733-20.753067-10.478934-3.618133-1.7408-7.202133-3.618133-10.8544-5.290666a449.194667 449.194667 0 0 0-31.607467-12.731734c-0.7168-0.273067-1.365333-0.6144-2.082133-0.8192-3.140267-1.1264-6.417067-1.911467-9.557333-2.935466-4.164267-1.399467-8.328533-2.833067-12.561067-4.096a259.9936 259.9936 0 0 0 129.194667-225.450667 260.061867 260.061867 0 0 0-76.629334-185.002667 259.9936 259.9936 0 0 0-185.002666-76.629333H512h-0.034133a259.857067 259.857067 0 0 0-185.002667 76.629333 259.925333 259.925333 0 0 0-76.629333 185.002667 259.584 259.584 0 0 0 76.629333 185.002667c15.906133 15.940267 33.655467 29.2864 52.565333 40.448-4.266667 1.262933-8.430933 2.730667-12.663466 4.096-3.140267 1.058133-6.3488 1.8432-9.489067 2.935466-0.7168 0.238933-1.365333 0.580267-2.048 0.8192-10.683733 3.822933-21.265067 8.0896-31.675733 12.765867-3.584 1.604267-7.0656 3.4816-10.615467 5.154133-7.099733 3.413333-14.165333 6.826667-21.0944 10.615467-4.266667 2.321067-8.3968 4.8128-12.561067 7.2704-3.822933 2.218667-7.748267 4.266667-11.502933 6.621867-0.512 0.3072-0.750933 0.8192-1.2288 1.160533-14.506667 9.147733-28.706133 18.807467-42.222933 29.559467a459.6736 459.6736 0 0 0-90.385067 96.768c-0.443733 0.546133-1.092267 0.8192-1.501867 1.4336-1.536 2.184533-2.7648 4.573867-4.266666 6.792533-1.604267 2.423467-3.447467 4.744533-5.0176 7.202133-2.56 3.9936-4.7104 8.157867-7.133867 12.219734-3.106133 5.188267-6.280533 10.376533-9.181867 15.701333-3.7888 6.929067-7.202133 13.994667-10.6496 21.0944-2.4576 5.12-5.051733 10.205867-7.338666 15.394133-3.515733 8.021333-6.519467 16.247467-9.557334 24.4736-1.672533 4.5056-3.549867 8.9088-5.051733 13.448534-4.3008 12.868267-8.0896 25.941333-11.264 39.185066-3.072 12.970667 2.594133 25.770667 13.073067 32.802134a31.3344 31.3344 0 0 0 9.966933 4.608 30.9248 30.9248 0 0 0 34.030933-15.2576 30.446933 30.446933 0 0 0 3.345067-7.7824c2.833067-11.844267 6.178133-23.483733 10.0352-34.9184 0.6144-1.8432 1.399467-3.549867 2.013867-5.358934 3.447467-9.762133 7.133867-19.456 11.332266-28.945066 0.512-1.160533 1.1264-2.2528 1.6384-3.447467 4.7104-10.308267 9.728-20.48 15.291734-30.344533l0.068266-0.1024a402.773333 402.773333 0 0 1 19.694934-31.4368l0.136533-0.375467a397.4144 397.4144 0 0 1 116.599467-111.2064c0.136533-0.1024 0.3072-0.068267 0.443733-0.170667a397.824 397.824 0 0 1 94.993067-42.973866c2.7648-0.8192 5.495467-1.7408 8.2944-2.491734 5.7344-1.604267 11.5712-3.003733 17.373866-4.334933a367.8208 367.8208 0 0 1 47.342934-7.953067c3.8912-0.443733 7.7824-0.9216 11.6736-1.2288 10.410667-0.785067 20.8896-1.3312 31.505066-1.3312s21.060267 0.546133 31.505067 1.3312c3.8912 0.3072 7.816533 0.785067 11.707733 1.2288a361.3696 361.3696 0 0 1 47.240534 7.953067c5.870933 1.3312 11.707733 2.730667 17.5104 4.334933 2.696533 0.750933 5.358933 1.6384 8.021333 2.4576 33.348267 10.103467 65.365333 24.405333 95.197867 43.008 0.136533 0.1024 0.3072 0.068267 0.443733 0.170667a396.151467 396.151467 0 0 1 116.599467 111.2064c0.1024 0.136533 0.1024 0.273067 0.170666 0.375467 13.687467 19.7632 25.3952 40.5504 35.191467 62.1568l1.467733 3.037866c4.3008 9.659733 8.055467 19.592533 11.605334 29.5936 0.546133 1.604267 1.2288 3.106133 1.774933 4.7104 3.822933 11.4688 7.236267 23.176533 10.0352 35.0208a31.061333 31.061333 0 0 0 60.450133-14.336zm-249.275733-560.2304A199.850667 199.850667 0 0 1 512 543.197867a199.850667 199.850667 0 0 1-199.5776-199.611734A199.816533 199.816533 0 0 1 512 144.008533a199.816533 199.816533 0 0 1 199.5776 199.5776z" fill="#FFFFFF"/></svg> + + + <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M437.314 840.84l-18.967-5.795c-43.935-13.425-84.182-35.551-119.623-65.767l-15.203-12.962 11.199-16.544c17.376-25.668 17.938-59.158 1.433-85.319-14.356-22.787-39.028-36.385-66.006-36.385-4.102 0-8.229 0.328-12.267 0.974l-19.752 3.158-5.301-19.288c-8.196-29.823-12.353-59.896-12.353-89.381 0-19.675 1.863-39.491 5.694-60.582l3.652-20.105 20.349 1.862c2.343 0.214 4.726 0.323 7.081 0.323 29.007 0 55.436-15.908 68.974-41.516 14.941-28.2 11.264-62.223-9.356-86.694l-13.166-15.625L278.1 276.7c38.694-38.954 86.677-68.095 138.76-84.273l19.741-6.132 7.631 19.211c11.88 29.908 40.312 49.234 72.432 49.234 32.097 0 60.521-19.328 72.413-49.241l7.632-19.197 19.73 6.122c43.968 13.642 84.295 36.164 119.862 66.938l15.414 13.337-11.883 16.561c-18.636 25.975-19.684 60.166-2.671 87.105 14.369 22.78 39.055 36.373 66.04 36.372 4.344 0 8.71-0.366 12.978-1.087l20.143-3.403 5.176 19.762c7.539 28.792 11.362 57.566 11.362 85.522 0 21.328-2.143 43.048-6.365 64.554l-3.859 19.65-19.952-1.709a77.999 77.999 0 0 0-6.612-0.281c-28.998 0-55.44 15.917-69.009 41.542-14.47 27.405-11.311 60.816 8.063 85.095l12.496 15.661-14.222 14.111c-38.674 38.378-86.551 67.041-138.455 82.892l-18.968 5.792-7.988-18.152c-12.462-28.318-40.459-46.617-71.325-46.617-30.883 0-58.893 18.299-71.36 46.619l-7.99 18.152zm-95.455-94.18c22.324 16.82 46.59 30.174 72.469 39.881 22.445-34.023 60.731-55.125 102.336-55.125 41.59 0 79.862 21.1 102.303 55.12 32.745-12.298 63.249-30.557 89.663-53.667-19.709-35.774-20.525-79.555-1.04-116.455 19.699-37.203 56.634-61.386 98.053-64.883 1.705-12.731 2.565-25.453 2.565-38 0-18.339-1.923-37.155-5.729-56.144-42.123-0.241-80.616-21.581-103.077-57.189-22.944-36.331-25.024-81.029-6.697-118.768-22.165-16.932-46.203-30.4-71.788-40.221-8.847 14.328-20.577 26.719-34.618 36.447-20.522 14.219-44.602 21.735-69.635 21.735-25.044 0-49.131-7.516-69.657-21.734-14.042-9.727-25.773-22.116-34.618-36.441-32.551 12.503-62.856 30.935-89.106 54.196 21.198 36.233 22.547 80.974 2.407 118.987-19.71 37.285-56.808 61.499-98.402 64.875-1.45 11.713-2.161 23.035-2.161 34.255 0 19.715 2.166 39.792 6.449 59.894 41.851 0.474 80.029 21.785 102.35 57.214 22.218 35.217 24.782 78.871 7.933 116.023z" fill="#FFFFFF"/><path d="M516.664 633.864c-66.246 0-120.141-53.897-120.141-120.147 0-66.249 53.895-120.146 120.141-120.146 66.237 0 120.127 53.897 120.127 120.146 0 66.25-53.89 120.147-120.127 120.147zm0-195.641c-41.625 0-75.488 33.866-75.488 75.494s33.863 75.495 75.488 75.495c41.617 0 75.475-33.867 75.475-75.495s-33.858-75.494-75.475-75.494z" fill="#FFFFFF"/></svg> + + <svg viewBox="0 0 1027 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0zm0 963.584c-249.344 0-451.584-202.24-451.584-451.584S262.656 60.416 512 60.416s451.584 202.24 451.584 451.584-202.24 451.584-451.584 451.584z" fill="#FFFFFF"/><path d="M437.314 840.84l-18.967-5.795c-43.935-13.425-84.182-35.551-119.623-65.767l-15.203-12.962 11.199-16.544c17.376-25.668 17.938-59.158 1.433-85.319-14.356-22.787-39.028-36.385-66.006-36.385-4.102 0-8.229 0.328-12.267 0.974l-19.752 3.158-5.301-19.288c-8.196-29.823-12.353-59.896-12.353-89.381 0-19.675 1.863-39.491 5.694-60.582l3.652-20.105 20.349 1.862c2.343 0.214 4.726 0.323 7.081 0.323 29.007 0 55.436-15.908 68.974-41.516 14.941-28.2 11.264-62.223-9.356-86.694l-13.166-15.625L278.1 276.7c38.694-38.954 86.677-68.095 138.76-84.273l19.741-6.132 7.631 19.211c11.88 29.908 40.312 49.234 72.432 49.234 32.097 0 60.521-19.328 72.413-49.241l7.632-19.197 19.73 6.122c43.968 13.642 84.295 36.164 119.862 66.938l15.414 13.337-11.883 16.561c-18.636 25.975-19.684 60.166-2.671 87.105 14.369 22.78 39.055 36.373 66.04 36.372 4.344 0 8.71-0.366 12.978-1.087l20.143-3.403 5.176 19.762c7.539 28.792 11.362 57.566 11.362 85.522 0 21.328-2.143 43.048-6.365 64.554l-3.859 19.65-19.952-1.709a77.999 77.999 0 0 0-6.612-0.281c-28.998 0-55.44 15.917-69.009 41.542-14.47 27.405-11.311 60.816 8.063 85.095l12.496 15.661-14.222 14.111c-38.674 38.378-86.551 67.041-138.455 82.892l-18.968 5.792-7.988-18.152c-12.462-28.318-40.459-46.617-71.325-46.617-30.883 0-58.893 18.299-71.36 46.619l-7.99 18.152zm-95.455-94.18c22.324 16.82 46.59 30.174 72.469 39.881 22.445-34.023 60.731-55.125 102.336-55.125 41.59 0 79.862 21.1 102.303 55.12 32.745-12.298 63.249-30.557 89.663-53.667-19.709-35.774-20.525-79.555-1.04-116.455 19.699-37.203 56.634-61.386 98.053-64.883 1.705-12.731 2.565-25.453 2.565-38 0-18.339-1.923-37.155-5.729-56.144-42.123-0.241-80.616-21.581-103.077-57.189-22.944-36.331-25.024-81.029-6.697-118.768-22.165-16.932-46.203-30.4-71.788-40.221-8.847 14.328-20.577 26.719-34.618 36.447-20.522 14.219-44.602 21.735-69.635 21.735-25.044 0-49.131-7.516-69.657-21.734-14.042-9.727-25.773-22.116-34.618-36.441-32.551 12.503-62.856 30.935-89.106 54.196 21.198 36.233 22.547 80.974 2.407 118.987-19.71 37.285-56.808 61.499-98.402 64.875-1.45 11.713-2.161 23.035-2.161 34.255 0 19.715 2.166 39.792 6.449 59.894 41.851 0.474 80.029 21.785 102.35 57.214 22.218 35.217 24.782 78.871 7.933 116.023z" fill="#FFFFFF"/><path d="M516.664 633.864c-66.246 0-120.141-53.897-120.141-120.147 0-66.249 53.895-120.146 120.141-120.146 66.237 0 120.127 53.897 120.127 120.146 0 66.25-53.89 120.147-120.127 120.147zm0-195.641c-41.625 0-75.488 33.866-75.488 75.494s33.863 75.495 75.488 75.495c41.617 0 75.475-33.867 75.475-75.495s-33.858-75.494-75.475-75.494z" fill="#FFFFFF"/></svg> diff --git a/DHSoftware/Views/ImageViewerControl.Designer.cs b/DHSoftware/Views/ImageViewerControl.Designer.cs index 4d48794..8c08a85 100644 --- a/DHSoftware/Views/ImageViewerControl.Designer.cs +++ b/DHSoftware/Views/ImageViewerControl.Designer.cs @@ -11,15 +11,7 @@ /// 清理所有正在使用的资源。 /// /// 如果应释放托管资源,为 true;否则为 false。 - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - + #region 组件设计器生成的代码 /// diff --git a/DHSoftware/Views/ImageViewerControl.cs b/DHSoftware/Views/ImageViewerControl.cs index 9eb2c9b..3865c47 100644 --- a/DHSoftware/Views/ImageViewerControl.cs +++ b/DHSoftware/Views/ImageViewerControl.cs @@ -16,54 +16,61 @@ namespace DHSoftware.Views private Bitmap _currentImage; private float _scale = 1.0f; private PointF _offset = PointF.Empty; - private RectangleF _roiRect; - private PointF _roiStart; - private bool _isDrawing; private Point _dragStart; private bool _isDragging; - private Pen _roiPen = new Pen(Color.Red, 2); + private readonly object _imageLock = new object(); #endregion - #region 公开属性 #region 公开属性 public Bitmap Image { - get => _currentImage; + get + { + lock (_imageLock) + { + return _currentImage?.Clone() as Bitmap; + } + } set { - // 记录旧状态 - var oldSize = _currentImage?.Size ?? Size.Empty; - var oldScale = _scale; - var oldOffset = _offset; + Bitmap newImage = value?.Clone() as Bitmap; + Bitmap oldImageToDispose = null; - _currentImage?.Dispose(); - _currentImage = value; - - if (_currentImage != null) + lock (_imageLock) { - if (_currentImage.Size != oldSize) + // 交换图像引用 + oldImageToDispose = _currentImage; + _currentImage = newImage; + + if (_currentImage != null) { - // 尺寸不同时:重置ROI、自动适配 - _roiRect = RectangleF.Empty; - AutoFit(); - } - else - { - // 尺寸相同时:保留缩放和偏移 - _scale = oldScale; - _offset = oldOffset; - ClampOffset(); + if (oldImageToDispose?.Size != _currentImage.Size) + { + AutoFit(); + } + else + { + ClampOffset(); + } } } - pictureBox.Invalidate(); + // 在锁外安全释放旧图像 + if (oldImageToDispose != null) + { + // 使用BeginInvoke确保在UI线程释放资源 + BeginInvoke(new Action(() => + { + oldImageToDispose.Dispose(); + oldImageToDispose = null; + })); + } + + SafeInvalidate(); } } #endregion - public RectangleF CurrentROI => _roiRect; - #endregion - public ImageViewerControl() { InitializeComponents(); @@ -73,15 +80,13 @@ namespace DHSoftware.Views #region 初始化 private void InitializeComponents() { - // 主显示区域 pictureBox = new PictureBox { Dock = DockStyle.Fill, BackColor = Color.DarkGray, - Cursor = Cursors.Cross + Cursor = Cursors.Hand }; - // 状态栏 statusLabel = new Label { Dock = DockStyle.Bottom, @@ -91,7 +96,6 @@ namespace DHSoftware.Views Font = new Font("Consolas", 10) }; - // 事件绑定 pictureBox.MouseDown += PictureBox_MouseDown; pictureBox.MouseMove += PictureBox_MouseMove; pictureBox.MouseUp += PictureBox_MouseUp; @@ -106,8 +110,8 @@ namespace DHSoftware.Views { typeof(PictureBox).GetMethod("SetStyle", System.Reflection.BindingFlags.NonPublic | - System.Reflection.BindingFlags.Instance) - ?.Invoke(pictureBox, new object[] { + System.Reflection.BindingFlags.Instance)? + .Invoke(pictureBox, new object[] { ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true @@ -118,189 +122,225 @@ namespace DHSoftware.Views #region 核心功能 private void AutoFit() { - if (_currentImage == null) return; + lock (_imageLock) + { + if (_currentImage == null) return; - const float marginRatio = 0.1f; - float marginWidth = Width * marginRatio; - float marginHeight = Height * marginRatio; + const float marginRatio = 0.1f; + float marginWidth = Width * marginRatio; + float marginHeight = Height * marginRatio; - _scale = Math.Min( - (Width - marginWidth * 2) / _currentImage.Width, - (Height - marginHeight * 2) / _currentImage.Height - ); + _scale = Math.Min( + (Width - marginWidth * 2) / _currentImage.Width, + (Height - marginHeight * 2) / _currentImage.Height + ); - _offset.X = marginWidth + (Width - marginWidth * 2 - _currentImage.Width * _scale) / 2; - _offset.Y = marginHeight + (Height - marginHeight * 2 - _currentImage.Height * _scale) / 2; + _offset.X = marginWidth + (Width - marginWidth * 2 - _currentImage.Width * _scale) / 2; + _offset.Y = marginHeight + (Height - marginHeight * 2 - _currentImage.Height * _scale) / 2; + + ClampOffset(); + } } private void PictureBox_Paint(object sender, PaintEventArgs e) { - if (_currentImage == null) return; + Bitmap drawImage = null; + RectangleF destRect; + float scale; + PointF offset; - // 绘制图像 - var destRect = new RectangleF( - _offset.X, - _offset.Y, - _currentImage.Width * _scale, - _currentImage.Height * _scale); - - e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - e.Graphics.DrawImage(_currentImage, destRect); - - // 绘制ROI - if (!_roiRect.IsEmpty) + // 创建临时绘图参数 + lock (_imageLock) { - var displayRect = new RectangleF( - _roiRect.X * _scale + _offset.X, - _roiRect.Y * _scale + _offset.Y, - _roiRect.Width * _scale, - _roiRect.Height * _scale); + if (_currentImage == null) return; - using (var pen = new Pen(_roiPen.Color, _roiPen.Width / _scale)) - { - e.Graphics.DrawRectangle(pen, - displayRect.X, - displayRect.Y, - displayRect.Width, - displayRect.Height); - } + // 创建绘图副本 + drawImage = _currentImage.Clone() as Bitmap; + scale = _scale; + offset = _offset; + + destRect = new RectangleF( + offset.X, + offset.Y, + drawImage.Width * scale, + drawImage.Height * scale); + } + + try + { + e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + e.Graphics.DrawImage(drawImage, destRect); + } + finally + { + drawImage?.Dispose(); } } #endregion - #region 鼠标事件 + #region 鼠标交互(左键拖动) private void PictureBox_MouseDown(object sender, MouseEventArgs e) { - if (e.Button == MouseButtons.Left && _currentImage != null) + if (InvokeRequired) return; + + lock (_imageLock) { - _roiStart = ClampCoordinates(ConvertToImageCoords(e.Location)); - _isDrawing = true; - } - else if (e.Button == MouseButtons.Right) - { - _dragStart = e.Location; - _isDragging = true; + if (_currentImage == null) return; + + if (e.Button == MouseButtons.Left) + { + _dragStart = e.Location; + _isDragging = true; + } } } private void PictureBox_MouseMove(object sender, MouseEventArgs e) { - if (_isDragging) - { - _offset.X += e.X - _dragStart.X; - _offset.Y += e.Y - _dragStart.Y; - _dragStart = e.Location; - ClampOffset(); - pictureBox.Invalidate(); - } - else if (_isDrawing && _currentImage != null) - { - PointF current = ClampCoordinates(ConvertToImageCoords(e.Location)); - _roiRect = new RectangleF( - Math.Min(_roiStart.X, current.X), - Math.Min(_roiStart.Y, current.Y), - Math.Abs(_roiStart.X - current.X), - Math.Abs(_roiStart.Y - current.Y) - ); - pictureBox.Invalidate(); - } + if (InvokeRequired) return; - UpdateStatus(e.Location); + lock (_imageLock) + { + if (_currentImage == null) return; + + if (_isDragging) + { + _offset.X += e.X - _dragStart.X; + _offset.Y += e.Y - _dragStart.Y; + _dragStart = e.Location; + ClampOffset(); + SafeInvalidate(); + } + + UpdateStatus(e.Location); + } } private void PictureBox_MouseUp(object sender, MouseEventArgs e) { - _isDragging = false; - _isDrawing = false; + lock (_imageLock) + { + _isDragging = false; + } } private void PictureBox_MouseWheel(object sender, MouseEventArgs e) { - if (_currentImage == null) return; + if (InvokeRequired) return; - PointF mousePos = e.Location; - PointF imgPosBefore = ConvertToImageCoords(mousePos); + lock (_imageLock) + { + if (_currentImage == null) return; - if (imgPosBefore.X < 0 || imgPosBefore.X > _currentImage.Width || - imgPosBefore.Y < 0 || imgPosBefore.Y > _currentImage.Height) return; + PointF imgPos = ConvertToImageCoords(e.Location); + if (imgPos.X < 0 || imgPos.X > _currentImage.Width || + imgPos.Y < 0 || imgPos.Y > _currentImage.Height) + return; - float zoom = e.Delta > 0 ? 1.1f : 0.9f; - float newScale = Math.Clamp(_scale * zoom, 0.1f, 10f); + float zoom = e.Delta > 0 ? 1.1f : 0.9f; + float newScale = Math.Clamp(_scale * zoom, 0.1f, 10f); - _offset.X = mousePos.X - imgPosBefore.X * newScale; - _offset.Y = mousePos.Y - imgPosBefore.Y * newScale; + _offset.X = e.Location.X - imgPos.X * newScale; + _offset.Y = e.Location.Y - imgPos.Y * newScale; - _scale = newScale; - ClampOffset(); - pictureBox.Invalidate(); + _scale = newScale; + ClampOffset(); + SafeInvalidate(); + } } #endregion #region 辅助方法 - private PointF ConvertToImageCoords(PointF mousePos) + private PointF ConvertToImageCoords(Point mousePos) { - return new PointF( - (mousePos.X - _offset.X) / _scale, - (mousePos.Y - _offset.Y) / _scale); - } - - private PointF ClampCoordinates(PointF point) - { - return new PointF( - Math.Max(0, Math.Min(_currentImage.Width, point.X)), - Math.Max(0, Math.Min(_currentImage.Height, point.Y))); + lock (_imageLock) + { + if (_currentImage == null) return PointF.Empty; + return new PointF( + (mousePos.X - _offset.X) / _scale, + (mousePos.Y - _offset.Y) / _scale); + } } private void ClampOffset() { - if (_currentImage == null) return; + lock (_imageLock) + { + if (_currentImage == null) return; - float imgWidth = _currentImage.Width * _scale; - float imgHeight = _currentImage.Height * _scale; + float imgWidth = _currentImage.Width * _scale; + float imgHeight = _currentImage.Height * _scale; - if (imgWidth <= Width) - { - _offset.X = Math.Clamp(_offset.X, 0, Width - imgWidth); - } - else - { - _offset.X = Math.Clamp(_offset.X, Width - imgWidth, 0); - } + _offset.X = Math.Clamp(_offset.X, + imgWidth > Width ? Width - imgWidth : 0, + imgWidth > Width ? 0 : Width - imgWidth); - if (imgHeight <= Height) - { - _offset.Y = Math.Clamp(_offset.Y, 0, Height - imgHeight); - } - else - { - _offset.Y = Math.Clamp(_offset.Y, Height - imgHeight, 0); + _offset.Y = Math.Clamp(_offset.Y, + imgHeight > Height ? Height - imgHeight : 0, + imgHeight > Height ? 0 : Height - imgHeight); } } private void UpdateStatus(Point mousePos) { - if (_currentImage == null) return; + if (InvokeRequired) + { + BeginInvoke(new Action(UpdateStatus), mousePos); + return; + } - PointF imgPos = ConvertToImageCoords(mousePos); - bool inImage = imgPos.X >= 0 && imgPos.X <= _currentImage.Width && - imgPos.Y >= 0 && imgPos.Y <= _currentImage.Height; + lock (_imageLock) + { + if (_currentImage == null) + { + statusLabel.Text = "无有效图像"; + return; + } - string roiInfo = _roiRect.IsEmpty ? - "未选择区域" : - $"选区: X={_roiRect.X:0} Y={_roiRect.Y:0} {_roiRect.Width:0}x{_roiRect.Height:0}"; + PointF imgPos = ConvertToImageCoords(mousePos); + bool inImage = imgPos.X >= 0 && imgPos.X <= _currentImage.Width && + imgPos.Y >= 0 && imgPos.Y <= _currentImage.Height; - statusLabel.Text = inImage ? - $"坐标: ({imgPos.X:0}, {imgPos.Y:0}) | 缩放: {_scale * 100:0}% | {roiInfo}" : - $"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}% | {roiInfo}"; + statusLabel.Text = inImage ? + $"坐标: ({imgPos.X:F1}, {imgPos.Y:F1}) | 缩放: {_scale * 100:0}%" : + $"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}%"; + } } - public void ClearROI() + private void SafeInvalidate() { - _roiRect = RectangleF.Empty; - pictureBox.Invalidate(); // 触发重绘 - UpdateStatus(Point.Empty); // 更新状态栏 + if (InvokeRequired) + { + BeginInvoke(new Action(pictureBox.Invalidate)); + } + else + { + pictureBox.Invalidate(); + } + } + #endregion + + public Bitmap GetCurrentSnapshot() + { + lock (_imageLock) + { + // 返回深拷贝防止原始图像被修改 + return _currentImage?.Clone() as Bitmap; + } } - #endregion + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + lock (_imageLock) + { + _currentImage?.Dispose(); + _currentImage = null; + } + base.Dispose(disposing); + } } } \ No newline at end of file diff --git a/DHSoftware/Views/SavePositionControl.Designer.cs b/DHSoftware/Views/SavePositionControl.Designer.cs new file mode 100644 index 0000000..6168432 --- /dev/null +++ b/DHSoftware/Views/SavePositionControl.Designer.cs @@ -0,0 +1,189 @@ +namespace DHSoftware.Views +{ + partial class SavePositionControl + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + panel1 = new AntdUI.Panel(); + sltName = new AntdUI.Select(); + label1 = new AntdUI.Label(); + iptPosition = new AntdUI.Input(); + label3 = new AntdUI.Label(); + divider1 = new AntdUI.Divider(); + stackPanel1 = new AntdUI.StackPanel(); + button_cancel = new AntdUI.Button(); + button_ok = new AntdUI.Button(); + divider2 = new AntdUI.Divider(); + lbTitleName = new AntdUI.Label(); + panel1.SuspendLayout(); + stackPanel1.SuspendLayout(); + SuspendLayout(); + // + // panel1 + // + panel1.Controls.Add(sltName); + panel1.Controls.Add(label1); + panel1.Controls.Add(iptPosition); + panel1.Controls.Add(label3); + panel1.Controls.Add(divider1); + panel1.Controls.Add(stackPanel1); + panel1.Controls.Add(divider2); + panel1.Controls.Add(lbTitleName); + panel1.Dock = DockStyle.Fill; + panel1.Location = new Point(0, 0); + panel1.Name = "panel1"; + panel1.Padding = new Padding(12); + panel1.Shadow = 6; + panel1.Size = new Size(500, 243); + panel1.TabIndex = 0; + panel1.Text = "panel1"; + // + // sltName + // + sltName.Dock = DockStyle.Top; + sltName.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + sltName.Location = new Point(18, 196); + sltName.MaxCount = 20; + sltName.Name = "sltName"; + sltName.Radius = 3; + sltName.Size = new Size(464, 38); + sltName.TabIndex = 24; + // + // label1 + // + label1.Dock = DockStyle.Top; + label1.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); + label1.Location = new Point(18, 172); + label1.Name = "label1"; + label1.Size = new Size(464, 24); + label1.TabIndex = 23; + label1.Text = "工位名称"; + // + // iptPosition + // + iptPosition.Dock = DockStyle.Top; + iptPosition.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + iptPosition.Location = new Point(18, 134); + iptPosition.Name = "iptPosition"; + iptPosition.Radius = 3; + iptPosition.Size = new Size(464, 38); + iptPosition.TabIndex = 22; + // + // label3 + // + label3.Dock = DockStyle.Top; + label3.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); + label3.Location = new Point(18, 110); + label3.Name = "label3"; + label3.Size = new Size(464, 24); + label3.TabIndex = 21; + label3.Text = "当前位置"; + // + // divider1 + // + divider1.Dock = DockStyle.Top; + divider1.Location = new Point(18, 98); + divider1.Name = "divider1"; + divider1.Size = new Size(464, 12); + divider1.TabIndex = 20; + // + // stackPanel1 + // + stackPanel1.Controls.Add(button_cancel); + stackPanel1.Controls.Add(button_ok); + stackPanel1.Dock = DockStyle.Top; + stackPanel1.Location = new Point(18, 54); + stackPanel1.Name = "stackPanel1"; + stackPanel1.RightToLeft = RightToLeft.No; + stackPanel1.Size = new Size(464, 44); + stackPanel1.TabIndex = 19; + stackPanel1.Text = "stackPanel1"; + // + // button_cancel + // + button_cancel.BorderWidth = 1F; + button_cancel.Font = new Font("Microsoft YaHei UI", 9F); + button_cancel.Ghost = true; + button_cancel.Location = new Point(84, 3); + button_cancel.Name = "button_cancel"; + button_cancel.Size = new Size(75, 38); + button_cancel.TabIndex = 1; + button_cancel.Text = "取消"; + // + // button_ok + // + button_ok.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + button_ok.Location = new Point(3, 3); + button_ok.Name = "button_ok"; + button_ok.Size = new Size(75, 38); + button_ok.TabIndex = 0; + button_ok.Text = "确定"; + button_ok.Type = AntdUI.TTypeMini.Primary; + // + // divider2 + // + divider2.Dock = DockStyle.Top; + divider2.Location = new Point(18, 42); + divider2.Name = "divider2"; + divider2.Size = new Size(464, 12); + divider2.TabIndex = 18; + // + // lbTitleName + // + lbTitleName.Dock = DockStyle.Top; + lbTitleName.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); + lbTitleName.Location = new Point(18, 18); + lbTitleName.Name = "lbTitleName"; + lbTitleName.Size = new Size(464, 24); + lbTitleName.TabIndex = 17; + lbTitleName.Text = "保存当前位置操作"; + // + // SavePositionControl + // + Controls.Add(panel1); + Name = "SavePositionControl"; + Size = new Size(500, 243); + panel1.ResumeLayout(false); + stackPanel1.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private AntdUI.Panel panel1; + private AntdUI.Label lbTitleName; + private AntdUI.Input iptPosition; + private AntdUI.Label label3; + private AntdUI.Divider divider1; + private AntdUI.StackPanel stackPanel1; + private AntdUI.Button button_cancel; + private AntdUI.Button button_ok; + private AntdUI.Divider divider2; + private AntdUI.Select sltName; + private AntdUI.Label label1; + } +} diff --git a/DHSoftware/Views/SavePositionControl.cs b/DHSoftware/Views/SavePositionControl.cs new file mode 100644 index 0000000..38c78a1 --- /dev/null +++ b/DHSoftware/Views/SavePositionControl.cs @@ -0,0 +1,116 @@ + + +using System.Text.RegularExpressions; +using DH.Commons.Base; +using DH.Commons.Enums; +using DH.Commons.Helper; +using DH.Commons.Models; + +namespace DHSoftware.Views +{ + public partial class SavePositionControl : UserControl + { + private AntdUI.Window window; + public bool submit; + public int Position; + + + public SavePositionControl(AntdUI.Window _window,int _position) + { + this.window = _window; + InitializeComponent(); + Position=_position; + sltName.Items.Clear(); + var targetFields = GetSpecificIOFields(); + foreach(var item in targetFields) + { + sltName.Items.Add(item); + } + iptPosition.Text= Position.ToString(); + // 绑定事件 + BindEventHandler(); + } + + private void BindEventHandler() + { + button_ok.Click += Button_ok_Click; + button_cancel.Click += Button_cancel_Click; + } + + private void Button_cancel_Click(object sender, EventArgs e) + { + submit = false; + this.Dispose(); + } + + private void Button_ok_Click(object sender, EventArgs e) + { + iptPosition.Status = AntdUI.TType.None; + + if (String.IsNullOrEmpty(sltName.Text)) + { + sltName.Status = AntdUI.TType.Error; + AntdUI.Message.warn(window, "请选择工位!", autoClose: 3); + return; + } + + //根据工位查找点位 + PLCItem? pLCItem = ConfigModel.PLCBaseList? + .FirstOrDefault()? + .PLCItemList? + .Where(it=>it.Name==sltName.Text).FirstOrDefault(); + if (pLCItem == null) + { + AntdUI.Message.warn(window, $"未找到{sltName.Text}地址,请检查该地址是否存在于点位表!", autoClose: 3); + return; + } + + PLCItem? pLCItem1 = ConfigModel.GlobalList? + .FirstOrDefault()? + .StartProcessList? + .Where(it=>it.Name ==sltName.Text).FirstOrDefault(); + + if (pLCItem1 == null) + { + pLCItem1=new PLCItem(); + pLCItem1.Name = pLCItem.Name; + pLCItem1.Address = pLCItem.Address; + pLCItem1.Value = iptPosition.Text; + pLCItem1.Type = pLCItem.Type; + pLCItem1.StartExecute = true; + ConfigModel.GlobalList? + .FirstOrDefault()? + .StartProcessList?.Add(pLCItem1); + } + else + { + pLCItem1.Value = iptPosition.Text; + } + + ConfigHelper.SaveConfig(); + AntdUI.Message.success(window, "保存成功!", autoClose: 3); + submit = true; + this.Dispose(); + } + + public static List GetSpecificIOFields() + { + return Enum.GetNames(typeof(EnumPLCOutputIO)) + .Where(name => + Regex.IsMatch(name, @"^工位[1-9]|10$") || // 匹配工位1-10 + name == "OK脉冲" || + name == "NG脉冲") + .OrderBy(name => + { + // 对工位进行数字排序 + if (name.StartsWith("工位")) + { + return int.Parse(name.Substring(2)); + } + return 99; // OK/NG放在最后 + }) + .ToList(); + } + } +} + diff --git a/DHSoftware/Views/SavePositionControl.resx b/DHSoftware/Views/SavePositionControl.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/DHSoftware/Views/SavePositionControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DHSoftware/Views/VisualLocalizationWindow.Designer.cs b/DHSoftware/Views/VisualLocalizationWindow.Designer.cs new file mode 100644 index 0000000..c3941ac --- /dev/null +++ b/DHSoftware/Views/VisualLocalizationWindow.Designer.cs @@ -0,0 +1,379 @@ +namespace DHSoftware.Views +{ + partial class VisualLocalizationWindow + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + titlebar = new AntdUI.PageHeader(); + panel1 = new AntdUI.Panel(); + iptThreshold = new AntdUI.InputNumber(); + label6 = new AntdUI.Label(); + btnSelectBackImg = new AntdUI.Button(); + btnSelectModel = new AntdUI.Button(); + iptBackImg = new AntdUI.Input(); + label3 = new AntdUI.Label(); + iptModel = new AntdUI.Input(); + label2 = new AntdUI.Label(); + sltCameraName = new AntdUI.Select(); + label1 = new AntdUI.Label(); + panel2 = new AntdUI.Panel(); + iptSpeed = new AntdUI.InputNumber(); + label7 = new AntdUI.Label(); + btnSaveImg = new AntdUI.Button(); + btnSavePos = new AntdUI.Button(); + btnReverse = new AntdUI.Button(); + btnForward = new AntdUI.Button(); + btnLocalization = new AntdUI.Button(); + btnAcquisition = new AntdUI.Button(); + iptPosition = new AntdUI.InputNumber(); + label5 = new AntdUI.Label(); + sltDirection = new AntdUI.Select(); + label4 = new AntdUI.Label(); + imageViewerControl1 = new ImageViewerControl(); + panel1.SuspendLayout(); + panel2.SuspendLayout(); + SuspendLayout(); + // + // titlebar + // + titlebar.BackColor = Color.FromArgb(46, 108, 227); + titlebar.DividerShow = true; + titlebar.DividerThickness = 0F; + titlebar.Dock = DockStyle.Top; + titlebar.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); + titlebar.ForeColor = Color.White; + titlebar.Location = new Point(0, 0); + titlebar.Mode = AntdUI.TAMode.Dark; + titlebar.Name = "titlebar"; + titlebar.ShowButton = true; + titlebar.ShowIcon = true; + titlebar.Size = new Size(1210, 37); + titlebar.SubText = "视觉定位系统"; + titlebar.TabIndex = 1; + titlebar.Text = "山东迭慧智能科技有限公司"; + // + // panel1 + // + panel1.Controls.Add(iptThreshold); + panel1.Controls.Add(label6); + panel1.Controls.Add(btnSelectBackImg); + panel1.Controls.Add(btnSelectModel); + panel1.Controls.Add(iptBackImg); + panel1.Controls.Add(label3); + panel1.Controls.Add(iptModel); + panel1.Controls.Add(label2); + panel1.Controls.Add(sltCameraName); + panel1.Controls.Add(label1); + panel1.Dock = DockStyle.Top; + panel1.Location = new Point(0, 37); + panel1.Name = "panel1"; + panel1.Size = new Size(1210, 56); + panel1.TabIndex = 2; + panel1.Text = "panel1"; + // + // iptThreshold + // + iptThreshold.Location = new Point(1094, 6); + iptThreshold.Name = "iptThreshold"; + iptThreshold.Size = new Size(107, 43); + iptThreshold.TabIndex = 15; + iptThreshold.Text = "0"; + // + // label6 + // + label6.BackColor = SystemColors.Window; + label6.Location = new Point(1044, 18); + label6.Name = "label6"; + label6.Size = new Size(57, 23); + label6.TabIndex = 14; + label6.Text = "定位阈值"; + // + // btnSelectBackImg + // + btnSelectBackImg.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnSelectBackImg.Location = new Point(963, 11); + btnSelectBackImg.Name = "btnSelectBackImg"; + btnSelectBackImg.Size = new Size(75, 38); + btnSelectBackImg.TabIndex = 13; + btnSelectBackImg.Text = "打开"; + btnSelectBackImg.Type = AntdUI.TTypeMini.Primary; + // + // btnSelectModel + // + btnSelectModel.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnSelectModel.Location = new Point(525, 9); + btnSelectModel.Name = "btnSelectModel"; + btnSelectModel.Size = new Size(75, 38); + btnSelectModel.TabIndex = 12; + btnSelectModel.Text = "打开"; + btnSelectModel.Type = AntdUI.TTypeMini.Primary; + // + // iptBackImg + // + iptBackImg.Location = new Point(694, 6); + iptBackImg.Name = "iptBackImg"; + iptBackImg.PlaceholderText = "请选择背景图片"; + iptBackImg.Size = new Size(263, 43); + iptBackImg.TabIndex = 10; + // + // label3 + // + label3.BackColor = SystemColors.Window; + label3.Location = new Point(606, 18); + label3.Name = "label3"; + label3.Size = new Size(82, 23); + label3.TabIndex = 9; + label3.Text = "背景图片路径"; + // + // iptModel + // + iptModel.Location = new Point(256, 6); + iptModel.Name = "iptModel"; + iptModel.PlaceholderText = "请选择算法"; + iptModel.Size = new Size(263, 43); + iptModel.TabIndex = 3; + // + // label2 + // + label2.BackColor = SystemColors.Window; + label2.Location = new Point(193, 18); + label2.Name = "label2"; + label2.Size = new Size(57, 23); + label2.TabIndex = 2; + label2.Text = "算法路径"; + // + // sltCameraName + // + sltCameraName.List = true; + sltCameraName.Location = new Point(72, 7); + sltCameraName.MaxCount = 10; + sltCameraName.Name = "sltCameraName"; + sltCameraName.PlaceholderText = "请选择相机"; + sltCameraName.Size = new Size(115, 43); + sltCameraName.TabIndex = 1; + // + // label1 + // + label1.BackColor = SystemColors.Window; + label1.Location = new Point(9, 18); + label1.Name = "label1"; + label1.Size = new Size(57, 23); + label1.TabIndex = 0; + label1.Text = "相机名称"; + // + // panel2 + // + panel2.Controls.Add(iptSpeed); + panel2.Controls.Add(label7); + panel2.Controls.Add(btnSaveImg); + panel2.Controls.Add(btnSavePos); + panel2.Controls.Add(btnReverse); + panel2.Controls.Add(btnForward); + panel2.Controls.Add(btnLocalization); + panel2.Controls.Add(btnAcquisition); + panel2.Controls.Add(iptPosition); + panel2.Controls.Add(label5); + panel2.Controls.Add(sltDirection); + panel2.Controls.Add(label4); + panel2.Dock = DockStyle.Top; + panel2.Location = new Point(0, 93); + panel2.Name = "panel2"; + panel2.Size = new Size(1210, 56); + panel2.TabIndex = 3; + panel2.Text = "panel2"; + // + // iptSpeed + // + iptSpeed.Location = new Point(256, 6); + iptSpeed.Name = "iptSpeed"; + iptSpeed.Size = new Size(107, 43); + iptSpeed.TabIndex = 20; + iptSpeed.Text = "0"; + // + // label7 + // + label7.BackColor = SystemColors.Window; + label7.Location = new Point(193, 18); + label7.Name = "label7"; + label7.Size = new Size(57, 23); + label7.TabIndex = 19; + label7.Text = "转盘速度"; + // + // btnSaveImg + // + btnSaveImg.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnSaveImg.Location = new Point(1126, 11); + btnSaveImg.Name = "btnSaveImg"; + btnSaveImg.Size = new Size(75, 38); + btnSaveImg.TabIndex = 18; + btnSaveImg.Text = "保存图像"; + btnSaveImg.Type = AntdUI.TTypeMini.Primary; + // + // btnSavePos + // + btnSavePos.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnSavePos.Location = new Point(1013, 11); + btnSavePos.Name = "btnSavePos"; + btnSavePos.Size = new Size(75, 38); + btnSavePos.TabIndex = 17; + btnSavePos.Text = "保存定位"; + btnSavePos.Type = AntdUI.TTypeMini.Primary; + // + // btnReverse + // + btnReverse.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnReverse.Location = new Point(904, 11); + btnReverse.Name = "btnReverse"; + btnReverse.Size = new Size(75, 38); + btnReverse.TabIndex = 16; + btnReverse.Text = "转盘反转"; + btnReverse.Type = AntdUI.TTypeMini.Primary; + // + // btnForward + // + btnForward.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnForward.Location = new Point(804, 11); + btnForward.Name = "btnForward"; + btnForward.Size = new Size(75, 38); + btnForward.TabIndex = 15; + btnForward.Text = "转盘正转"; + btnForward.Type = AntdUI.TTypeMini.Primary; + // + // btnLocalization + // + btnLocalization.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnLocalization.Location = new Point(704, 11); + btnLocalization.Name = "btnLocalization"; + btnLocalization.Size = new Size(75, 38); + btnLocalization.TabIndex = 14; + btnLocalization.Text = "开始定位"; + btnLocalization.Type = AntdUI.TTypeMini.Primary; + // + // btnAcquisition + // + btnAcquisition.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + btnAcquisition.Location = new Point(606, 11); + btnAcquisition.Name = "btnAcquisition"; + btnAcquisition.Size = new Size(75, 38); + btnAcquisition.TabIndex = 13; + btnAcquisition.Text = "开始采集"; + btnAcquisition.Type = AntdUI.TTypeMini.Primary; + // + // iptPosition + // + iptPosition.Location = new Point(425, 6); + iptPosition.Name = "iptPosition"; + iptPosition.ReadOnly = true; + iptPosition.Size = new Size(175, 43); + iptPosition.TabIndex = 7; + iptPosition.Text = "0"; + // + // label5 + // + label5.BackColor = SystemColors.Window; + label5.Location = new Point(369, 18); + label5.Name = "label5"; + label5.Size = new Size(59, 23); + label5.TabIndex = 6; + label5.Text = "当前位置"; + // + // sltDirection + // + sltDirection.Items.AddRange(new object[] { "正方向", "反方向" }); + sltDirection.List = true; + sltDirection.Location = new Point(72, 6); + sltDirection.MaxCount = 10; + sltDirection.Name = "sltDirection"; + sltDirection.PlaceholderText = "请选择方向"; + sltDirection.Size = new Size(115, 43); + sltDirection.TabIndex = 5; + // + // label4 + // + label4.BackColor = SystemColors.Window; + label4.Location = new Point(9, 18); + label4.Name = "label4"; + label4.Size = new Size(57, 23); + label4.TabIndex = 4; + label4.Text = "转盘方向"; + // + // imageViewerControl1 + // + imageViewerControl1.Dock = DockStyle.Fill; + imageViewerControl1.Image = null; + imageViewerControl1.Location = new Point(0, 149); + imageViewerControl1.Name = "imageViewerControl1"; + imageViewerControl1.Size = new Size(1210, 558); + imageViewerControl1.TabIndex = 4; + // + // VisualLocalizationWindow + // + AutoScaleDimensions = new SizeF(7F, 17F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1210, 707); + Controls.Add(imageViewerControl1); + Controls.Add(panel2); + Controls.Add(panel1); + Controls.Add(titlebar); + Name = "VisualLocalizationWindow"; + Text = "CCD光学筛选定位系统"; + WindowState = FormWindowState.Maximized; + panel1.ResumeLayout(false); + panel2.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private AntdUI.PageHeader titlebar; + private AntdUI.Panel panel1; + private AntdUI.Select sltCameraName; + private AntdUI.Label label1; + private AntdUI.Input iptModel; + private AntdUI.Label label2; + private AntdUI.Input iptBackImg; + private AntdUI.Label label3; + private AntdUI.Panel panel2; + private AntdUI.Label label4; + private AntdUI.Select sltDirection; + private AntdUI.InputNumber iptPosition; + private AntdUI.Label label5; + private AntdUI.Button btnSelectBackImg; + private AntdUI.Button btnSelectModel; + private AntdUI.Button btnAcquisition; + private AntdUI.Button btnLocalization; + private AntdUI.Button btnReverse; + private AntdUI.Button btnForward; + private AntdUI.Button btnSaveImg; + private AntdUI.Button btnSavePos; + private ImageViewerControl imageViewerControl1; + private AntdUI.InputNumber iptThreshold; + private AntdUI.Label label6; + private AntdUI.InputNumber iptSpeed; + private AntdUI.Label label7; + } +} diff --git a/DHSoftware/Views/VisualLocalizationWindow.cs b/DHSoftware/Views/VisualLocalizationWindow.cs new file mode 100644 index 0000000..87577b8 --- /dev/null +++ b/DHSoftware/Views/VisualLocalizationWindow.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using AntdUI; +using DH.Commons.Base; +using DH.Commons.Enums; +using DH.Devices.Camera; +using HalconDotNet; +using OpenCvSharp.Extensions; +using Sunny.UI; +using static System.Net.Mime.MediaTypeNames; + +namespace DHSoftware.Views +{ + public partial class VisualLocalizationWindow : Window + { + //采集状态 + private volatile bool isCapturing=false; + //定位状态 + private volatile bool isLocationing = false; + //算法 + HDevEngine MyEngine = new HDevEngine(); + HDevProcedure Procedure; + HDevProcedureCall ProcCall; + //背景图 + HImage backImage = new HImage(); + //当前相机 + Do3ThinkCamera Do3ThinkCamera = new Do3ThinkCamera(); + //定时器 + private System.Threading.Timer Timer; + + + public VisualLocalizationWindow() + { + InitializeComponent(); + Load += VisualLocalizationWindow_Load; + btnSelectModel.Click += BtnSelectModel_Click; + btnSelectBackImg.Click += BtnSelectBackImg_Click; + btnAcquisition.Click += BtnAcquisition_Click; + btnLocalization.Click += BtnLocalization_Click; + btnForward.MouseDown += BtnForward_MouseDown; + btnForward.MouseUp += BtnForward_MouseUp; + btnReverse.MouseDown += BtnReverse_MouseDown; + btnReverse.MouseUp += BtnReverse_MouseUp; + btnSaveImg.Click += BtnSaveImg_Click; + btnSavePos.Click += BtnSavePos_Click; + + + } + + + private void BtnSavePos_Click(object? sender, EventArgs e) + { + var form = new SavePositionControl(this,Convert.ToInt32(iptPosition.Text)) { Size = new Size(300, 300) }; + AntdUI.Modal.open(new AntdUI.Modal.Config(this, "", form, TType.None) + { + BtnHeight = 0, + }); + if (form.submit) + { + //保存用户操作到文件 + VisualLocalization visualLocalization = new VisualLocalization(); + visualLocalization.CameraName = sltCameraName.Text; + visualLocalization.ModelPath=iptModel.Text; + visualLocalization.ImgPath=iptBackImg.Text; + visualLocalization.Threshold=iptThreshold.Text; + visualLocalization.Direction=sltDirection.Text; + visualLocalization.Speed=iptSpeed.Text; + visualLocalization.SaveToFile("VisualLocalization.json"); + } + } + + /// + /// 保存图像 + /// + /// + /// + private void BtnSaveImg_Click(object? sender, EventArgs e) + { + if (!isCapturing) + { + AntdUI.Message.warn(this, $"未开始采集,无法保存图像!", autoClose: 3); + return; + } + Bitmap bitmap =imageViewerControl1.GetCurrentSnapshot(); + using (SaveFileDialog saveDialog = new SaveFileDialog()) + { + saveDialog.Title = "保存图像文件"; + saveDialog.Filter = "JPEG 图像|*.jpg"; + saveDialog.FilterIndex = 0; + saveDialog.AddExtension = true; + saveDialog.OverwritePrompt = true; + + if (saveDialog.ShowDialog() == DialogResult.OK) + { + bitmap.Save(saveDialog.FileName, ImageFormat.Jpeg); + AntdUI.Message.success(this, $"图像保存成功!", autoClose: 3); + } + else + { + AntdUI.Message.warn(this, $"取消图像保存操作!", autoClose: 3); + } + } + } + + /// + /// 定位 + /// + /// + /// + private void BtnLocalization_Click(object? sender, EventArgs e) + { + if (!isCapturing) + { + AntdUI.Message.warn(this, $"未开始采集,无法开始定位!", autoClose: 3); + return; + } + if (!isLocationing) + { + bool direction =sltDirection.SelectedIndex==0?true:false; + if (string.IsNullOrEmpty(iptSpeed.Text)) + { + AntdUI.Message.warn(this, $"请输入速度!", autoClose: 3); + return; + } + int speed = 0; + + try + { + bool isValid = int.TryParse(iptSpeed.Text, out speed); + if (!isValid) + { + AntdUI.Message.warn(this, $"输入的速度不是有效值!", autoClose: 3); + return; + } + } + catch (Exception ex) { } + MainWindow.Instance.PLC.TurnSpeed(speed); + MainWindow.Instance.PLC.TurnDirection(direction); + MainWindow.Instance.PLC.TurnStart(true); + isLocationing = true; + btnLocalization.Text = "结束定位"; + btnLocalization.Type = TTypeMini.Warn; + } + else + { + MainWindow.Instance.PLC.TurnStart(false); + iptPosition.Text= MainWindow.Instance.PLC.ReadVisionPos().ToString(); + isLocationing = false; + btnLocalization.Text = "开始定位"; + btnLocalization.Type = TTypeMini.Primary; + } + + } + + /// + /// 采集 + /// + /// + /// + private void BtnAcquisition_Click(object? sender, EventArgs e) + { + if (!isCapturing) + { + if (string.IsNullOrWhiteSpace(sltCameraName.Text)) + { + AntdUI.Message.warn(this, $"请选择相机!", autoClose: 3); + return; + } + if (string.IsNullOrWhiteSpace(iptModel.Text)) + { + AntdUI.Message.warn(this, $"请选择算法!", autoClose: 3); + return; + } + if (string.IsNullOrWhiteSpace(iptBackImg.Text)) + { + AntdUI.Message.warn(this, $"请选择背景图片!", autoClose: 3); + return; + } + + // 加载HALCON模型 + if (!File.Exists(iptModel.Text)) + { + AntdUI.Message.warn(this, $"算法文件不存在!", autoClose: 3); + return; + } + if (!File.Exists(iptBackImg.Text)) + { + AntdUI.Message.warn(this, $"图片文件不存在!", autoClose: 3); + return; + } + + //获取背景图 + backImage = new HImage(); + backImage.ReadImage(iptBackImg.Text); + // 从完整路径获取过程名称 + string procedureName = Path.GetFileNameWithoutExtension(iptModel.Text); + string procedureDir = Path.GetDirectoryName(iptModel.Text); + + // 重新初始化HALCON引擎 + MyEngine.SetProcedurePath(procedureDir); + Procedure = new HDevProcedure(procedureName); + ProcCall = new HDevProcedureCall(Procedure); + + if (MainWindow.Instance.PLC.Connected) + { + //启用视觉定位 + MainWindow.Instance.PLC.VisionPos(true); + } + else + { + AntdUI.Message.warn(this, $"未连接PLC,无法视觉定位!", autoClose: 3); + return; + } + Do3ThinkCamera=MainWindow.Instance.Cameras.Where(it=>it.CameraName==sltCameraName.Text).FirstOrDefault()??new Do3ThinkCamera(); + Do3ThinkCamera.OnHImageOutput += OnCameraHImageOutput; + Timer = new System.Threading.Timer(CaptureLoop, null, 0, 50); + isCapturing = true; + btnAcquisition.Text = "结束采集"; + btnAcquisition.Type = TTypeMini.Warn; + } + else + { + if (isLocationing) + { + AntdUI.Message.warn(this, $"定位未结束,不能结束采集!", autoClose: 3); + return; + } + + MainWindow.Instance.PLC.VisionPos(false); + Do3ThinkCamera.OnHImageOutput -= OnCameraHImageOutput; + Timer?.Dispose(); + isCapturing = false; + btnAcquisition.Text = "开始采集"; + btnAcquisition.Type = TTypeMini.Primary; + } + + + + } + + /// + /// 触发 + /// + /// + private void CaptureLoop(object? state) + { + Do3ThinkCamera.Snapshot(); + } + + /// + /// 反转抬起 + /// + /// + /// + private void BtnReverse_MouseUp(object? sender, MouseEventArgs e) + { + if (MainWindow.Instance.PLC.Connected) + { + MainWindow.Instance.PLC.TurnStart(false); + iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); + } + else + { + AntdUI.Message.warn(this, $"未连接PLC!", autoClose: 3); + } + } + + /// + /// 反转按下 + /// + /// + /// + private void BtnReverse_MouseDown(object? sender, MouseEventArgs e) + { + if (MainWindow.Instance.PLC.Connected) + { + //开启转盘 + if (string.IsNullOrEmpty(iptSpeed.Text)) + { + AntdUI.Message.warn(this, $"请输入速度!", autoClose: 3); + return; + } + int speed = 0; + + try + { + bool isValid = int.TryParse(iptSpeed.Text, out speed); + if (!isValid) + { + AntdUI.Message.warn(this, $"输入的速度不是有效值!", autoClose: 3); + return; + } + } + catch (Exception ex) { } + + MainWindow.Instance.PLC.TurnSpeed(speed); + MainWindow.Instance.PLC.TurnDirection(false); + MainWindow.Instance.PLC.TurnStart(true); + iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); + } + else + { + AntdUI.Message.warn(this, $"未连接PLC!", autoClose: 3); + } + } + + /// + /// 正转抬起 + /// + /// + /// + private void BtnForward_MouseUp(object? sender, MouseEventArgs e) + { + if (MainWindow.Instance.PLC.Connected) + { + MainWindow.Instance.PLC.TurnStart(false); + iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); + } + else + { + AntdUI.Message.warn(this, $"未连接PLC!", autoClose: 3); + } + } + + /// + /// 正转按下 + /// + /// + /// + private void BtnForward_MouseDown(object? sender, MouseEventArgs e) + { + if (MainWindow.Instance.PLC.Connected) + { + //开启转盘 + if (string.IsNullOrEmpty(iptSpeed.Text)) + { + AntdUI.Message.warn(this, $"请输入速度!", autoClose: 3); + return; + } + int speed = 0; + + try + { + bool isValid = int.TryParse(iptSpeed.Text, out speed); + if (!isValid) + { + AntdUI.Message.warn(this, $"输入的速度不是有效值!", autoClose: 3); + return; + } + } + catch (Exception ex) { } + + MainWindow.Instance.PLC.TurnSpeed(speed); + MainWindow.Instance.PLC.TurnDirection(true); + MainWindow.Instance.PLC.TurnStart(true); + iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); + } + else + { + AntdUI.Message.warn(this, $"未连接PLC!", autoClose: 3); + } + } + + /// + /// 选择背景图 + /// + /// + /// + private void BtnSelectBackImg_Click(object? sender, EventArgs e) + { + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + // 设置对话框标题 + openFileDialog.Title = "选择背景图片"; + // 限制文件后缀为 .hdvp + openFileDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp"; + // 禁止多选 + openFileDialog.Multiselect = false; + + // 显示对话框并等待用户操作 + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + string filePath = openFileDialog.FileName; + + iptBackImg.Text = filePath; + + } + } + } + + /// + /// 选择算法 + /// + /// + /// + private void BtnSelectModel_Click(object? sender, EventArgs e) + { + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + // 设置对话框标题 + openFileDialog.Title = "选择算法文件"; + // 限制文件后缀为 .hdvp + openFileDialog.Filter = "算法文件 (*.hdvp)|*.hdvp"; + // 禁止多选 + openFileDialog.Multiselect = false; + + // 显示对话框并等待用户操作 + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + string filePath = openFileDialog.FileName; + + iptModel.Text = filePath; + + } + } + } + + /// + /// 加载事件 + /// + /// + /// + private void VisualLocalizationWindow_Load(object? sender, EventArgs e) + { + sltDirection.SelectedIndex = 0; + sltCameraName.Items.Clear(); + if (MainWindow.Instance.Cameras?.Count > 0) + { + foreach(var cam in MainWindow.Instance.Cameras) + { + sltCameraName.Items.Add(cam.CameraName); + } + } + else + { + AntdUI.Message.warn(this, $"未找到启用相机!", autoClose: 3); + } + + + } + + /// + /// 窗体对象实例 + /// + private static VisualLocalizationWindow _instance; + + internal static VisualLocalizationWindow Instance + { + get + { + if (_instance == null || _instance.IsDisposed) + _instance = new VisualLocalizationWindow(); + return _instance; + } + } + + /// + /// 相机回调 + /// + /// + /// + /// + private void OnCameraHImageOutput(DateTime dt, CameraBase camera, MatSet imageSet) + { + + imageViewerControl1.Image = imageSet._mat.ToBitmap(); + HObject obj = OpenCVHelper.MatToHImage(imageSet._mat); + HImage hImage = HalconHelper.ConvertHObjectToHImage(obj); + // 调用 ProcCall 的方法 + ProcCall.SetInputIconicParamObject("INPUT_Image", hImage); // 将图像输入Proc + + ProcCall.SetInputIconicParamObject("BackGroundPic", backImage); + + ProcCall.SetInputCtrlParamTuple("DistThreshold", Convert.ToInt32(iptThreshold.Text)); + ProcCall.Execute(); + double nNUm = ProcCall.GetOutputCtrlParamTuple("OUTPUT_Flag"); + + if (nNUm == 0) + { + MainWindow.Instance.PLC.TurnStart(false); + iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); + } + } + } +} diff --git a/DHSoftware/Views/VisualLocalizationWindow.resx b/DHSoftware/Views/VisualLocalizationWindow.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/DHSoftware/Views/VisualLocalizationWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file