diff --git a/Doc/2Z443 Distributed Controller补偿校准.pdf b/Doc/2Z443 Distributed Controller补偿校准.pdf new file mode 100644 index 0000000..64027c9 Binary files /dev/null and b/Doc/2Z443 Distributed Controller补偿校准.pdf differ diff --git a/HSI_HexagonMI_EF3/ACS/ACSCL_x64.LIB b/HSI_HexagonMI_EF3/ACS/ACSCL_x64.LIB deleted file mode 100644 index 4260762..0000000 Binary files a/HSI_HexagonMI_EF3/ACS/ACSCL_x64.LIB and /dev/null differ diff --git a/HSI_HexagonMI_EF3/version.h b/HSI_HexagonMI_EF3/version.h index bb35120..649320e 100644 --- a/HSI_HexagonMI_EF3/version.h +++ b/HSI_HexagonMI_EF3/version.h @@ -12,5 +12,5 @@ #define HSI_VERSION_REVNUM #define HSI_VERSION_BUILD_DATE _T(__DATE__ ) #define HSI_VERSION_BUILD_TIME _T(__TIME__ ) -#define HSI_FILE_DESCRIPTION "2025.03.05 / 16:50 " -#define HSI_FILE_CSDESCRIPTION _T("2025.03.05 / 16:50 ") +#define HSI_FILE_DESCRIPTION "2025.03.11 / 17:19 " +#define HSI_FILE_CSDESCRIPTION _T("2025.03.11 / 17:19 ") diff --git a/HSI_SEVENOCEAN_EF1_CsTest/bin/Debug/Config/EF3_Config.ini b/HSI_SEVENOCEAN_EF1_CsTest/bin/Debug/Config/EF3_Config.ini index b316dc1..92ed18c 100644 Binary files a/HSI_SEVENOCEAN_EF1_CsTest/bin/Debug/Config/EF3_Config.ini and b/HSI_SEVENOCEAN_EF1_CsTest/bin/Debug/Config/EF3_Config.ini differ diff --git a/HexcalMC/Base/BaseFunction.cs b/HexcalMC/Base/BaseFunction.cs index 1c60f15..8d57e0e 100644 --- a/HexcalMC/Base/BaseFunction.cs +++ b/HexcalMC/Base/BaseFunction.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Drawing; @@ -10,9 +9,7 @@ using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using System.Windows.Forms; -using System.Net.NetworkInformation; namespace HexcalMC.Base { @@ -21,6 +18,7 @@ namespace HexcalMC.Base public class RichTextUnit { public static float MSize = 16; + public static void SetFont(RichTextBox mRichTextBox, Color mColor, bool bBold = false, float size = 16) { mRichTextBox.SelectionColor = mColor; @@ -70,6 +68,7 @@ namespace HexcalMC.Base RichTextUnit.SetFont(mRichTextBox, Color.DarkRed, false, size); } break; + case '>': { RichTextUnit.SetFont(mRichTextBox, Color.Blue, false, size); @@ -77,12 +76,14 @@ namespace HexcalMC.Base RichTextUnit.SetFont(mRichTextBox, Color.Black, false, size); } break; + case '/': { RichTextUnit.SetFont(mRichTextBox, Color.Blue, false, size); mRichTextBox.SelectedText = strText[i].ToString(); } break; + case '=': { if (strText[i + 1] == '"') @@ -93,6 +94,7 @@ namespace HexcalMC.Base } } break; + case '"': { RichTextUnit.SetFont(mRichTextBox, Color.Blue, false, size); @@ -103,12 +105,14 @@ namespace HexcalMC.Base RichTextUnit.SetFont(mRichTextBox, Color.DarkRed, false, size); } break; + case '!': { RichTextUnit.SetFont(mRichTextBox, Color.Green, false, size); mRichTextBox.SelectedText = strText[i].ToString(); } break; + case '\r': { if (strText[i + 1] == '\n') @@ -118,13 +122,14 @@ namespace HexcalMC.Base } } break; + default: mRichTextBox.SelectedText = strText[i].ToString(); break; } } } - #endregion + #endregion PARSE THROUGH TEXT DATA mRichTextBox.SelectedText = Environment.NewLine; })); @@ -143,6 +148,7 @@ namespace HexcalMC.Base RichTextUnit.SetFont(mRichTextBox, Color.Red, false, size); mRichTextBox.SelectedText = strText[i].ToString(); break; + case '0': case '1': case '2': @@ -156,16 +162,18 @@ namespace HexcalMC.Base RichTextUnit.SetFont(mRichTextBox, Color.DeepSkyBlue, false, size); mRichTextBox.SelectedText = strText[i].ToString(); break; + default: RichTextUnit.SetFont(mRichTextBox, Color.White, false, size); mRichTextBox.SelectedText = strText[i].ToString(); break; } } - #endregion + #endregion PARSE THROUGH TEXT DATA })); } } + public class MyBase { #region 内存回收 @@ -198,7 +206,7 @@ namespace HexcalMC.Base } } - #endregion + #endregion 内存回收 public static void KillSoftware(string strSoftwareName) { @@ -316,8 +324,8 @@ namespace HexcalMC.Base if (mColor == new Color()) { if (str.ToUpper().Contains("ERROR") || str.ToUpper().Contains("错误") || - str.ToUpper().Contains("出错") || str.ToUpper().Contains("EXCEPTION") || - str.ToUpper().Contains("异常") || str.ToUpper().Contains("失败")) + str.ToUpper().Contains("出错") || str.ToUpper().Contains("EXCEPTION") || + str.ToUpper().Contains("异常") || str.ToUpper().Contains("失败")) { setColor = Color.Red; } @@ -331,7 +339,7 @@ namespace HexcalMC.Base setColor = mColor; } - string strText = str + Environment.NewLine; //DateTime.Now.ToString("HH:mm:ss.fff") + "--" + + string strText = str + Environment.NewLine; //DateTime.Now.ToString("HH:mm:ss.fff") + "--" + rtb.SelectionStart = rtb.TextLength; if (string.IsNullOrEmpty(str)) RichTextUnit.SetText(rtb, " " + Environment.NewLine, setColor, false, 14); @@ -353,7 +361,7 @@ namespace HexcalMC.Base } /// - /// 写debug文件,记录程序过程 + /// 写debug文件,记录程序过程 /// /// 要写入日志的内容 public static void TraceWriteLine(string str) @@ -369,7 +377,6 @@ namespace HexcalMC.Base } } - public static string InputBox(string caption, string hint, string defaultTxt, string btn1 = "OK", string btn2 = "Cancel", char strstyle = '*', bool bShowData = false) { @@ -407,7 +414,7 @@ namespace HexcalMC.Base btnok.Height = 30; btnok.Parent = inputForm; btnok.Text = btn1; - inputForm.AcceptButton = btnok; //回车响应 + inputForm.AcceptButton = btnok; //回车响应 btnok.DialogResult = DialogResult.OK; Button btncancal = new Button(); @@ -477,7 +484,7 @@ namespace HexcalMC.Base btnYes.Text = btnName1; btnYes.DialogResult = DialogResult.Yes; - errorForm.AcceptButton = btnYes; //回车响应 + errorForm.AcceptButton = btnYes; //回车响应 Button btnNo = new Button(); btnNo.Location = new System.Drawing.Point(190, 210); @@ -485,7 +492,7 @@ namespace HexcalMC.Base btnNo.Parent = errorForm; btnNo.Text = btnName2; btnNo.DialogResult = DialogResult.No; - errorForm.AcceptButton = btnNo; //回车响应 + errorForm.AcceptButton = btnNo; //回车响应 Button btncancal = new Button(); btncancal.Location = new System.Drawing.Point(350, 210); @@ -493,7 +500,7 @@ namespace HexcalMC.Base btncancal.Parent = errorForm; btncancal.Text = btnName3; btncancal.DialogResult = DialogResult.Cancel; - errorForm.AcceptButton = btncancal; //回车响应 + errorForm.AcceptButton = btncancal; //回车响应 try { btnYes.Select(); @@ -513,17 +520,17 @@ namespace HexcalMC.Base [DllImport("User32.dll")] private static extern bool SetCursorPos(int x, int y); - static public void SetCursorPosXy(int dx, int dy) + public static void SetCursorPosXy(int dx, int dy) { SetCursorPos(dx, dy); } - static public void SetCursorPosXy(Point point) + public static void SetCursorPosXy(Point point) { System.Windows.Forms.Cursor.Position = point; } - #endregion + #endregion 界面控件操作 /// /// CopyFiles 函数 @@ -563,7 +570,9 @@ namespace HexcalMC.Base public class HardwareInfoBase { - /// 获取指定驱动器的空间总大小(单位为B) ,只需输入代表驱动器的字母即可 + /// + /// 获取指定驱动器的空间总大小(单位为B) ,只需输入代表驱动器的字母即可 + /// public static long GetHardDiskSpace(string strHardDiskName) { long totalSize = new long(); @@ -581,7 +590,9 @@ namespace HexcalMC.Base return totalSize; } - /// 获取指定驱动器的剩余空间总大小(单位为B) ,只需输入代表驱动器的字母即可 + /// + /// 获取指定驱动器的剩余空间总大小(单位为B) ,只需输入代表驱动器的字母即可 + /// public static long GetHardDiskFreeSpace(string strHardDiskName) { long freeSpace = new long(); @@ -599,19 +610,25 @@ namespace HexcalMC.Base return freeSpace; } - /// 获取指定驱动器的剩余空间总大小(单位为K) ,只需输入代表驱动器的字母即可 + /// + /// 获取指定驱动器的剩余空间总大小(单位为K) ,只需输入代表驱动器的字母即可 + /// public static long GetHardDiskFreeSpace_K(string strHardDiskName) { return GetHardDiskFreeSpace(strHardDiskName) / 1024; } - /// 获取指定驱动器的剩余空间总大小(单位为M) ,只需输入代表驱动器的字母即可 + /// + /// 获取指定驱动器的剩余空间总大小(单位为M) ,只需输入代表驱动器的字母即可 + /// public static long GetHardDiskFreeSpace_M(string strHardDiskName) { return GetHardDiskFreeSpace_K(strHardDiskName) / 1024; } - /// 获取指定驱动器的剩余空间总大小(单位为G) ,只需输入代表驱动器的字母即可 + /// + /// 获取指定驱动器的剩余空间总大小(单位为G) ,只需输入代表驱动器的字母即可 + /// public static long GetHardDiskFreeSpace_G(string strHardDiskName) { return GetHardDiskFreeSpace_M(strHardDiskName) / 1024; @@ -644,7 +661,6 @@ namespace HexcalMC.Base return false; } - [DllImport("kernel32")] private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath); @@ -744,7 +760,7 @@ namespace HexcalMC.Base WritePrivateProfileString(section, key, strWrite, path); } - #endregion + #endregion 写INI参数 //=====================//=====================//=====================Read data @@ -759,7 +775,7 @@ namespace HexcalMC.Base /// 要读取的string类型内容 public static string ReadString(string path, string section, string key) { - // 每次从ini中读取多少字节 // section=配置节,key=键名,temp=上面,path=路径 + // 每次从ini中读取多少字节 // section=配置节,key=键名,temp=上面,path=路径 System.Text.StringBuilder temp = new System.Text.StringBuilder(255); GetPrivateProfileString(section, key, "", temp, 255, path); String str = temp.ToString(); @@ -814,10 +830,9 @@ namespace HexcalMC.Base return false; } - #endregion + #endregion 读INI参数 } - //==================================================================================================SoundBase /// /// 声音播放类 @@ -852,7 +867,7 @@ namespace HexcalMC.Base // FileDialog.AddExtension = true; // FileDialog.CheckFileExists = true; // FileDialog.CheckPathExists = true; - // //the next sentence must be in single line + // //the next sentence must be in single line // FileDialog.Filter = "WAV文件(*.wav)|*.wav|MP3文件(*.mp3)|*.mp3|VCD文件(*.dat)|*.dat|Audio文件(*.avi)|*.avi|所有文件 (*.*)|*.*"; // FileDialog.DefaultExt = "*.mp3"; // if (FileDialog.ShowDialog() == DialogResult.OK) @@ -867,7 +882,7 @@ namespace HexcalMC.Base // FileDialog.AddExtension = true; // FileDialog.CheckFileExists = true; // FileDialog.CheckPathExists = true; - // //the next sentence must be in single line + // //the next sentence must be in single line // FileDialog.Filter = "WAV文件(*.wav)|*.wav|MP3文件(*.mp3)|*.mp3|VCD文件(*.dat)|*.dat|Audio文件(*.avi)|*.avi|所有文件 (*.*)|*.*"; // FileDialog.DefaultExt = "*.mp3"; // if (FileDialog.ShowDialog() == DialogResult.OK) @@ -914,12 +929,11 @@ namespace HexcalMC.Base } } - //================================================================================================== /// /// 数据格式化或校验检测 /// - class FormatCheckBase + internal class FormatCheckBase { /// /// 检测是否为十六进制字符串,长度不够在前面添加0 @@ -1019,14 +1033,14 @@ namespace HexcalMC.Base return true; } - /// - /// 每隔n个字符插入一个字符 - /// - /// 从右边开始插入 - /// 源字符串 - /// 间隔字符数 - /// 待插入值 - /// 待补充值,最后不足间隔字符数时,用此字符补齐;Supplement=""时,不补任何字符。 + /// + /// 每隔n个字符插入一个字符 + /// + /// 从右边开始插入 + /// 源字符串 + /// 间隔字符数 + /// 待插入值 + /// 待补充值,最后不足间隔字符数时,用此字符补齐;Supplement=""时,不补任何字符。 /// 返回新生成字符串 public static string InsertFormat(bool isRight, string input, int interval, string value, string supplement) { @@ -1125,7 +1139,7 @@ namespace HexcalMC.Base #region - static readonly byte[] AuchCrcHi = new byte[] + private static readonly byte[] AuchCrcHi = new byte[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, @@ -1161,7 +1175,7 @@ namespace HexcalMC.Base #region - static readonly byte[] AuchCrcLo = new byte[] + private static readonly byte[] AuchCrcLo = new byte[] { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, @@ -1218,19 +1232,18 @@ namespace HexcalMC.Base } } - //==================================================================================================ConvertBase /// /// 数据转换类 /// - class ConvertBase + internal class ConvertBase { #region 图像 <--> 数组 /// /// 图像转换为Byte数组 /// - static public byte[] ImageToByteArray(Image imageIn) + public static byte[] ImageToByteArray(Image imageIn) { MemoryStream ms = new MemoryStream(); imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Png); @@ -1240,7 +1253,7 @@ namespace HexcalMC.Base /// /// Byte数组转换为图像 /// - static public Image ByteArrayToImage(byte[] byteArrayIn) + public static Image ByteArrayToImage(byte[] byteArrayIn) { MemoryStream ms = new MemoryStream(byteArrayIn); Image returnImage = Image.FromStream(ms); @@ -1442,9 +1455,9 @@ namespace HexcalMC.Base for (int i = 0; i < str.Length; i++) { if (! - (((str[i] >= '0') && (str[i] <= '9')) || - ((str[i] >= 'a') && (str[i] <= 'f')) || - ((str[i] >= 'A') && (str[i] <= 'F'))) + (((str[i] >= '0') && (str[i] <= '9')) || + ((str[i] >= 'a') && (str[i] <= 'f')) || + ((str[i] >= 'A') && (str[i] <= 'F'))) ) { return false; @@ -1644,12 +1657,12 @@ namespace HexcalMC.Base #region C++转换程序(C#里面有完整的转换函数) - //十进制转二制 + //十进制转二制 public static string DtoB(int d) { //Console.WriteLine(Convert.ToString(5,2)) string str = ""; - //判断该数如果小于2,则直接输出 + //判断该数如果小于2,则直接输出 if (d < 2) { str = d.ToString(); @@ -1724,7 +1737,7 @@ namespace HexcalMC.Base return o; } - //十进制转十六进制 + //十进制转十六进制 public static string DtoX(int d) { string x = ""; @@ -1749,7 +1762,7 @@ namespace HexcalMC.Base do { c = d / 16; - m[i++] = Chang(d % 16); //判断是否大于10,如果大于10,则转换为A~F的格式 + m[i++] = Chang(d % 16); //判断是否大于10,如果大于10,则转换为A~F的格式 d = c; } while (c >= 16); @@ -1763,7 +1776,7 @@ namespace HexcalMC.Base return x; } - //判断是否为10~15之间的数,如果是则进行转换 + //判断是否为10~15之间的数,如果是则进行转换 public static string Chang(int d) { string x = ""; @@ -1772,21 +1785,27 @@ namespace HexcalMC.Base case 10: x = "A"; break; + case 11: x = "B"; break; + case 12: x = "C"; break; + case 13: x = "D"; break; + case 14: x = "E"; break; + case 15: x = "F"; break; + default: x = d.ToString(); break; @@ -1807,12 +1826,11 @@ namespace HexcalMC.Base } } - //==================================================================================================TcpBase /// /// 网络通讯通用类函数库 /// - class TcpBase + internal class TcpBase { /// /// 用CMD命令测试网络连接状态 @@ -1851,12 +1869,11 @@ namespace HexcalMC.Base } } - //==================================================================================================MyMath /// /// 数学函数库(算法) /// - class MyMath + internal class MyMath { public static double GetMax(double[] datas) { @@ -1981,36 +1998,52 @@ namespace HexcalMC.Base { case 0: return (data & 0x0001) > 0; + case 1: return (data & 0x0002) > 0; + case 2: return (data & 0x0004) > 0; + case 3: return (data & 0x0008) > 0; + case 4: return (data & 0x0010) > 0; + case 5: return (data & 0x0020) > 0; + case 6: return (data & 0x0040) > 0; + case 7: return (data & 0x0080) > 0; + case 8: return (data & 0x0100) > 0; + case 9: return (data & 0x0200) > 0; + case 10: return (data & 0x0400) > 0; + case 11: return (data & 0x0800) > 0; + case 12: return (data & 0x1000) > 0; + case 13: return (data & 0x2000) > 0; + case 14: return (data & 0x4000) > 0; + case 15: return (data & 0x8000) > 0; + default: return false; } @@ -2022,37 +2055,52 @@ namespace HexcalMC.Base { case 0: return (data & 0x0100) > 0; + case 1: return (data & 0x0200) > 0; + case 2: return (data & 0x0400) > 0; + case 3: return (data & 0x0800) > 0; + case 4: return (data & 0x1000) > 0; + case 5: return (data & 0x2000) > 0; + case 6: return (data & 0x4000) > 0; + case 7: return (data & 0x8000) > 0; case 8: return (data & 0x0001) > 0; + case 9: return (data & 0x0002) > 0; + case 10: return (data & 0x0004) > 0; + case 11: return (data & 0x0008) > 0; + case 12: return (data & 0x0010) > 0; + case 13: return (data & 0x0020) > 0; + case 14: return (data & 0x0040) > 0; + case 15: return (data & 0x0080) > 0; + default: return false; } @@ -2077,7 +2125,6 @@ namespace HexcalMC.Base return bResult; } - /// /// 获取单精度浮点数(float)数据 /// @@ -2187,6 +2234,7 @@ namespace HexcalMC.Base } break; + case 10: foreach (byte b in arrLength) { @@ -2194,6 +2242,7 @@ namespace HexcalMC.Base } break; + case 16: foreach (byte b in arrLength) { @@ -2220,7 +2269,6 @@ namespace HexcalMC.Base } } - //====================================================================== /// /// 字节数组(byte 8位)转化为ushort(16位)数组 @@ -2282,7 +2330,6 @@ namespace HexcalMC.Base return ushorts; } - //====================================================================== /// /// 字节数组(byte 8位)转化为short(16位)数组 @@ -2355,13 +2402,13 @@ namespace HexcalMC.Base } } - class CodeDfn + internal class CodeDfn { public const string BlankSpace = " "; public const string StrEnter = "\r\n"; } - class PlcMath + internal class PlcMath { #region S7协议数据处理(以字节为基础) @@ -2412,7 +2459,7 @@ namespace HexcalMC.Base if (byteData.Length < (dataAddr - startAddr)) { MessageBox.Show("错误:获取S7字节数据, 要读取的偏移地址超出数组长度,偏移=" + (dataAddr - startAddr) + ", 数组长度=" + - byteData.Length); + byteData.Length); return 0; } else @@ -2547,6 +2594,7 @@ namespace HexcalMC.Base } break; + case 10: foreach (byte b in arrLength) { @@ -2554,6 +2602,7 @@ namespace HexcalMC.Base } break; + case 16: foreach (byte b in arrLength) { @@ -2561,6 +2610,7 @@ namespace HexcalMC.Base } break; + default: foreach (byte b in arrLength) { @@ -2582,7 +2632,6 @@ namespace HexcalMC.Base #endregion - #region ModbusTCP/FinsTCP协议数据处理(以字为基础) /// @@ -2708,36 +2757,52 @@ namespace HexcalMC.Base { case 0: return (data & 0x0001) > 0; + case 1: return (data & 0x0002) > 0; + case 2: return (data & 0x0004) > 0; + case 3: return (data & 0x0008) > 0; + case 4: return (data & 0x0010) > 0; + case 5: return (data & 0x0020) > 0; + case 6: return (data & 0x0040) > 0; + case 7: return (data & 0x0080) > 0; + case 8: return (data & 0x0100) > 0; + case 9: return (data & 0x0200) > 0; + case 10: return (data & 0x0400) > 0; + case 11: return (data & 0x0800) > 0; + case 12: return (data & 0x1000) > 0; + case 13: return (data & 0x2000) > 0; + case 14: return (data & 0x4000) > 0; + case 15: return (data & 0x8000) > 0; + default: return false; } @@ -2749,37 +2814,52 @@ namespace HexcalMC.Base { case 0: return (data & 0x0100) > 0; + case 1: return (data & 0x0200) > 0; + case 2: return (data & 0x0400) > 0; + case 3: return (data & 0x0800) > 0; + case 4: return (data & 0x1000) > 0; + case 5: return (data & 0x2000) > 0; + case 6: return (data & 0x4000) > 0; + case 7: return (data & 0x8000) > 0; case 8: return (data & 0x0001) > 0; + case 9: return (data & 0x0002) > 0; + case 10: return (data & 0x0004) > 0; + case 11: return (data & 0x0008) > 0; + case 12: return (data & 0x0010) > 0; + case 13: return (data & 0x0020) > 0; + case 14: return (data & 0x0040) > 0; + case 15: return (data & 0x0080) > 0; + default: return false; } @@ -2913,6 +2993,7 @@ namespace HexcalMC.Base } break; + case 10: foreach (byte b in arrLength) { @@ -2920,6 +3001,7 @@ namespace HexcalMC.Base } break; + case 16: foreach (byte b in arrLength) { @@ -2927,6 +3009,7 @@ namespace HexcalMC.Base } break; + default: foreach (byte b in arrLength) { @@ -2949,11 +3032,8 @@ namespace HexcalMC.Base #endregion } - - - class Internet + internal class Internet { - public static bool IsIpReachable(string ipAddress) { try diff --git a/HexcalMC/Base/DebugDfn.cs b/HexcalMC/Base/DebugDfn.cs index 3c2e731..439129c 100644 --- a/HexcalMC/Base/DebugDfn.cs +++ b/HexcalMC/Base/DebugDfn.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; namespace HexcalMC.Base @@ -20,7 +16,6 @@ namespace HexcalMC.Base public static RichTextBox textBox_Msg; - //================================================================= public static void StartDebugObj() { @@ -122,26 +117,21 @@ namespace HexcalMC.Base } } - #endregion + #endregion 信息显示 } public class Errors { /// - /// 0:无故障 PLC按位求 1对应101 - /// 1:通讯故障 - /// 2:扫描枪读取故障 - /// 3: 控制柜急停被按下 - /// 4:外部急停被按下 - /// 5:PCL急停,辊道在CMM测量时上料 + /// 0:无故障 PLC按位求 1对应101 1:通讯故障 2:扫描枪读取故障 + /// 3: 控制柜急停被按下 4:外部急停被按下 5:PCL急停,辊道在CMM测量时上料 /// public static int iErrors = 0; - /// 101:通讯故障 - /// 102:扫描枪读取故障 - /// 103: 控制柜急停被按下 - /// 104:外部急停被按下 - /// 105:PCL急停,辊道在CMM测量时上料 + + /// 101:通讯故障 102:扫描枪读取故障 + /// 103: 控制柜急停被按下 104:外部急停被按下 105:PCL急停,辊道在CMM测量时上料 public static bool bCommError = false; + public static bool bReaderError = false; public static bool bEStop_Controller = false; public static bool bEStop_Out = false; @@ -158,7 +148,7 @@ namespace HexcalMC.Base try { if (Errors.ErrorWrite == null) - Errors.ErrorWrite = new StreamWriter(DebugDfn.StrDebugSavePath + "Error_(" + DateTime.Now.ToString("yyyy-MM-dd") + ").txt", true); + Errors.ErrorWrite = new StreamWriter(DebugDfn.StrDebugSavePath + "Error_(" + DateTime.Now.ToString("yyyy-MM-dd") + ").txt", true); Errors.ErrorWrite.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + strError); Errors.ErrorWrite.Flush(); } @@ -179,4 +169,4 @@ namespace HexcalMC.Base { } } } -} +} \ No newline at end of file diff --git a/HexcalMC/Base/Editor3D/Editor3D.cs b/HexcalMC/Base/Editor3D/Editor3D.cs index c44e6a7..0318a99 100644 --- a/HexcalMC/Base/Editor3D/Editor3D.cs +++ b/HexcalMC/Base/Editor3D/Editor3D.cs @@ -1,5 +1,4 @@ - -/***************************************************************************** +/***************************************************************************** This class has been written by Elmü (elmue@gmx.de) @@ -7,18 +6,18 @@ Check if you have the latest version on: https://www.codeproject.com/Articles/5293980/Graph3D-A-Windows-Forms-Render-Control-in-Csharp ======================================= - + IMPORTANT: This class has been written by a software developer with 45 years programming exprience. This class is optimized for the highest possible speed in every line of it's code. If you found a fork of this code on Github or elsewhere you do NOT have the original high quality code! -This code with 5000 lines is extremely complex and there is a very high risk that a beginner has +This code with 5000 lines is extremely complex and there is a very high risk that a beginner has broken this code by modifying it without properly understanding it. ======================================= - + NAMING CONVENTIONS which allow to see the type of a variable immediately without having to jump to the variable definition: - + cName for class definitions tName for type definitions eName for enum definitions @@ -35,7 +34,7 @@ NAMING CONVENTIONS which allow to see the type of a variable immediately without r_Name for Rectangle s_Name for strings o_Name for objects - + s8_Name for signed 8 Bit (sbyte) s16_Name for signed 16 Bit (short) s32_Name for signed 32 Bit (int) @@ -47,5102 +46,5110 @@ NAMING CONVENTIONS which allow to see the type of a variable immediately without An additional "m" is prefixed for all member variables (e.g. ms_String) - *****************************************************************************/ // Print render speed to Trace output // ATTENTION: The times in the very first trace output are always wrong because of JIT compilation delays. #if DEBUG -// #define DEBUG_SPEED +// #define DEBUG_SPEED #endif using System; -using System.Text; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; -using System.ComponentModel; -using System.Collections.Generic; using System.Globalization; +using System.Text; using System.Windows.Forms; -using System.Diagnostics; namespace Plot3D { - /// - /// ATTENTION: This class is not thread safe. - /// Call all functions only from the GUI thread or use Control.Invoke() - /// - public class Editor3D : UserControl - { - #region enums + /// + /// ATTENTION: This class is not thread safe. Call all functions only from the GUI thread or use Control.Invoke() + /// + public class Editor3D : UserControl + { + #region enums - public enum eColorScheme - { - Autumn = 0, - Cool, - Copper, - Hot, - Hsv, - Monochrome, - Pink, - Rainbow_Sweep, // This creates a 100% cyclic rainbow with 1536 colors. The end color is the same as the start color. - Rainbow_Bright, // This creates a rainbow without magenta with 1024 colors. It goes from blue to red. - Rainbow_Dark, // This is similar to RainbowBright, but darker and only with 64 colors. - Spring, - Summer, - Winter, + public enum eColorScheme + { + Autumn = 0, + Cool, + Copper, + Hot, + Hsv, + Monochrome, + Pink, + Rainbow_Sweep, // This creates a 100% cyclic rainbow with 1536 colors. The end color is the same as the start color. + Rainbow_Bright, // This creates a rainbow without magenta with 1024 colors. It goes from blue to red. + Rainbow_Dark, // This is similar to RainbowBright, but darker and only with 64 colors. + Spring, + Summer, + Winter, Green } - public enum eRaster - { - Off, // turn off coordinate system - MainAxes, // draw only solid main axes for X,Y,Z - Raster, // draw additional thin raster lines - Labels, // draw additional labels in quadrant 3 - } - - /// - /// If a function has an asymetric range for X and Y as demo "Callback" a separate normalization - /// would always lead to a square X,Y pane which would be a distortion for the relation between X and Y values. - /// MaintainXY guarantees that the relation between X and Y values is maintained. - /// MaintainXYZ additionally guarantees that the relation between X, Y and Z values is maintained. - /// - public enum eNormalize - { - Separate, // Normalize X,Y,Z separately (use this for discrete values) - MaintainXY, // Normalize X,Y without changing their relation (use this for math functions) - MaintainXYZ, // Normalize X,Y,Z without changing their relation (use this for math functions) - } - - public enum eScatterShape - { - // 0 is invalid - Circle = 1, - Square = 2, - Triangle = 3, - // Star = 4, you can implement your own shapes here - } - - /// - /// Used internally for coordinate system - /// - public enum eCoord - { - // for axis in coordinate system - X = 0, - Y = 1, - Z = 2, - Invalid, - } - - /// - /// These flags define which axes are allowed to be mirrored. - /// For user objects option "All" is used. - /// Coordinate system axes and raster lines are mirrored individually. - /// - [FlagsAttribute] - public enum eMirror - { - None = 0, - X = 1, - Y = 2, - Z = 4, - XY = X | Y, - XZ = X | Z, - YZ = Y | Z, - All = X | Y | Z, - } - - /// - /// Mouse operations - /// - public enum eMouseAction - { - None = 0, - Move, // Move the coordinate system with the mouse - Theta, // Elevate the coordinate system with the mouse - Phi, // Rotate the coordinate system with the mouse - ThetaAndPhi, // Elevate + Rotate the coordinate system with the mouse - Rho, // Zoom in / out - SelectObj, // Select a 3D object or call the selection callback function if defined - Callback, // Call the selection callback function if defined - } - - public enum eSelEvent - { - MouseDown, // Inform selection callback function that the pre-defined mouse button goes down - MouseDrag, // Inform selection callback function that the mouse is moved while the pre-defined mouse button is down - MouseUp, // Inform selection callback function that the pre-defined mouse button goes up - } - - public enum ePolygonMode - { - Fill, // Fill polygons with Brush - Lines, // Draw only polygon border lines - } - - /// - /// This enum defines of which type is a cObject3D - /// - public enum eObjType - { - Point, - Line, - Shape, - Polygon, - } - - /// - /// These flags are used to filter the 3D objects that are selected. - /// - [FlagsAttribute] - public enum eSelType - { - Line = 0x1, - Shape = 0x2, - Polygon = 0x4, - All = 0x7, - } - - [FlagsAttribute] - public enum eTooltip - { - Off = 0x0, // Tooltip is disabled - UserText = 0x1, // Show user defined tooltip text that has been set in cPoint3D.Tooltip - Coord = 0x2, // Show coordinates X,Y,Z of cPoint3D - All = 0x3, // Show all - } - - // This enum is to get the maximum speed out of your CPU. - // Re-calculation is done only if required. - [FlagsAttribute] - private enum eRecalculate - { - Nothing = 0x0, // repaint objects after changed selection --> recalculate nothing - Objects = 0x1, // Projection, Brush, LineWidth,... has changed --> recalculate 3D objects - CoordSystem = 0x2, // The coordinate system must be recalculated --> recalculate Min/Max and Coord System - AddRemove = 0x4, // Draw Objects have been added or removed --> refresh lists and recalculate all - } - - /// - /// These are the possible return values of the Selection callback. - /// See description of SelectionCallback() at the end of this class. - /// - public enum eInvalidate - { - NoChange, // The callback has not modified anything --> do nothing. - Invalidate, // Calls Invalidate() to redraw what is required depending on the flags in me_Recalculate. - CoordSystem, // The coordinate system will be recalculated, then Invalidate() is called. - // Use this option after moving a 3D object with the mouse. - } - - /// - /// This defines with which mouse button the user controls rotation and elevation - /// - public enum eMouseCtrl - { - L_Theta_R_Phi, // Left mouse button vertical: Theta, Right mouse button horizontal: Phi - L_Theta_L_Phi, // Left mouse button vertical: Theta, Left mouse button horizontal: Phi - M_Theta_M_Phi, // Middle mouse button vertical: Theta, Middle mouse button horizontal: Phi - } - - /// - /// 3D object properties that the user may change are stored in the Undo Buffer - /// - public enum eUndoProp - { - // cObject3D - Tag, - Selected, - CanSelect, - - // cPoint3D - X, - Y, - Z, - Tooltip, - - // cLine3D - Pen, - Width, - - // cShape3D - Shape, - Radius, - - // cShape3D + cPolygon3D - Brush, - } - - public enum eLegendPos - { - BottomLeft, // The main axis legends are drawn in the bottom left corner of the graph pane. (recommended) - AxisEnd, // The main axis legends are drawn at the end of the axis and rotate with the graph. (not recommended) - } - - #endregion - - // ================== PUBLIC ================= - - #region cObject3D - - /// - /// Base class for cPoint3D, cShape3D, cLine3D, cPloygon3D - /// - public abstract class cObject3D - { - public Editor3D mi_Inst; - protected Object mo_Tag; - protected bool mb_Selected; - protected bool mb_CanSelect = true; - protected cPoint3D[] mi_Points; // This instance must never be replaced by a new array! - protected int ms32_Col = -1; - protected int ms32_Row = -1; - - public virtual void Restore(eUndoProp e_Property, Object o_Value) - { - switch (e_Property) - { - case eUndoProp.Tag: mo_Tag = o_Value; break; - case eUndoProp.Selected: mb_Selected = (bool)o_Value; break; - case eUndoProp.CanSelect: mb_CanSelect = (bool)o_Value; break; - default: throw new ArgumentException(); - } - } - - // -------------------------------------------------- - - /// - /// Gets the type of this cObject3D - /// - public virtual eObjType ObjType - { - get { throw new NotImplementedException(); } - } - - /// - /// 1 point for cPoint3D - /// 1 point for cShape3D - /// 2 points for cLine3D - /// n points for cPolygon3D - /// - public cPoint3D[] Points - { - get { return mi_Points; } - // set must NOT be allowed here !!!! - } - - /// - /// The column in the grid. - /// This is only valid for Polygons and Scatter circles created by cSurfaceData, otherwise -1 - /// - public int Column - { - get { return ms32_Col; } // the grid column cannot be changed - } - - /// - /// The row in the grid. - /// This is only valid for Polygons and Scatter circles created by cSurfaceData, otherwise -1 - /// - public int Row - { - get { return ms32_Row; } // the grid row cannot be changed - } - - /// - /// Here you can store your private data which is passed in Selection.Callback to your code. - /// - public Object Tag - { - get { return mo_Tag; } - set - { - if (mo_Tag == value) - return; - - if (mi_Inst != null) - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Tag, mo_Tag, value); - - mo_Tag = value; - } - } - - /// - /// The draw object has been selected by ALT + click - /// - public virtual bool Selected - { - get { return mb_Selected; } - set - { - if (mb_Selected == value) - return; - - if (mi_Inst != null) - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Selected, mb_Selected, value); - - mb_Selected = value; - // me_Recalculate needs no change here - } - } - - /// - /// Defines if the user is allowed to select this object - /// - public virtual bool CanSelect - { - get { return mb_CanSelect; } - set - { - if (mb_CanSelect == value) - return; - - if (mi_Inst != null) - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.CanSelect, mb_CanSelect, value); - - mb_CanSelect = value; - } - } - - /// - /// Move the object in the 3D space - /// - public void Move(double d_DeltaX, double d_DeltaY, double d_DeltaZ) - { - foreach (cPoint3D i_Point in mi_Points) - { - i_Point.X += d_DeltaX; - i_Point.Y += d_DeltaY; - i_Point.Z += d_DeltaZ; - } - } - } - - #endregion - - #region cPoint3D - - public class cPoint3D : cObject3D - { - private double md_X; - private double md_Y; - private double md_Z; - private String ms_Tooltip; - - public override void Restore(eUndoProp e_Property, Object o_Value) - { - switch (e_Property) - { - case eUndoProp.X: md_X = (double)o_Value; break; - case eUndoProp.Y: md_Y = (double)o_Value; break; - case eUndoProp.Z: md_Z = (double)o_Value; break; - case eUndoProp.Tooltip: ms_Tooltip = (String)o_Value; break; - default: base.Restore(e_Property, o_Value); break; - } - } - - // -------------------------------------------------- - - /// - /// Gets the type of this cObject3D - /// - public override eObjType ObjType - { - get { return eObjType.Point; } - } - - /// - /// 3D coordinate - /// - public double X - { - get { return md_X; } - set - { - if (md_X == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.X, md_X, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - - if (value < mi_Inst.mi_Bounds.X.Min || value > mi_Inst.mi_Bounds.X.Max) - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; - } - md_X = value; - } - } - - /// - /// 3D coordinate - /// - public double Y - { - get { return md_Y; } - set - { - if (md_Y == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Y, md_Y, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - - if (value < mi_Inst.mi_Bounds.Y.Min || value > mi_Inst.mi_Bounds.Y.Max) - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; - } - md_Y = value; - } - } - - /// - /// 3D coordinate - /// - public double Z - { - get { return md_Z; } - set - { - if (md_Z == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Z, md_Z, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - - if (value < mi_Inst.mi_Bounds.Z.Min || value > mi_Inst.mi_Bounds.Z.Max) - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; - } - md_Z = value; - } - } - - /// - /// Optional tooltip text to be displayed when the mouse is over this point - /// - public String Tooltip - { - get { return ms_Tooltip; } - set - { - if (ms_Tooltip == value) - return; - - if (mi_Inst != null) - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Tooltip, ms_Tooltip, value); - - ms_Tooltip = value; - } - } - - // -------------------------------------------------- - - /// - /// Constructor - /// s_ToolTip is displayed when eTooltip.UserText is enabled - /// In o_Tag you can store any data that you need when the Selection callback is called after the user has selected a point. - /// - public cPoint3D(double d_X, double d_Y, double d_Z, String s_ToolTip = null, Object o_Tag = null) - { - md_X = d_X; - md_Y = d_Y; - md_Z = d_Z; - mo_Tag = o_Tag; - mi_Points = new cPoint3D[] { this }; - - if (s_ToolTip != null) - ms_Tooltip = s_ToolTip.Trim(); - } - - // =================== used for coordinate system =================== - - public cPoint3D Clone() - { - return new cPoint3D(md_X, md_Y, md_Z, ms_Tooltip, Tag); - } - - public bool CoordEquals(cPoint3D i_Point) - { - return md_X == i_Point.md_X && md_Y == i_Point.md_Y && md_Z == i_Point.md_Z; - } - - public double GetValue(eCoord e_Coord) - { - switch (e_Coord) - { - case eCoord.X: return md_X; - case eCoord.Y: return md_Y; - case eCoord.Z: return md_Z; - default: return 0; - } - } - - public void SetValue(eCoord e_Coord, double d_Value) - { - switch (e_Coord) - { - case eCoord.X: X = d_Value; break; - case eCoord.Y: Y = d_Value; break; - case eCoord.Z: Z = d_Value; break; - } - } - - // For debugging in Visual Studio - public override string ToString() - { - return String.Format("cPoint3D (X={0}, Y={1}, Z={2})", FormatDouble(md_X), FormatDouble(md_Y), FormatDouble(md_Z)); - } - } - - #endregion - - #region cLine3D - - public class cLine3D : cObject3D - { - private Pen mi_Pen; - private int ms32_Width; - private int ms32_Parts; - - public override void Restore(eUndoProp e_Property, Object o_Value) - { - switch (e_Property) - { - case eUndoProp.Pen: mi_Pen = (Pen)o_Value; break; - case eUndoProp.Width: ms32_Width = (int)o_Value; break; - default: base.Restore(e_Property, o_Value); break; - // ms32_Parts is constant - } - } - - // -------------------------------------------------- - - /// - /// Gets the type of this cObject3D - /// - public override eObjType ObjType - { - get { return eObjType.Line; } - } - - /// - /// The line width in pixels - /// - public int Width - { - get { return ms32_Width; } - set - { - if (ms32_Width == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Width, ms32_Width, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - ms32_Width = value; - } - } - - /// - /// If Pen is null, a Pen from the ColorScheme will be used. - /// The width of the Pen does not matter. It will be set to the property Width. - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Pen assigned here. - /// If you externally change for example Pen.Color the Undo will not work correctly. - /// Always assign a different instance of Pen to avoid this. - /// - public Pen Pen - { - get { return mi_Pen; } - set - { - if (mi_Pen == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Pen, mi_Pen, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - mi_Pen = value; - } - } - - /// - /// Parts of different color in multi-color lines - /// - public int ColorParts - { - get { return ms32_Parts; } - } - - /// - /// Constructor - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Pen assigned here. - /// If you externally change for example Pen.Color the Undo will not work correctly. - /// Always assign a different instance of Pen to cLine3D.Pen to avoid this. - /// - public cLine3D(cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, int s32_Parts, Object o_Tag = null) - { - mi_Points = new cPoint3D[] { i_Start, i_End }; - ms32_Width = s32_Width; - mi_Pen = i_Pen; - ms32_Parts = s32_Parts; - mo_Tag = o_Tag; - } - - // For debugging in Visual Studio - public override string ToString() - { - return "cLine3D from " + mi_Points[0] + " to " + mi_Points[1]; - } - } - - #endregion - - #region cShape3D - - public class cShape3D : cObject3D - { - private eScatterShape me_Shape; - private int ms32_Radius; - private Brush mi_Brush; - - public override void Restore(eUndoProp e_Property, Object o_Value) - { - switch (e_Property) - { - case eUndoProp.Shape: me_Shape = (eScatterShape)o_Value; break; - case eUndoProp.Radius: ms32_Radius = (int) o_Value; break; - case eUndoProp.Brush: mi_Brush = (Brush) o_Value; break; - default: base.Restore(e_Property, o_Value); break; - } - } - - // -------------------------------------------------- - - /// - /// Gets the type of this cObject3D - /// - public override eObjType ObjType - { - get { return eObjType.Shape; } - } - - /// - /// The type of shape to be painted - /// - public eScatterShape Shape - { - get { return me_Shape; } - set - { - if (me_Shape == value) - return; - - // Circle and rectangle are drawn by the framework --> no recalculation required. - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Shape, me_Shape, value); - - if (value != eScatterShape.Circle && value != eScatterShape.Square) - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - me_Shape = value; - } - } - - /// - /// The radius of the circle, square or triangle - /// - public int Radius - { - get { return ms32_Radius; } - set - { - if (ms32_Radius == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Radius, ms32_Radius, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - ms32_Radius = value; - } - } - - /// - /// The color of the Shape or null to use a color from the ColorScheme - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Brush assigned here. - /// If you externally change for example SolidBrush.Color the Undo will not work correctly. - /// Always assign a different instance of Brush to avoid this. - /// - public Brush Brush - { - get { return mi_Brush; } - set - { - if (mi_Brush == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Brush, mi_Brush, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - mi_Brush = value; - } - } - - /// - /// The shape is selected - /// - public override bool Selected - { - get { return mi_Points[0].Selected; } - set { mi_Points[0].Selected = value; } // me_Recalculate needs no change - } - - /// - /// Defines if the user is allowed to select this shape - /// - public override bool CanSelect - { - get { return mi_Points[0].CanSelect; } - set { mi_Points[0].CanSelect = value; } - } - - /// - /// Constructor - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Brush assigned here. - /// If you externally change for example SolidBrush.Color the Undo will not work correctly. - /// Always assign a different instance of Brush to cShape3D.Brush to avoid this. - /// - public cShape3D(int s32_Col, int s32_Row, cPoint3D i_Point, eScatterShape e_Shape, int s32_Radius, Brush i_Brush, Object o_Tag = null) - { - ms32_Col = s32_Col; - ms32_Row = s32_Row; - i_Point.Tag = o_Tag; - mi_Points = new cPoint3D[] { i_Point }; - me_Shape = e_Shape; - ms32_Radius = s32_Radius; - mi_Brush = i_Brush; - mo_Tag = o_Tag; - } - - // For debugging in Visual Studio - public override string ToString() - { - return "cShape3D " + me_Shape + " at " + mi_Points[0]; - } - } - - #endregion - - #region cPolygon3D - - public class cPolygon3D : cObject3D - { - private Brush mi_Brush; - - public override void Restore(eUndoProp e_Property, Object o_Value) - { - switch (e_Property) - { - case eUndoProp.Brush: mi_Brush = (Brush)o_Value; break; - default: base.Restore(e_Property, o_Value); break; - // ms32_Col is constant - // ms32_Row is constant - } - } - - // -------------------------------------------------- - - /// - /// Gets the type of this cObject3D - /// - public override eObjType ObjType - { - get { return eObjType.Polygon; } - } - - /// - /// The color of the Polygon or null to use a color from the ColorScheme - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Brush assigned here. - /// If you externally change for example SolidBrush.Color the Undo will not work correctly. - /// Always assign a different instance of Brush to avoid this. - /// - public Brush Brush - { - get { return mi_Brush; } - set - { - if (mi_Brush == value) - return; - - if (mi_Inst != null) - { - mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Brush, mi_Brush, value); - mi_Inst.me_Recalculate |= eRecalculate.Objects; - } - mi_Brush = value; - } - } - - /// - /// Constructor - /// IMPORTANT: - /// If you use the UndoBuffer you must never modify any property of the Brush assigned here. - /// If you externally change for example SolidBrush.Color the Undo will not work correctly. - /// Always assign a different instance of Brush to cPolygon3D.Brush to avoid this. - /// - public cPolygon3D(int s32_Col, int s32_Row, Brush i_Brush, params cPoint3D[] i_Points) - { - if (i_Points.Length < 3) - throw new ArgumentException("At least 3 points are required to draw a polygon."); - - mi_Points = i_Points; - mi_Brush = i_Brush; - ms32_Col = s32_Col; - ms32_Row = s32_Row; - } - - // For debugging in Visual Studio - public override string ToString() - { - return "cPolygon3D (" + mi_Points.Length + " points)"; - } - } - - #endregion - - // --------------------- - - #region cAxis - - /// - /// Used for the main axes and raster lines of the coordinate system - /// - [Serializable] - [TypeConverter(typeof(ExpandableObjectConverter))] - public class cAxis - { - private Editor3D mi_Inst; - private eCoord me_Coord; - private Pen mi_RasterPen; // Raster lines (brigther) - private Pen mi_AxisPen; // Main coordinate axis (darker) - private SolidBrush mi_LegendBrush; // Label text - private String ms_LegendText; - private bool mb_Mirror; - private bool mb_IncludeZero; - - public cAxis(Editor3D i_Inst, eCoord e_Coord, Color c_Color) - { - mi_Inst = i_Inst; - me_Coord = e_Coord; - Color = c_Color; - Reset(); - } - - public void Reset() - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - ms_LegendText = null; - mb_Mirror = false; - mb_IncludeZero = me_Coord == eCoord.Z; - } - - /// - /// The color of the axis lines and the legend. - /// This change will become visible the next time you call Invalidate() - /// Not modified by Reset() - /// - public Color Color - { - get { return mi_AxisPen.Color; } - set - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - mi_LegendBrush = new SolidBrush(value); - mi_AxisPen = new Pen(value, 3); - mi_RasterPen = new Pen(BrightenColor(value), 1); - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; - } - } - - /// - /// Internally used to draw raster lines (brighter) - /// - [Browsable(false)] - public Pen RasterPen - { - get { return mi_RasterPen; } - } - - /// - /// Internally used to draw main coordinates (darker) - /// - [Browsable(false)] - public Pen AxisPen - { - get { return mi_AxisPen; } - } - - /// - /// Internally used for label text - /// - [Browsable(false)] - public SolidBrush LegendBrush - { - get { return mi_LegendBrush; } - } - - /// - /// Here you can add an optional legend which is displayed for the axis. - /// With the property Editor3D.LegendPos you can define where the legend is drawn. - /// If the string is null or empty, no legend will be displayed for this axis. - /// This change will become visible the next time you call Invalidate() - /// - public String LegendText - { - get { return ms_LegendText; } - set { ms_LegendText = value; } - } - - /// - /// In the default rotation angle the axis values are normally increasing - /// from right to left (X), back to front (Y) and bottom to top (Z). - /// If Mirror = true they will decrease instead of increase. - /// This change will become visible the next time you call Invalidate() - /// - public bool Mirror - { - get { return mb_Mirror; } - set - { - if (mb_Mirror == value) - return; - - if (me_Coord == eCoord.Z) - throw new NotImplementedException("Mirroring the Z axis is not implemented because there are multiple issues and it does not make sense to draw the Z values reverse."); - - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - mb_Mirror = value; - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - } - - /// - /// Example: If the axis has values from 4 to 10 and - /// IncludeZero = false --> the coordinate axis has a range from 4 to 10 - /// IncludeZero = true --> the coordinate axis has a range from 0 to 10 (default) - /// ATTENTION: This setting is ignored if the coordinate system is not drawn (eRaster.Off) - /// This change will become visible the next time you call Invalidate() - /// - public bool IncludeZero - { - get { return mb_IncludeZero; } - set - { - if (mb_IncludeZero == value) - return; - - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - mb_IncludeZero = value; - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - } - } - - #endregion - - #region cSelection - - /// - /// This class controls the user selection of points, lines, shapes and polygons - /// - [Serializable] - [TypeConverter(typeof(ExpandableObjectConverter))] - public class cSelection - { - private Editor3D mi_Inst; - private bool mb_Enabled; - private bool mb_MultiSel; - private bool mb_SinglePoints; - private delSelectHandler mf_Callback; - private Color mc_HighlightColor = Color.Empty; - private Brush mi_HighlightBrush; - private Pen mi_HighlightPen; - - public cSelection(Editor3D i_Inst) - { - mi_Inst = i_Inst; - } - - /// - /// This property defines if the user is allowed to select 3D objects. - /// The selection will only be visible if HighlightColor has also been set. - /// The callback will only be called if Callback has also been assigned. - /// - public bool Enabled - { - get { return mb_Enabled; } - set { mb_Enabled = value; } - } - - /// - /// This defines the color with which selected draw objects / points are painted. - /// If you pass Color.Empty draw objects will not be highlighted although they are selected! - /// This change will become visible the next time you call Invalidate() - /// - public Color HighlightColor - { - set - { - if (value.A > 0) - { - mi_HighlightBrush = new SolidBrush(value); - mi_HighlightPen = new Pen (value); - mi_HighlightPen.StartCap = LineCap.Round; - mi_HighlightPen.EndCap = LineCap.Round; - } - else - { - mi_HighlightBrush = null; - mi_HighlightPen = null; - } - mc_HighlightColor = value; - } - get - { - return mc_HighlightColor; - } - } - - [Browsable(false)] - public Pen HighlightPen - { - get - { - if (mb_Enabled) return mi_HighlightPen; - else return null; - } - } - - [Browsable(false)] - public Brush HighlightBrush - { - get - { - if (mb_Enabled) return mi_HighlightBrush; - else return null; - } - } - - /// - /// true --> allow selection of multiple 3D objects at once - /// false --> allow only selection of a single 3D object at a time - /// This setting will be ignored when a Callback is assigned. - /// The callback is responsible for any selection changes! - /// - public bool MultiSelect - { - get { return mb_MultiSel; } - set { mb_MultiSel = value; } - } - - /// - /// Enables selection of single points. - /// true --> the user can only select single points of a polygon or the end points of a line. - /// false --> the user can only select an entire polygon or an entire line. - /// For scatter shapes this setting does not matter because a scatter shape corresponds to one point. - /// IMPORTANT: Selecting entire polygons makes only sense if Fill mode is used. Otherwise polygons are transparent - /// and a click would hit the background behind the polygon, so this setting is ignored for polygons in Line mode. - /// This change will become visible the next time you call Invalidate() - /// - public bool SinglePoints - { - get { return mb_SinglePoints; } - set - { - mb_SinglePoints = value; - - // A mix of selected lines or polygons and selected points is possible but the user will be confused. - DeSelectAll(); - } - } - - /// - /// IMPORTANT: Read the detailed comment of function SelectionCallback() at the end of this class. - /// You can set null here to turn off the callback. - /// - [Browsable(false)] - public delSelectHandler Callback - { - set { mf_Callback = value; } - get { return mf_Callback; } - } - - /// - /// Returns selected cLine3D, cShape3D or cPoygon3D objects. - /// Multiple enums can be combined. Example: GetSelectedObjects(eSelType.Line | eSelType.Shape) - /// NOTE: For Shape3D the selection of the shape itself and it's point is always the same. - /// - public cObject3D[] GetSelectedObjects(eSelType e_SelType) - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - - List i_List = new List(); - foreach (cDrawObj i_Object in mi_Inst.mi_UserObjects) - { - if (!i_Object.Selected || (i_Object.SelType & e_SelType) == 0) - continue; - - i_List.Add(i_Object.mi_Object3D); - } - return i_List.ToArray(); - } - - /// - /// Gets the points that the user may select individually if Selection.SinglePoints = true - /// and gets all points of a Line3D or Polygon3D if it is selected. - /// Multiple enums can be combined. Example: GetSelectedPoints(eSelType.Line | eSelType.Shape) - /// NOTE: The returned array contains only unique points. - /// NOTE: For Shape3D the selection of the shape itself and it's point is always the same. - /// - public cPoint3D[] GetSelectedPoints(eSelType e_SelType) - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - - List i_Unique = new List(); - - foreach (cDrawObj i_Object in mi_Inst.mi_UserObjects) - { - if ((i_Object.SelType & e_SelType) == 0) - continue; - - // If the object itself is selected return all it's points, no matter if the points are selected or not. - if (i_Object.Selected) - { - foreach (cPoint i_Point in i_Object.mi_Points) - { - if (!i_Unique.Contains(i_Point.mi_P3D)) - i_Unique.Add (i_Point.mi_P3D); - } - } - else // Object not selected --> check if single points of the object are selected - { - foreach (cPoint i_Point in i_Object.mi_Points) - { - if (i_Point.mi_P3D.Selected && - !i_Unique.Contains(i_Point.mi_P3D)) - i_Unique.Add (i_Point.mi_P3D); - } - } - } - return i_Unique.ToArray(); - } - - /// - /// Remove the selection from all draw objects - /// This change will become visible the next time you call Invalidate() - /// - public void DeSelectAll() - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - - foreach (cDrawObj i_Obj in mi_Inst.mi_UserObjects) - { - i_Obj.Selected = false; - - foreach (cPoint i_Point in i_Obj.mi_Points) - { - i_Point.mi_P3D.Selected = false; - } - } - - mi_Inst.mi_UndoBuffer.Store(); - } - } - - #endregion - - #region cUndoBuffer - - /// - /// This public base class is exposed to the user. - /// The implementation is in cUndoImpl. - /// - [Serializable] - [TypeConverter(typeof(ExpandableObjectConverter))] - public abstract class cUndoBuffer - { - /// - /// Enable the Undo Buffer only if you need the Undo functionality. - /// By default Undo / Redo is disabled. - /// - public virtual bool Enabled - { - get; - set; - } - - /// - /// Clears the Undo Buffer. - /// - public virtual void Clear() - { - throw new NotImplementedException(); - } - - /// - /// Undo the last user operation. - /// return false if Undo is not available. - /// This is also called when hitting CTRL + Z while the 3D Editor has the keyboard focus. - /// - public virtual bool Undo() - { - throw new NotImplementedException(); - } - - /// - /// Redo the last user operation. - /// return false if Redo is not available. - /// This is also called when hitting CTRL + Y while the 3D Editor has the keyboard focus. - /// - public virtual bool Redo() - { - throw new NotImplementedException(); - } - } - - #endregion - - #region cColorScheme - - /// - /// Pens and Brushes are GDI+ objects which must not be created on the fly in Draw() - /// For speed optimization these are created only once and stored in this class. - /// - public class cColorScheme - { - // ========================= STATIC =========================== - - public static Color[] GetSchema(eColorScheme e_Scheme) - { - Byte[,] u8_RGB; - switch (e_Scheme) - { - case eColorScheme.Green: return new Color[] { Color.Green }; - case eColorScheme.Rainbow_Sweep: return CalcRainbow(6); // all colors, also magenta - case eColorScheme.Rainbow_Bright: return CalcRainbow(4); // from red to blue, no magenta - case eColorScheme.Monochrome: return new Color[] { Color.Goldenrod }; - case eColorScheme.Autumn: u8_RGB = new byte[,] { { 255, 0, 0 }, { 255, 4, 0 }, { 255, 8, 0 }, { 255, 12, 0 }, { 255, 16, 0 }, { 255, 20, 0 }, { 255, 24, 0 }, { 255, 28, 0 }, { 255, 32, 0 }, { 255, 36, 0 }, { 255, 40, 0 }, { 255, 45, 0 }, { 255, 49, 0 }, { 255, 53, 0 }, { 255, 57, 0 }, { 255, 61, 0 }, { 255, 65, 0 }, { 255, 69, 0 }, { 255, 73, 0 }, { 255, 77, 0 }, { 255, 81, 0 }, { 255, 85, 0 }, { 255, 89, 0 }, { 255, 93, 0 }, { 255, 97, 0 }, { 255, 101, 0 }, { 255, 105, 0 }, { 255, 109, 0 }, { 255, 113, 0 }, { 255, 117, 0 }, { 255, 121, 0 }, { 255, 125, 0 }, { 255, 130, 0 }, { 255, 134, 0 }, { 255, 138, 0 }, { 255, 142, 0 }, { 255, 146, 0 }, { 255, 150, 0 }, { 255, 154, 0 }, { 255, 158, 0 }, { 255, 162, 0 }, { 255, 166, 0 }, { 255, 170, 0 }, { 255, 174, 0 }, { 255, 178, 0 }, { 255, 182, 0 }, { 255, 186, 0 }, { 255, 190, 0 }, { 255, 194, 0 }, { 255, 198, 0 }, { 255, 202, 0 }, { 255, 206, 0 }, { 255, 210, 0 }, { 255, 215, 0 }, { 255, 219, 0 }, { 255, 223, 0 }, { 255, 227, 0 }, { 255, 231, 0 }, { 255, 235, 0 }, { 255, 239, 0 }, { 255, 243, 0 }, { 255, 247, 0 }, { 255, 251, 0 }, { 255, 255, 0 } }; break; - case eColorScheme.Cool: u8_RGB = new byte[,] { { 0, 255, 255 }, { 4, 251, 255 }, { 8, 247, 255 }, { 12, 243, 255 }, { 16, 239, 255 }, { 20, 235, 255 }, { 24, 231, 255 }, { 28, 227, 255 }, { 32, 223, 255 }, { 36, 219, 255 }, { 40, 215, 255 }, { 45, 210, 255 }, { 49, 206, 255 }, { 53, 202, 255 }, { 57, 198, 255 }, { 61, 194, 255 }, { 65, 190, 255 }, { 69, 186, 255 }, { 73, 182, 255 }, { 77, 178, 255 }, { 81, 174, 255 }, { 85, 170, 255 }, { 89, 166, 255 }, { 93, 162, 255 }, { 97, 158, 255 }, { 101, 154, 255 }, { 105, 150, 255 }, { 109, 146, 255 }, { 113, 142, 255 }, { 117, 138, 255 }, { 121, 134, 255 }, { 125, 130, 255 }, { 130, 125, 255 }, { 134, 121, 255 }, { 138, 117, 255 }, { 142, 113, 255 }, { 146, 109, 255 }, { 150, 105, 255 }, { 154, 101, 255 }, { 158, 97, 255 }, { 162, 93, 255 }, { 166, 89, 255 }, { 170, 85, 255 }, { 174, 81, 255 }, { 178, 77, 255 }, { 182, 73, 255 }, { 186, 69, 255 }, { 190, 65, 255 }, { 194, 61, 255 }, { 198, 57, 255 }, { 202, 53, 255 }, { 206, 49, 255 }, { 210, 45, 255 }, { 215, 40, 255 }, { 219, 36, 255 }, { 223, 32, 255 }, { 227, 28, 255 }, { 231, 24, 255 }, { 235, 20, 255 }, { 239, 16, 255 }, { 243, 12, 255 }, { 247, 8, 255 }, { 251, 4, 255 }, { 255, 0, 255 } }; break; - case eColorScheme.Copper: u8_RGB = new byte[,] { { 0, 0, 0 }, { 5, 3, 2 }, { 10, 6, 4 }, { 15, 9, 6 }, { 20, 13, 8 }, { 25, 16, 10 }, { 30, 19, 12 }, { 35, 22, 14 }, { 40, 25, 16 }, { 46, 28, 18 }, { 51, 32, 20 }, { 56, 35, 22 }, { 61, 38, 24 }, { 66, 41, 26 }, { 71, 44, 28 }, { 76, 47, 30 }, { 81, 51, 32 }, { 86, 54, 34 }, { 91, 57, 36 }, { 96, 60, 38 }, { 101, 63, 40 }, { 106, 66, 42 }, { 111, 70, 44 }, { 116, 73, 46 }, { 121, 76, 48 }, { 126, 79, 50 }, { 132, 82, 52 }, { 137, 85, 54 }, { 142, 89, 56 }, { 147, 92, 58 }, { 152, 95, 60 }, { 157, 98, 62 }, { 162, 101, 64 }, { 167, 104, 66 }, { 172, 108, 68 }, { 177, 111, 70 }, { 182, 114, 72 }, { 187, 117, 75 }, { 192, 120, 77 }, { 197, 123, 79 }, { 202, 126, 81 }, { 207, 130, 83 }, { 212, 133, 85 }, { 218, 136, 87 }, { 223, 139, 89 }, { 228, 142, 91 }, { 233, 145, 93 }, { 238, 149, 95 }, { 243, 152, 97 }, { 248, 155, 99 }, { 253, 158, 101 }, { 255, 161, 103 }, { 255, 164, 105 }, { 255, 168, 107 }, { 255, 171, 109 }, { 255, 174, 111 }, { 255, 177, 113 }, { 255, 180, 115 }, { 255, 183, 117 }, { 255, 187, 119 }, { 255, 190, 121 }, { 255, 193, 123 }, { 255, 196, 125 }, { 255, 199, 127 } }; break; - case eColorScheme.Hot: u8_RGB = new byte[,] { { 11, 0, 0 }, { 21, 0, 0 }, { 32, 0, 0 }, { 43, 0, 0 }, { 53, 0, 0 }, { 64, 0, 0 }, { 74, 0, 0 }, { 85, 0, 0 }, { 96, 0, 0 }, { 106, 0, 0 }, { 117, 0, 0 }, { 128, 0, 0 }, { 138, 0, 0 }, { 149, 0, 0 }, { 159, 0, 0 }, { 170, 0, 0 }, { 181, 0, 0 }, { 191, 0, 0 }, { 202, 0, 0 }, { 213, 0, 0 }, { 223, 0, 0 }, { 234, 0, 0 }, { 244, 0, 0 }, { 255, 0, 0 }, { 255, 11, 0 }, { 255, 21, 0 }, { 255, 32, 0 }, { 255, 43, 0 }, { 255, 53, 0 }, { 255, 64, 0 }, { 255, 74, 0 }, { 255, 85, 0 }, { 255, 96, 0 }, { 255, 106, 0 }, { 255, 117, 0 }, { 255, 128, 0 }, { 255, 138, 0 }, { 255, 149, 0 }, { 255, 159, 0 }, { 255, 170, 0 }, { 255, 181, 0 }, { 255, 191, 0 }, { 255, 202, 0 }, { 255, 213, 0 }, { 255, 223, 0 }, { 255, 234, 0 }, { 255, 244, 0 }, { 255, 255, 0 }, { 255, 255, 16 }, { 255, 255, 32 }, { 255, 255, 48 }, { 255, 255, 64 }, { 255, 255, 80 }, { 255, 255, 96 }, { 255, 255, 112 }, { 255, 255, 128 }, { 255, 255, 143 }, { 255, 255, 159 }, { 255, 255, 175 }, { 255, 255, 191 }, { 255, 255, 207 }, { 255, 255, 223 }, { 255, 255, 239 }, { 255, 255, 255 } }; break; - case eColorScheme.Hsv: u8_RGB = new byte[,] { { 255, 0, 0 }, { 255, 24, 0 }, { 255, 48, 0 }, { 255, 72, 0 }, { 255, 96, 0 }, { 255, 120, 0 }, { 255, 143, 0 }, { 255, 167, 0 }, { 255, 191, 0 }, { 255, 215, 0 }, { 255, 239, 0 }, { 247, 255, 0 }, { 223, 255, 0 }, { 199, 255, 0 }, { 175, 255, 0 }, { 151, 255, 0 }, { 128, 255, 0 }, { 104, 255, 0 }, { 80, 255, 0 }, { 56, 255, 0 }, { 32, 255, 0 }, { 8, 255, 0 }, { 0, 255, 16 }, { 0, 255, 40 }, { 0, 255, 64 }, { 0, 255, 88 }, { 0, 255, 112 }, { 0, 255, 135 }, { 0, 255, 159 }, { 0, 255, 183 }, { 0, 255, 207 }, { 0, 255, 231 }, { 0, 255, 255 }, { 0, 231, 255 }, { 0, 207, 255 }, { 0, 183, 255 }, { 0, 159, 255 }, { 0, 135, 255 }, { 0, 112, 255 }, { 0, 88, 255 }, { 0, 64, 255 }, { 0, 40, 255 }, { 0, 16, 255 }, { 8, 0, 255 }, { 32, 0, 255 }, { 56, 0, 255 }, { 80, 0, 255 }, { 104, 0, 255 }, { 128, 0, 255 }, { 151, 0, 255 }, { 175, 0, 255 }, { 199, 0, 255 }, { 223, 0, 255 }, { 247, 0, 255 }, { 255, 0, 239 }, { 255, 0, 215 }, { 255, 0, 191 }, { 255, 0, 167 }, { 255, 0, 143 }, { 255, 0, 120 }, { 255, 0, 96 }, { 255, 0, 72 }, { 255, 0, 48 }, { 255, 0, 24 } }; break; - case eColorScheme.Rainbow_Dark: u8_RGB = new byte[,] { { 0, 0, 143 }, { 0, 0, 159 }, { 0, 0, 175 }, { 0, 0, 191 }, { 0, 0, 207 }, { 0, 0, 223 }, { 0, 0, 239 }, { 0, 0, 255 }, { 0, 16, 255 }, { 0, 32, 255 }, { 0, 48, 255 }, { 0, 64, 255 }, { 0, 80, 255 }, { 0, 96, 255 }, { 0, 112, 255 }, { 0, 128, 255 }, { 0, 143, 255 }, { 0, 159, 255 }, { 0, 175, 255 }, { 0, 191, 255 }, { 0, 207, 255 }, { 0, 223, 255 }, { 0, 239, 255 }, { 0, 255, 255 }, { 16, 255, 239 }, { 32, 255, 223 }, { 48, 255, 207 }, { 64, 255, 191 }, { 80, 255, 175 }, { 96, 255, 159 }, { 112, 255, 143 }, { 128, 255, 128 }, { 143, 255, 112 }, { 159, 255, 96 }, { 175, 255, 80 }, { 191, 255, 64 }, { 207, 255, 48 }, { 223, 255, 32 }, { 239, 255, 16 }, { 255, 255, 0 }, { 255, 239, 0 }, { 255, 223, 0 }, { 255, 207, 0 }, { 255, 191, 0 }, { 255, 175, 0 }, { 255, 159, 0 }, { 255, 143, 0 }, { 255, 128, 0 }, { 255, 112, 0 }, { 255, 96, 0 }, { 255, 80, 0 }, { 255, 64, 0 }, { 255, 48, 0 }, { 255, 32, 0 }, { 255, 16, 0 }, { 255, 0, 0 }, { 239, 0, 0 }, { 223, 0, 0 }, { 207, 0, 0 }, { 191, 0, 0 }, { 175, 0, 0 }, { 159, 0, 0 }, { 143, 0, 0 }, { 128, 0, 0 } }; break; - case eColorScheme.Pink: u8_RGB = new byte[,] { { 30, 0, 0 }, { 50, 26, 26 }, { 64, 37, 37 }, { 75, 45, 45 }, { 85, 52, 52 }, { 94, 59, 59 }, { 102, 64, 64 }, { 110, 69, 69 }, { 117, 74, 74 }, { 123, 79, 79 }, { 130, 83, 83 }, { 136, 87, 87 }, { 141, 91, 91 }, { 147, 95, 95 }, { 152, 98, 98 }, { 157, 102, 102 }, { 162, 105, 105 }, { 167, 108, 108 }, { 172, 111, 111 }, { 176, 114, 114 }, { 181, 117, 117 }, { 185, 120, 120 }, { 189, 123, 123 }, { 194, 126, 126 }, { 195, 132, 129 }, { 197, 138, 131 }, { 199, 144, 134 }, { 201, 149, 136 }, { 202, 154, 139 }, { 204, 159, 141 }, { 206, 164, 144 }, { 207, 169, 146 }, { 209, 174, 148 }, { 211, 178, 151 }, { 212, 183, 153 }, { 214, 187, 155 }, { 216, 191, 157 }, { 217, 195, 160 }, { 219, 199, 162 }, { 220, 203, 164 }, { 222, 207, 166 }, { 223, 211, 168 }, { 225, 215, 170 }, { 226, 218, 172 }, { 228, 222, 174 }, { 229, 225, 176 }, { 231, 229, 178 }, { 232, 232, 180 }, { 234, 234, 185 }, { 235, 235, 191 }, { 237, 237, 196 }, { 238, 238, 201 }, { 240, 240, 206 }, { 241, 241, 211 }, { 243, 243, 216 }, { 244, 244, 221 }, { 245, 245, 225 }, { 247, 247, 230 }, { 248, 248, 234 }, { 250, 250, 238 }, { 251, 251, 243 }, { 252, 252, 247 }, { 254, 254, 251 }, { 255, 255, 255 } }; break; - case eColorScheme.Spring: u8_RGB = new byte[,] { { 255, 0, 255 }, { 255, 4, 251 }, { 255, 8, 247 }, { 255, 12, 243 }, { 255, 16, 239 }, { 255, 20, 235 }, { 255, 24, 231 }, { 255, 28, 227 }, { 255, 32, 223 }, { 255, 36, 219 }, { 255, 40, 215 }, { 255, 45, 210 }, { 255, 49, 206 }, { 255, 53, 202 }, { 255, 57, 198 }, { 255, 61, 194 }, { 255, 65, 190 }, { 255, 69, 186 }, { 255, 73, 182 }, { 255, 77, 178 }, { 255, 81, 174 }, { 255, 85, 170 }, { 255, 89, 166 }, { 255, 93, 162 }, { 255, 97, 158 }, { 255, 101, 154 }, { 255, 105, 150 }, { 255, 109, 146 }, { 255, 113, 142 }, { 255, 117, 138 }, { 255, 121, 134 }, { 255, 125, 130 }, { 255, 130, 125 }, { 255, 134, 121 }, { 255, 138, 117 }, { 255, 142, 113 }, { 255, 146, 109 }, { 255, 150, 105 }, { 255, 154, 101 }, { 255, 158, 97 }, { 255, 162, 93 }, { 255, 166, 89 }, { 255, 170, 85 }, { 255, 174, 81 }, { 255, 178, 77 }, { 255, 182, 73 }, { 255, 186, 69 }, { 255, 190, 65 }, { 255, 194, 61 }, { 255, 198, 57 }, { 255, 202, 53 }, { 255, 206, 49 }, { 255, 210, 45 }, { 255, 215, 40 }, { 255, 219, 36 }, { 255, 223, 32 }, { 255, 227, 28 }, { 255, 231, 24 }, { 255, 235, 20 }, { 255, 239, 16 }, { 255, 243, 12 }, { 255, 247, 8 }, { 255, 251, 4 }, { 255, 255, 0 } }; break; - case eColorScheme.Summer: u8_RGB = new byte[,] { { 0, 128, 102 }, { 4, 130, 102 }, { 8, 132, 102 }, { 12, 134, 102 }, { 16, 136, 102 }, { 20, 138, 102 }, { 24, 140, 102 }, { 28, 142, 102 }, { 32, 144, 102 }, { 36, 146, 102 }, { 40, 148, 102 }, { 45, 150, 102 }, { 49, 152, 102 }, { 53, 154, 102 }, { 57, 156, 102 }, { 61, 158, 102 }, { 65, 160, 102 }, { 69, 162, 102 }, { 73, 164, 102 }, { 77, 166, 102 }, { 81, 168, 102 }, { 85, 170, 102 }, { 89, 172, 102 }, { 93, 174, 102 }, { 97, 176, 102 }, { 101, 178, 102 }, { 105, 180, 102 }, { 109, 182, 102 }, { 113, 184, 102 }, { 117, 186, 102 }, { 121, 188, 102 }, { 125, 190, 102 }, { 130, 192, 102 }, { 134, 194, 102 }, { 138, 196, 102 }, { 142, 198, 102 }, { 146, 200, 102 }, { 150, 202, 102 }, { 154, 204, 102 }, { 158, 206, 102 }, { 162, 208, 102 }, { 166, 210, 102 }, { 170, 212, 102 }, { 174, 215, 102 }, { 178, 217, 102 }, { 182, 219, 102 }, { 186, 221, 102 }, { 190, 223, 102 }, { 194, 225, 102 }, { 198, 227, 102 }, { 202, 229, 102 }, { 206, 231, 102 }, { 210, 233, 102 }, { 215, 235, 102 }, { 219, 237, 102 }, { 223, 239, 102 }, { 227, 241, 102 }, { 231, 243, 102 }, { 235, 245, 102 }, { 239, 247, 102 }, { 243, 249, 102 }, { 247, 251, 102 }, { 251, 253, 102 }, { 255, 255, 102 } }; break; - case eColorScheme.Winter: u8_RGB = new byte[,] { { 0, 0, 255 }, { 0, 4, 253 }, { 0, 8, 251 }, { 0, 12, 249 }, { 0, 16, 247 }, { 0, 20, 245 }, { 0, 24, 243 }, { 0, 28, 241 }, { 0, 32, 239 }, { 0, 36, 237 }, { 0, 40, 235 }, { 0, 45, 233 }, { 0, 49, 231 }, { 0, 53, 229 }, { 0, 57, 227 }, { 0, 61, 225 }, { 0, 65, 223 }, { 0, 69, 221 }, { 0, 73, 219 }, { 0, 77, 217 }, { 0, 81, 215 }, { 0, 85, 213 }, { 0, 89, 210 }, { 0, 93, 208 }, { 0, 97, 206 }, { 0, 101, 204 }, { 0, 105, 202 }, { 0, 109, 200 }, { 0, 113, 198 }, { 0, 117, 196 }, { 0, 121, 194 }, { 0, 125, 192 }, { 0, 130, 190 }, { 0, 134, 188 }, { 0, 138, 186 }, { 0, 142, 184 }, { 0, 146, 182 }, { 0, 150, 180 }, { 0, 154, 178 }, { 0, 158, 176 }, { 0, 162, 174 }, { 0, 166, 172 }, { 0, 170, 170 }, { 0, 174, 168 }, { 0, 178, 166 }, { 0, 182, 164 }, { 0, 186, 162 }, { 0, 190, 160 }, { 0, 194, 158 }, { 0, 198, 156 }, { 0, 202, 154 }, { 0, 206, 152 }, { 0, 210, 150 }, { 0, 215, 148 }, { 0, 219, 146 }, { 0, 223, 144 }, { 0, 227, 142 }, { 0, 231, 140 }, { 0, 235, 138 }, { 0, 239, 136 }, { 0, 243, 134 }, { 0, 247, 132 }, { 0, 251, 130 }, { 0, 255, 128 } }; break; - default: - throw new ArgumentException("Invalid enum"); - } - - Color[] c_Schema = new Color[u8_RGB.GetLength(0)]; - for (int i=0; i - /// s32_Sweeps = 4 --> Colors from blue to red, not including magenta (1024 colors) - /// s32_Sweeps = 6 --> Complete sweep with all rainbow colors, also magenta (1536 colors) - /// - private static Color[] CalcRainbow(int s32_Sweeps) - { - Color[] c_Colors = new Color[s32_Sweeps * 256]; - int R,G,B,P=0; - for (int L=0; L - /// Constructor 1 - /// - public cColorScheme(eColorScheme e_Scheme) - : this (GetSchema(e_Scheme)) - { - } - - /// - /// Constructor 2 - /// If you want to draw the entire graph with only one color, you can pass a single Color here. - /// - public cColorScheme(params Color[] c_Colors) - { - if (c_Colors.Length == 0) - throw new ArgumentException("A ColorScheme needs at least one color."); - - mi_Brushes = new SolidBrush[c_Colors.Length]; - mi_Pens = new Pen [c_Colors.Length]; - - for (int i=0; i < c_Colors.Length; i++) - { - mi_Brushes[i] = new SolidBrush(c_Colors[i]); - mi_Pens [i] = new Pen (c_Colors[i], 1); // the width of the Pen will be modified later. - } - } - - public Brush GetBrush(int s32_Index) - { - s32_Index = Math.Max(0, s32_Index); - return mi_Brushes[s32_Index % mi_Brushes.Length]; - } - - public Pen GetPen(int s32_Index) - { - s32_Index = Math.Max(0, s32_Index); - return mi_Pens[s32_Index % mi_Pens.Length]; - } - - public int CalcIndex(double d_FactorZ) - { - if (double.IsNaN(d_FactorZ)) - return -1; - - d_FactorZ = Math.Min(1.0, d_FactorZ); - d_FactorZ = Math.Max(0.0, d_FactorZ); - - // d_FactorZ is a value between 0.0 and 1.0 - return (int)(d_FactorZ * (mi_Brushes.Length - 1)); - } - } - - #endregion - - #region cUserInput - - public class cUserInput - { - private MouseButtons me_MouseButton; - private Keys me_Modifiers; - private eMouseAction me_Action; - private Cursor mi_Cursor; - - /// - /// A unique identifier for the combination of mouse button and modifier(s) - /// Keys.Shift = 0x00010000 - /// Keys.Control = 0x00020000 - /// Keys.Alt = 0x00040000 - /// MouseButtons.Left = 0x00100000 - /// MouseButtons.Right = 0x00200000 - /// MouseButtons.Middle = 0x00400000 - /// MouseButtons.XButton1 = 0x00800000 - /// MouseButtons.XButton2 = 0x01000000 - /// - public int UID - { - get { return (int)me_MouseButton | (int)me_Modifiers; } - } - - public MouseButtons MouseButton - { - get { return me_MouseButton; } - } - public Keys Modifiers - { - get { return me_Modifiers; } - } - public eMouseAction Action - { - get { return me_Action; } - } - public Cursor Cursor - { - get { return mi_Cursor; } - } - - public cUserInput(MouseButtons e_MouseButton, Keys e_Modifiers, eMouseAction e_Action, Cursor i_Cursor = null) - { - Debug.Assert((e_Modifiers & Keys.KeyCode) == 0, "Invalid parameter e_Modifiers"); - Debug.Assert(e_MouseButton != MouseButtons.None, "Invalid parameter e_MouseButton"); - Debug.Assert(e_Action != eMouseAction.None, "Invalid parameter e_Action"); - - me_MouseButton = e_MouseButton; - me_Modifiers = e_Modifiers; - me_Action = e_Action; - mi_Cursor = i_Cursor; - - if (mi_Cursor == null) - { - switch (e_Action) - { - case eMouseAction.ThetaAndPhi: mi_Cursor = Cursors.SizeAll; break; - case eMouseAction.Theta: mi_Cursor = Cursors.NoMoveVert; break; - case eMouseAction.Phi: mi_Cursor = Cursors.NoMoveHoriz; break; - case eMouseAction.Rho: mi_Cursor = Cursors.SizeNS; break; - case eMouseAction.Move: mi_Cursor = Cursors.NoMove2D; break; - default: mi_Cursor = Cursors.Arrow; break; - } - } - } - - /// - /// For debugging in Visual Studio - /// - public override string ToString() - { - return String.Format("MouseButton: {0}, Modifiers: {1} --> Action: {2}", me_MouseButton, me_Modifiers, me_Action); - } - } - - #endregion - - // --------------------- - - #region cRenderData - - /// - /// Base class for cSurfaceData, cScatterData, cLineData, cPolygonData - /// - public abstract class cRenderData - { - public virtual void AddDrawObjects(Editor3D i_Inst) - { - throw new NotImplementedException(); - } - - /// - /// The width and color of the Pen may be modified later. - /// So the immutable framework collection like Pens.Black,... cannot be used here. - /// - protected static void CheckPenMutable(Pen i_Pen, cColorScheme i_ColorScheme) - { - if (i_Pen != null && i_ColorScheme != null) - { - try { i_Pen.Color = Color.BlanchedAlmond; } - catch { throw new ArgumentException("To use a color scheme create a new Pen. Do not use the immutable Pens.XYZ collection."); } - } - } - } - - #endregion - - #region cSurfaceData - - public class cSurfaceData : cRenderData - { - private bool mb_Fill; - private bool mb_Missing; - private int ms32_Cols; - private int ms32_Rows; - private int ms32_Radius; - private Pen mi_Pen; - private cPoint3D[,] mi_PointArray; - private cPolygon3D[,] mi_PolygonArray; - private cColorScheme mi_ColorScheme; - - public cColorScheme ColorScheme - { - get { return mi_ColorScheme; } - } - - /// - /// The count of points in one column of the surface grid - /// - public int Cols - { - get { return ms32_Cols; } - } - - /// - /// The count of points in one row of the surface grid - /// - public int Rows - { - get { return ms32_Rows; } - } - - /// - /// Fill Mode: - /// ------------ - /// Polygons are filled with a color from the ColorScheme. - /// If you want only one color, set a ColorScheme which contains only one color. - /// The Pen is used to draw the thin lines between the polygons (mostly black, 1 pixel) - /// If Pen is null, no lines are drawn. - /// - /// Line Mode: - /// ------------ - /// Only the border lines of the polygons are drawn. - /// The Pen is used to draw these lines. The Pen's color and width will be modified. - /// - /// Missing Mode: - /// -------------- - /// s32_Radius > 0 allows missing points. - /// s32_Radius defines the radius of cicles that represent points which have not enough neigbours to draw a polygon. - /// - public cSurfaceData(int s32_Cols, int s32_Rows, ePolygonMode e_Mode, Pen i_Pen, cColorScheme i_ColorScheme, - int s32_Radius = 0) - { - if (s32_Cols < 3 || s32_Rows < 3) - throw new ArgumentException("cSurfaceData needs at least 3 columns and 3 rows"); - - if (e_Mode == ePolygonMode.Fill) - { - if (i_ColorScheme == null) - throw new ArgumentException("In Fill mode you must specify a ColorScheme"); - - // The border pen is allowed to be immutable. It will not be changed. - } - else // Lines - { - if (i_Pen == null) - throw new ArgumentException("In Line mode you must specify a Line Pen"); - - CheckPenMutable(i_Pen, i_ColorScheme); - } - - mb_Fill = e_Mode == ePolygonMode.Fill; - mb_Missing = s32_Radius > 0; - ms32_Radius = s32_Radius; - ms32_Cols = s32_Cols; - ms32_Rows = s32_Rows; - mi_Pen = i_Pen; - mi_ColorScheme = i_ColorScheme; - mi_PointArray = new cPoint3D[s32_Cols, s32_Rows]; - } - - /// - /// Here you can set a callback function which will be called with X,Y to calculate the Z values of the points. - /// - public void ExecuteFunction(delRendererFunction f_Function, PointF k_Start, PointF k_End) - { - mb_Missing = false; - - double d_StepX = (k_End.X - k_Start.X) / (ms32_Cols - 1); - double d_StepY = (k_End.Y - k_Start.Y) / (ms32_Rows - 1); - - for (int C = 0; C < ms32_Cols; C++) - { - double d_X = k_Start.X + d_StepX * C; - - for (int R = 0; R < ms32_Rows; R++) - { - double d_Y = k_Start.Y + d_StepY * R; - double d_Z = f_Function(d_X, d_Y); - - SetPointAt(C, R, new cPoint3D(d_X, d_Y, d_Z)); - } - } - } - - /// - /// IMPORTANT: - /// The X coordinate of the point must be related to the column. - /// The Y coordinate of the point must be related to the row. - /// - public void SetPointAt(int s32_Column, int s32_Row, cPoint3D i_Point3D) - { - if (mi_PolygonArray != null) - throw new Exception("You cannot call cSurfaceData.SetPointAt() or ExecuteFunction() anymore after calling GetPolygonAt() " - + "or Editor3D.AddRenderData(). To modify a point after the polygons have been created call " - + "GetPointAt() and modify the X,Y,Z values of the returned point."); - - mi_PointArray[s32_Column, s32_Row] = i_Point3D; - } - - /// - /// ATTENTION: - /// In mode 'Missing' null may be returned or polygons with only 3 corners! - /// - public cPoint3D GetPointAt(int s32_Column, int s32_Row) - { - return mi_PointArray[s32_Column, s32_Row]; - } - - /// - /// ATTENTION: - /// The polygons have one row less than cSurfaceData.Rows and one column less than cSurfaceData.Cols - /// - public cPolygon3D GetPolygonAt(int s32_Column, int s32_Row) - { - CreatePolygons(); - return mi_PolygonArray[s32_Column, s32_Row]; - } - - private void CreatePolygons() - { - if (mi_PolygonArray != null) - return; - - cPolygon3D[,] i_TempArr = new cPolygon3D[ms32_Cols -1, ms32_Rows -1]; - - List i_Valid = new List(); - for (int C=0; C < ms32_Cols -1; C++) - { - for (int R=0; R < ms32_Rows -1; R++) - { - i_Valid.Clear(); - if (mi_PointArray[C, R] != null) i_Valid.Add(mi_PointArray[C, R]); - if (mi_PointArray[C, R+1] != null) i_Valid.Add(mi_PointArray[C, R+1]); - if (mi_PointArray[C+1, R+1] != null) i_Valid.Add(mi_PointArray[C+1, R+1]); - if (mi_PointArray[C+1, R] != null) i_Valid.Add(mi_PointArray[C+1, R]); - - if (i_Valid.Count < 4 && !mb_Missing) - throw new Exception("cSurfaceData: You must call cSurfaceData.SetPointAt() for all points!"); - - if (i_Valid.Count < 3) - continue; // A polygon needs at least 3 points - - i_TempArr[C, R] = new cPolygon3D(C, R, null, i_Valid.ToArray()); - } - } - - mi_PolygonArray = i_TempArr; - } - - // ============================================================================= - - /// - /// Called from AddRenderData() - /// - public override void AddDrawObjects(Editor3D i_Inst) - { - CreatePolygons(); - - bool b_Added = false; - List i_Used = new List(); - - foreach (cPolygon3D i_Poly3D in mi_PolygonArray) - { - if (i_Poly3D == null) - continue; - - i_Inst.AddDrawObject(new cPolygon(mb_Fill, i_Poly3D, mi_Pen, mi_ColorScheme)); - b_Added = true; - - foreach (cPoint3D i_Point3D in i_Poly3D.Points) - { - if (!i_Used.Contains(i_Point3D)) - i_Used.Add(i_Point3D); - } - } - - // Add all the remaining points as Scatter circles that are not part of a polygon. - for (int C = 0; C < ms32_Cols; C++) - { - for (int R = 0; R < ms32_Rows; R++) - { - cPoint3D i_Point3D = mi_PointArray[C, R]; - if (i_Point3D == null || i_Used.Contains(i_Point3D)) - continue; - - cShape3D i_Shape3D = new cShape3D(C, R, i_Point3D, eScatterShape.Circle, ms32_Radius, null); - i_Inst.AddDrawObject(new cShape (i_Shape3D, mi_ColorScheme)); - b_Added = true; - } - } - - if (!b_Added) - throw new Exception("You cannot draw a completely empty SurfaceData"); - } - } - - #endregion - - #region cScatterData - - public class cScatterData : cRenderData - { - private List mi_Shapes3D = new List(); - private cColorScheme mi_ColorScheme; - - public cShape3D[] AllShapes - { - get { return mi_Shapes3D.ToArray(); } - } - - public cColorScheme ColorScheme - { - get { return mi_ColorScheme; } - } - - /// - /// Constructor - /// If all Scatter shapes contain a valid Brush, you can pass i_ColorScheme == null here - /// - public cScatterData(cColorScheme i_ColorScheme) - { - mi_ColorScheme = i_ColorScheme; - } - - public cScatterData() - { - } + public enum eRaster + { + Off, // turn off coordinate system + MainAxes, // draw only solid main axes for X,Y,Z + Raster, // draw additional thin raster lines + Labels, // draw additional labels in quadrant 3 + } + + /// + /// If a function has an asymetric range for X and Y as demo "Callback" a separate + /// normalization would always lead to a square X,Y pane which would be a distortion for the + /// relation between X and Y values. MaintainXY guarantees that the relation between X and Y + /// values is maintained. MaintainXYZ additionally guarantees that the relation between X, Y + /// and Z values is maintained. + /// + public enum eNormalize + { + Separate, // Normalize X,Y,Z separately (use this for discrete values) + MaintainXY, // Normalize X,Y without changing their relation (use this for math functions) + MaintainXYZ, // Normalize X,Y,Z without changing their relation (use this for math functions) + } + + public enum eScatterShape + { + // 0 is invalid + Circle = 1, + + Square = 2, + Triangle = 3, + // Star = 4, you can implement your own shapes here + } + + /// + /// Used internally for coordinate system + /// + public enum eCoord + { + // for axis in coordinate system + X = 0, + + Y = 1, + Z = 2, + Invalid, + } + + /// + /// These flags define which axes are allowed to be mirrored. For user objects option "All" + /// is used. Coordinate system axes and raster lines are mirrored individually. + /// + [FlagsAttribute] + public enum eMirror + { + None = 0, + X = 1, + Y = 2, + Z = 4, + XY = X | Y, + XZ = X | Z, + YZ = Y | Z, + All = X | Y | Z, + } + + /// + /// Mouse operations + /// + public enum eMouseAction + { + None = 0, + Move, // Move the coordinate system with the mouse + Theta, // Elevate the coordinate system with the mouse + Phi, // Rotate the coordinate system with the mouse + ThetaAndPhi, // Elevate + Rotate the coordinate system with the mouse + Rho, // Zoom in / out + SelectObj, // Select a 3D object or call the selection callback function if defined + Callback, // Call the selection callback function if defined + } + + public enum eSelEvent + { + MouseDown, // Inform selection callback function that the pre-defined mouse button goes down + MouseDrag, // Inform selection callback function that the mouse is moved while the pre-defined mouse button is down + MouseUp, // Inform selection callback function that the pre-defined mouse button goes up + } + + public enum ePolygonMode + { + Fill, // Fill polygons with Brush + Lines, // Draw only polygon border lines + } + + /// + /// This enum defines of which type is a cObject3D + /// + public enum eObjType + { + Point, + Line, + Shape, + Polygon, + } + + /// + /// These flags are used to filter the 3D objects that are selected. + /// + [FlagsAttribute] + public enum eSelType + { + Line = 0x1, + Shape = 0x2, + Polygon = 0x4, + All = 0x7, + } + + [FlagsAttribute] + public enum eTooltip + { + Off = 0x0, // Tooltip is disabled + UserText = 0x1, // Show user defined tooltip text that has been set in cPoint3D.Tooltip + Coord = 0x2, // Show coordinates X,Y,Z of cPoint3D + All = 0x3, // Show all + } + + // This enum is to get the maximum speed out of your CPU. Re-calculation is done only if required. + [FlagsAttribute] + private enum eRecalculate + { + Nothing = 0x0, // repaint objects after changed selection --> recalculate nothing + Objects = 0x1, // Projection, Brush, LineWidth,... has changed --> recalculate 3D objects + CoordSystem = 0x2, // The coordinate system must be recalculated --> recalculate Min/Max and Coord System + AddRemove = 0x4, // Draw Objects have been added or removed --> refresh lists and recalculate all + } + + /// + /// These are the possible return values of the Selection callback. See description of + /// SelectionCallback() at the end of this class. + /// + public enum eInvalidate + { + NoChange, // The callback has not modified anything --> do nothing. + Invalidate, // Calls Invalidate() to redraw what is required depending on the flags in me_Recalculate. + CoordSystem, // The coordinate system will be recalculated, then Invalidate() is called. + // Use this option after moving a 3D object with the mouse. + } + + /// + /// This defines with which mouse button the user controls rotation and elevation + /// + public enum eMouseCtrl + { + L_Theta_R_Phi, // Left mouse button vertical: Theta, Right mouse button horizontal: Phi + L_Theta_L_Phi, // Left mouse button vertical: Theta, Left mouse button horizontal: Phi + M_Theta_M_Phi, // Middle mouse button vertical: Theta, Middle mouse button horizontal: Phi + } + + /// + /// 3D object properties that the user may change are stored in the Undo Buffer + /// + public enum eUndoProp + { + // cObject3D + Tag, + + Selected, + CanSelect, + + // cPoint3D + X, + + Y, + Z, + Tooltip, + + // cLine3D + Pen, + + Width, + + // cShape3D + Shape, + + Radius, + + // cShape3D + cPolygon3D + Brush, + } + + public enum eLegendPos + { + BottomLeft, // The main axis legends are drawn in the bottom left corner of the graph pane. (recommended) + AxisEnd, // The main axis legends are drawn at the end of the axis and rotate with the graph. (not recommended) + } + + #endregion enums + + // ================== PUBLIC ================= + + #region cObject3D + + /// + /// Base class for cPoint3D, cShape3D, cLine3D, cPloygon3D + /// + public abstract class cObject3D + { + public Editor3D mi_Inst; + protected Object mo_Tag; + protected bool mb_Selected; + protected bool mb_CanSelect = true; + protected cPoint3D[] mi_Points; // This instance must never be replaced by a new array! + protected int ms32_Col = -1; + protected int ms32_Row = -1; + + public virtual void Restore(eUndoProp e_Property, Object o_Value) + { + switch (e_Property) + { + case eUndoProp.Tag: mo_Tag = o_Value; break; + case eUndoProp.Selected: mb_Selected = (bool)o_Value; break; + case eUndoProp.CanSelect: mb_CanSelect = (bool)o_Value; break; + default: throw new ArgumentException(); + } + } + + // -------------------------------------------------- + + /// + /// Gets the type of this cObject3D + /// + public virtual eObjType ObjType + { + get { throw new NotImplementedException(); } + } + + /// + /// 1 point for cPoint3D 1 point for cShape3D 2 points for cLine3D n points for cPolygon3D + /// + public cPoint3D[] Points + { + get { return mi_Points; } + // set must NOT be allowed here !!!! + } + + /// + /// The column in the grid. This is only valid for Polygons and Scatter circles created + /// by cSurfaceData, otherwise -1 + /// + public int Column + { + get { return ms32_Col; } // the grid column cannot be changed + } + + /// + /// The row in the grid. This is only valid for Polygons and Scatter circles created by + /// cSurfaceData, otherwise -1 + /// + public int Row + { + get { return ms32_Row; } // the grid row cannot be changed + } + + /// + /// Here you can store your private data which is passed in Selection.Callback to your code. + /// + public Object Tag + { + get { return mo_Tag; } + set + { + if (mo_Tag == value) + return; + + if (mi_Inst != null) + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Tag, mo_Tag, value); + + mo_Tag = value; + } + } + + /// + /// The draw object has been selected by ALT + click + /// + public virtual bool Selected + { + get { return mb_Selected; } + set + { + if (mb_Selected == value) + return; + + if (mi_Inst != null) + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Selected, mb_Selected, value); + + mb_Selected = value; + // me_Recalculate needs no change here + } + } + + /// + /// Defines if the user is allowed to select this object + /// + public virtual bool CanSelect + { + get { return mb_CanSelect; } + set + { + if (mb_CanSelect == value) + return; + + if (mi_Inst != null) + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.CanSelect, mb_CanSelect, value); + + mb_CanSelect = value; + } + } + + /// + /// Move the object in the 3D space + /// + public void Move(double d_DeltaX, double d_DeltaY, double d_DeltaZ) + { + foreach (cPoint3D i_Point in mi_Points) + { + i_Point.X += d_DeltaX; + i_Point.Y += d_DeltaY; + i_Point.Z += d_DeltaZ; + } + } + } + + #endregion cObject3D + + #region cPoint3D + + public class cPoint3D : cObject3D + { + private double md_X; + private double md_Y; + private double md_Z; + private String ms_Tooltip; + + public override void Restore(eUndoProp e_Property, Object o_Value) + { + switch (e_Property) + { + case eUndoProp.X: md_X = (double)o_Value; break; + case eUndoProp.Y: md_Y = (double)o_Value; break; + case eUndoProp.Z: md_Z = (double)o_Value; break; + case eUndoProp.Tooltip: ms_Tooltip = (String)o_Value; break; + default: base.Restore(e_Property, o_Value); break; + } + } + + // -------------------------------------------------- + + /// + /// Gets the type of this cObject3D + /// + public override eObjType ObjType + { + get { return eObjType.Point; } + } + + /// + /// 3D coordinate + /// + public double X + { + get { return md_X; } + set + { + if (md_X == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.X, md_X, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + + if (value < mi_Inst.mi_Bounds.X.Min || value > mi_Inst.mi_Bounds.X.Max) + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; + } + md_X = value; + } + } + + /// + /// 3D coordinate + /// + public double Y + { + get { return md_Y; } + set + { + if (md_Y == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Y, md_Y, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + + if (value < mi_Inst.mi_Bounds.Y.Min || value > mi_Inst.mi_Bounds.Y.Max) + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; + } + md_Y = value; + } + } + + /// + /// 3D coordinate + /// + public double Z + { + get { return md_Z; } + set + { + if (md_Z == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Z, md_Z, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + + if (value < mi_Inst.mi_Bounds.Z.Min || value > mi_Inst.mi_Bounds.Z.Max) + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; + } + md_Z = value; + } + } + + /// + /// Optional tooltip text to be displayed when the mouse is over this point + /// + public String Tooltip + { + get { return ms_Tooltip; } + set + { + if (ms_Tooltip == value) + return; + + if (mi_Inst != null) + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Tooltip, ms_Tooltip, value); + + ms_Tooltip = value; + } + } + + // -------------------------------------------------- + + /// + /// Constructor s_ToolTip is displayed when eTooltip.UserText is enabled In o_Tag you + /// can store any data that you need when the Selection callback is called after the + /// user has selected a point. + /// + public cPoint3D(double d_X, double d_Y, double d_Z, String s_ToolTip = null, Object o_Tag = null) + { + md_X = d_X; + md_Y = d_Y; + md_Z = d_Z; + mo_Tag = o_Tag; + mi_Points = new cPoint3D[] { this }; + + if (s_ToolTip != null) + ms_Tooltip = s_ToolTip.Trim(); + } + + // =================== used for coordinate system =================== + + public cPoint3D Clone() + { + return new cPoint3D(md_X, md_Y, md_Z, ms_Tooltip, Tag); + } + + public bool CoordEquals(cPoint3D i_Point) + { + return md_X == i_Point.md_X && md_Y == i_Point.md_Y && md_Z == i_Point.md_Z; + } + + public double GetValue(eCoord e_Coord) + { + switch (e_Coord) + { + case eCoord.X: return md_X; + case eCoord.Y: return md_Y; + case eCoord.Z: return md_Z; + default: return 0; + } + } + + public void SetValue(eCoord e_Coord, double d_Value) + { + switch (e_Coord) + { + case eCoord.X: X = d_Value; break; + case eCoord.Y: Y = d_Value; break; + case eCoord.Z: Z = d_Value; break; + } + } + + // For debugging in Visual Studio + public override string ToString() + { + return String.Format("cPoint3D (X={0}, Y={1}, Z={2})", FormatDouble(md_X), FormatDouble(md_Y), FormatDouble(md_Z)); + } + } + + #endregion cPoint3D + + #region cLine3D + + public class cLine3D : cObject3D + { + private Pen mi_Pen; + private int ms32_Width; + private int ms32_Parts; + + public override void Restore(eUndoProp e_Property, Object o_Value) + { + switch (e_Property) + { + case eUndoProp.Pen: mi_Pen = (Pen)o_Value; break; + case eUndoProp.Width: ms32_Width = (int)o_Value; break; + default: base.Restore(e_Property, o_Value); break; + // ms32_Parts is constant + } + } + + // -------------------------------------------------- + + /// + /// Gets the type of this cObject3D + /// + public override eObjType ObjType + { + get { return eObjType.Line; } + } + + /// + /// The line width in pixels + /// + public int Width + { + get { return ms32_Width; } + set + { + if (ms32_Width == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Width, ms32_Width, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + ms32_Width = value; + } + } + + /// + /// If Pen is null, a Pen from the ColorScheme will be used. The width of the Pen does + /// not matter. It will be set to the property Width. IMPORTANT: If you use the + /// UndoBuffer you must never modify any property of the Pen assigned here. If you + /// externally change for example Pen.Color the Undo will not work correctly. Always + /// assign a different instance of Pen to avoid this. + /// + public Pen Pen + { + get { return mi_Pen; } + set + { + if (mi_Pen == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Pen, mi_Pen, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + mi_Pen = value; + } + } + + /// + /// Parts of different color in multi-color lines + /// + public int ColorParts + { + get { return ms32_Parts; } + } + + /// + /// Constructor IMPORTANT: If you use the UndoBuffer you must never modify any property + /// of the Pen assigned here. If you externally change for example Pen.Color the Undo + /// will not work correctly. Always assign a different instance of Pen to cLine3D.Pen to + /// avoid this. + /// + public cLine3D(cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, int s32_Parts, Object o_Tag = null) + { + mi_Points = new cPoint3D[] { i_Start, i_End }; + ms32_Width = s32_Width; + mi_Pen = i_Pen; + ms32_Parts = s32_Parts; + mo_Tag = o_Tag; + } + + // For debugging in Visual Studio + public override string ToString() + { + return "cLine3D from " + mi_Points[0] + " to " + mi_Points[1]; + } + } + + #endregion cLine3D + + #region cShape3D + + public class cShape3D : cObject3D + { + private eScatterShape me_Shape; + private int ms32_Radius; + private Brush mi_Brush; + + public override void Restore(eUndoProp e_Property, Object o_Value) + { + switch (e_Property) + { + case eUndoProp.Shape: me_Shape = (eScatterShape)o_Value; break; + case eUndoProp.Radius: ms32_Radius = (int)o_Value; break; + case eUndoProp.Brush: mi_Brush = (Brush)o_Value; break; + default: base.Restore(e_Property, o_Value); break; + } + } + + // -------------------------------------------------- + + /// + /// Gets the type of this cObject3D + /// + public override eObjType ObjType + { + get { return eObjType.Shape; } + } + + /// + /// The type of shape to be painted + /// + public eScatterShape Shape + { + get { return me_Shape; } + set + { + if (me_Shape == value) + return; + + // Circle and rectangle are drawn by the framework --> no recalculation required. + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Shape, me_Shape, value); + + if (value != eScatterShape.Circle && value != eScatterShape.Square) + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + me_Shape = value; + } + } + + /// + /// The radius of the circle, square or triangle + /// + public int Radius + { + get { return ms32_Radius; } + set + { + if (ms32_Radius == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Radius, ms32_Radius, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + ms32_Radius = value; + } + } + + /// + /// The color of the Shape or null to use a color from the ColorScheme IMPORTANT: If you + /// use the UndoBuffer you must never modify any property of the Brush assigned here. If + /// you externally change for example SolidBrush.Color the Undo will not work correctly. + /// Always assign a different instance of Brush to avoid this. + /// + public Brush Brush + { + get { return mi_Brush; } + set + { + if (mi_Brush == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Brush, mi_Brush, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + mi_Brush = value; + } + } + + /// + /// The shape is selected + /// + public override bool Selected + { + get { return mi_Points[0].Selected; } + set { mi_Points[0].Selected = value; } // me_Recalculate needs no change + } + + /// + /// Defines if the user is allowed to select this shape + /// + public override bool CanSelect + { + get { return mi_Points[0].CanSelect; } + set { mi_Points[0].CanSelect = value; } + } + + /// + /// Constructor IMPORTANT: If you use the UndoBuffer you must never modify any property + /// of the Brush assigned here. If you externally change for example SolidBrush.Color + /// the Undo will not work correctly. Always assign a different instance of Brush to + /// cShape3D.Brush to avoid this. + /// + public cShape3D(int s32_Col, int s32_Row, cPoint3D i_Point, eScatterShape e_Shape, int s32_Radius, Brush i_Brush, Object o_Tag = null) + { + ms32_Col = s32_Col; + ms32_Row = s32_Row; + i_Point.Tag = o_Tag; + mi_Points = new cPoint3D[] { i_Point }; + me_Shape = e_Shape; + ms32_Radius = s32_Radius; + mi_Brush = i_Brush; + mo_Tag = o_Tag; + } + + // For debugging in Visual Studio + public override string ToString() + { + return "cShape3D " + me_Shape + " at " + mi_Points[0]; + } + } + + #endregion cShape3D + + #region cPolygon3D + + public class cPolygon3D : cObject3D + { + private Brush mi_Brush; + + public override void Restore(eUndoProp e_Property, Object o_Value) + { + switch (e_Property) + { + case eUndoProp.Brush: mi_Brush = (Brush)o_Value; break; + default: base.Restore(e_Property, o_Value); break; + // ms32_Col is constant ms32_Row is constant + } + } + + // -------------------------------------------------- + + /// + /// Gets the type of this cObject3D + /// + public override eObjType ObjType + { + get { return eObjType.Polygon; } + } + + /// + /// The color of the Polygon or null to use a color from the ColorScheme IMPORTANT: If + /// you use the UndoBuffer you must never modify any property of the Brush assigned + /// here. If you externally change for example SolidBrush.Color the Undo will not work + /// correctly. Always assign a different instance of Brush to avoid this. + /// + public Brush Brush + { + get { return mi_Brush; } + set + { + if (mi_Brush == value) + return; + + if (mi_Inst != null) + { + mi_Inst.mi_UndoBuffer.Backup(this, eUndoProp.Brush, mi_Brush, value); + mi_Inst.me_Recalculate |= eRecalculate.Objects; + } + mi_Brush = value; + } + } + + /// + /// Constructor IMPORTANT: If you use the UndoBuffer you must never modify any property + /// of the Brush assigned here. If you externally change for example SolidBrush.Color + /// the Undo will not work correctly. Always assign a different instance of Brush to + /// cPolygon3D.Brush to avoid this. + /// + public cPolygon3D(int s32_Col, int s32_Row, Brush i_Brush, params cPoint3D[] i_Points) + { + if (i_Points.Length < 3) + throw new ArgumentException("At least 3 points are required to draw a polygon."); + + mi_Points = i_Points; + mi_Brush = i_Brush; + ms32_Col = s32_Col; + ms32_Row = s32_Row; + } + + // For debugging in Visual Studio + public override string ToString() + { + return "cPolygon3D (" + mi_Points.Length + " points)"; + } + } + + #endregion cPolygon3D + + // --------------------- + + #region cAxis + + /// + /// Used for the main axes and raster lines of the coordinate system + /// + [Serializable] + [TypeConverter(typeof(ExpandableObjectConverter))] + public class cAxis + { + private Editor3D mi_Inst; + private eCoord me_Coord; + private Pen mi_RasterPen; // Raster lines (brigther) + private Pen mi_AxisPen; // Main coordinate axis (darker) + private SolidBrush mi_LegendBrush; // Label text + private String ms_LegendText; + private bool mb_Mirror; + private bool mb_IncludeZero; + + public cAxis(Editor3D i_Inst, eCoord e_Coord, Color c_Color) + { + mi_Inst = i_Inst; + me_Coord = e_Coord; + Color = c_Color; + Reset(); + } + + public void Reset() + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + ms_LegendText = null; + mb_Mirror = false; + mb_IncludeZero = me_Coord == eCoord.Z; + } + + /// + /// The color of the axis lines and the legend. This change will become visible the next + /// time you call Invalidate() Not modified by Reset() + /// + public Color Color + { + get { return mi_AxisPen.Color; } + set + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + mi_LegendBrush = new SolidBrush(value); + mi_AxisPen = new Pen(value, 3); + mi_RasterPen = new Pen(BrightenColor(value), 1); + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem; + } + } + + /// + /// Internally used to draw raster lines (brighter) + /// + [Browsable(false)] + public Pen RasterPen + { + get { return mi_RasterPen; } + } + + /// + /// Internally used to draw main coordinates (darker) + /// + [Browsable(false)] + public Pen AxisPen + { + get { return mi_AxisPen; } + } + + /// + /// Internally used for label text + /// + [Browsable(false)] + public SolidBrush LegendBrush + { + get { return mi_LegendBrush; } + } + + /// + /// Here you can add an optional legend which is displayed for the axis. With the + /// property Editor3D.LegendPos you can define where the legend is drawn. If the string + /// is null or empty, no legend will be displayed for this axis. This change will become + /// visible the next time you call Invalidate() + /// + public String LegendText + { + get { return ms_LegendText; } + set { ms_LegendText = value; } + } + + /// + /// In the default rotation angle the axis values are normally increasing from right to + /// left (X), back to front (Y) and bottom to top (Z). If Mirror = true they will + /// decrease instead of increase. This change will become visible the next time you call Invalidate() + /// + public bool Mirror + { + get { return mb_Mirror; } + set + { + if (mb_Mirror == value) + return; + + if (me_Coord == eCoord.Z) + throw new NotImplementedException("Mirroring the Z axis is not implemented because there are multiple issues and it does not make sense to draw the Z values reverse."); + + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + mb_Mirror = value; + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + + /// + /// Example: If the axis has values from 4 to 10 and IncludeZero = false --> the + /// coordinate axis has a range from 4 to 10 IncludeZero = true --> the coordinate + /// axis has a range from 0 to 10 (default) + /// ATTENTION: This setting is ignored if the coordinate system is not drawn (eRaster.Off) + /// This change will become visible the next time you call Invalidate() + /// + public bool IncludeZero + { + get { return mb_IncludeZero; } + set + { + if (mb_IncludeZero == value) + return; + + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + mb_IncludeZero = value; + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + } + + #endregion cAxis + + #region cSelection + + /// + /// This class controls the user selection of points, lines, shapes and polygons + /// + [Serializable] + [TypeConverter(typeof(ExpandableObjectConverter))] + public class cSelection + { + private Editor3D mi_Inst; + private bool mb_Enabled; + private bool mb_MultiSel; + private bool mb_SinglePoints; + private delSelectHandler mf_Callback; + private Color mc_HighlightColor = Color.Empty; + private Brush mi_HighlightBrush; + private Pen mi_HighlightPen; + + public cSelection(Editor3D i_Inst) + { + mi_Inst = i_Inst; + } + + /// + /// This property defines if the user is allowed to select 3D objects. The selection + /// will only be visible if HighlightColor has also been set. The callback will only be + /// called if Callback has also been assigned. + /// + public bool Enabled + { + get { return mb_Enabled; } + set { mb_Enabled = value; } + } + + /// + /// This defines the color with which selected draw objects / points are painted. If you + /// pass Color.Empty draw objects will not be highlighted although they are selected! + /// This change will become visible the next time you call Invalidate() + /// + public Color HighlightColor + { + set + { + if (value.A > 0) + { + mi_HighlightBrush = new SolidBrush(value); + mi_HighlightPen = new Pen(value); + mi_HighlightPen.StartCap = LineCap.Round; + mi_HighlightPen.EndCap = LineCap.Round; + } + else + { + mi_HighlightBrush = null; + mi_HighlightPen = null; + } + mc_HighlightColor = value; + } + get + { + return mc_HighlightColor; + } + } + + [Browsable(false)] + public Pen HighlightPen + { + get + { + if (mb_Enabled) return mi_HighlightPen; + else return null; + } + } + + [Browsable(false)] + public Brush HighlightBrush + { + get + { + if (mb_Enabled) return mi_HighlightBrush; + else return null; + } + } + + /// + /// true --> allow selection of multiple 3D objects at once false --> allow only + /// selection of a single 3D object at a time This setting will be ignored when a + /// Callback is assigned. The callback is responsible for any selection changes! + /// + public bool MultiSelect + { + get { return mb_MultiSel; } + set { mb_MultiSel = value; } + } + + /// + /// Enables selection of single points. true --> the user can only select single + /// points of a polygon or the end points of a line. false --> the user can only + /// select an entire polygon or an entire line. For scatter shapes this setting does not + /// matter because a scatter shape corresponds to one point. + /// IMPORTANT: Selecting entire polygons makes only sense if Fill mode is used. + /// Otherwise polygons are transparent and a click would hit the background behind the + /// polygon, so this setting is ignored for polygons in Line mode. This change will + /// become visible the next time you call Invalidate() + /// + public bool SinglePoints + { + get { return mb_SinglePoints; } + set + { + mb_SinglePoints = value; + + // A mix of selected lines or polygons and selected points is possible but the + // user will be confused. + DeSelectAll(); + } + } + + /// + /// IMPORTANT: Read the detailed comment of function SelectionCallback() at the end of + /// this class. You can set null here to turn off the callback. + /// + [Browsable(false)] + public delSelectHandler Callback + { + set { mf_Callback = value; } + get { return mf_Callback; } + } + + /// + /// Returns selected cLine3D, cShape3D or cPoygon3D objects. Multiple enums can be + /// combined. Example: GetSelectedObjects(eSelType.Line | eSelType.Shape) + /// NOTE: For Shape3D the selection of the shape itself and it's point is always the same. + /// + public cObject3D[] GetSelectedObjects(eSelType e_SelType) + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + + List i_List = new List(); + foreach (cDrawObj i_Object in mi_Inst.mi_UserObjects) + { + if (!i_Object.Selected || (i_Object.SelType & e_SelType) == 0) + continue; + + i_List.Add(i_Object.mi_Object3D); + } + return i_List.ToArray(); + } + + /// + /// Gets the points that the user may select individually if Selection.SinglePoints = + /// true and gets all points of a Line3D or Polygon3D if it is selected. Multiple enums + /// can be combined. Example: GetSelectedPoints(eSelType.Line | eSelType.Shape) + /// NOTE: The returned array contains only unique points. + /// NOTE: For Shape3D the selection of the shape itself and it's point is always the same. + /// + public cPoint3D[] GetSelectedPoints(eSelType e_SelType) + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + + List i_Unique = new List(); + + foreach (cDrawObj i_Object in mi_Inst.mi_UserObjects) + { + if ((i_Object.SelType & e_SelType) == 0) + continue; + + // If the object itself is selected return all it's points, no matter if the + // points are selected or not. + if (i_Object.Selected) + { + foreach (cPoint i_Point in i_Object.mi_Points) + { + if (!i_Unique.Contains(i_Point.mi_P3D)) + i_Unique.Add(i_Point.mi_P3D); + } + } + else // Object not selected --> check if single points of the object are selected + { + foreach (cPoint i_Point in i_Object.mi_Points) + { + if (i_Point.mi_P3D.Selected && + !i_Unique.Contains(i_Point.mi_P3D)) + i_Unique.Add(i_Point.mi_P3D); + } + } + } + return i_Unique.ToArray(); + } + + /// + /// Remove the selection from all draw objects This change will become visible the next + /// time you call Invalidate() + /// + public void DeSelectAll() + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + + foreach (cDrawObj i_Obj in mi_Inst.mi_UserObjects) + { + i_Obj.Selected = false; + + foreach (cPoint i_Point in i_Obj.mi_Points) + { + i_Point.mi_P3D.Selected = false; + } + } + + mi_Inst.mi_UndoBuffer.Store(); + } + } + + #endregion cSelection + + #region cUndoBuffer + + /// + /// This public base class is exposed to the user. The implementation is in cUndoImpl. + /// + [Serializable] + [TypeConverter(typeof(ExpandableObjectConverter))] + public abstract class cUndoBuffer + { + /// + /// Enable the Undo Buffer only if you need the Undo functionality. By default Undo / + /// Redo is disabled. + /// + public virtual bool Enabled + { + get; + set; + } + + /// + /// Clears the Undo Buffer. + /// + public virtual void Clear() + { + throw new NotImplementedException(); + } + + /// + /// Undo the last user operation. return false if Undo is not available. This is also + /// called when hitting CTRL + Z while the 3D Editor has the keyboard focus. + /// + public virtual bool Undo() + { + throw new NotImplementedException(); + } + + /// + /// Redo the last user operation. return false if Redo is not available. This is also + /// called when hitting CTRL + Y while the 3D Editor has the keyboard focus. + /// + public virtual bool Redo() + { + throw new NotImplementedException(); + } + } + + #endregion cUndoBuffer + + #region cColorScheme + + /// + /// Pens and Brushes are GDI+ objects which must not be created on the fly in Draw() For + /// speed optimization these are created only once and stored in this class. + /// + public class cColorScheme + { + // ========================= STATIC =========================== + + public static Color[] GetSchema(eColorScheme e_Scheme) + { + Byte[,] u8_RGB; + switch (e_Scheme) + { + case eColorScheme.Green: return new Color[] { Color.Green }; + case eColorScheme.Rainbow_Sweep: return CalcRainbow(6); // all colors, also magenta + case eColorScheme.Rainbow_Bright: return CalcRainbow(4); // from red to blue, no magenta + case eColorScheme.Monochrome: return new Color[] { Color.Goldenrod }; + case eColorScheme.Autumn: u8_RGB = new byte[,] { { 255, 0, 0 }, { 255, 4, 0 }, { 255, 8, 0 }, { 255, 12, 0 }, { 255, 16, 0 }, { 255, 20, 0 }, { 255, 24, 0 }, { 255, 28, 0 }, { 255, 32, 0 }, { 255, 36, 0 }, { 255, 40, 0 }, { 255, 45, 0 }, { 255, 49, 0 }, { 255, 53, 0 }, { 255, 57, 0 }, { 255, 61, 0 }, { 255, 65, 0 }, { 255, 69, 0 }, { 255, 73, 0 }, { 255, 77, 0 }, { 255, 81, 0 }, { 255, 85, 0 }, { 255, 89, 0 }, { 255, 93, 0 }, { 255, 97, 0 }, { 255, 101, 0 }, { 255, 105, 0 }, { 255, 109, 0 }, { 255, 113, 0 }, { 255, 117, 0 }, { 255, 121, 0 }, { 255, 125, 0 }, { 255, 130, 0 }, { 255, 134, 0 }, { 255, 138, 0 }, { 255, 142, 0 }, { 255, 146, 0 }, { 255, 150, 0 }, { 255, 154, 0 }, { 255, 158, 0 }, { 255, 162, 0 }, { 255, 166, 0 }, { 255, 170, 0 }, { 255, 174, 0 }, { 255, 178, 0 }, { 255, 182, 0 }, { 255, 186, 0 }, { 255, 190, 0 }, { 255, 194, 0 }, { 255, 198, 0 }, { 255, 202, 0 }, { 255, 206, 0 }, { 255, 210, 0 }, { 255, 215, 0 }, { 255, 219, 0 }, { 255, 223, 0 }, { 255, 227, 0 }, { 255, 231, 0 }, { 255, 235, 0 }, { 255, 239, 0 }, { 255, 243, 0 }, { 255, 247, 0 }, { 255, 251, 0 }, { 255, 255, 0 } }; break; + case eColorScheme.Cool: u8_RGB = new byte[,] { { 0, 255, 255 }, { 4, 251, 255 }, { 8, 247, 255 }, { 12, 243, 255 }, { 16, 239, 255 }, { 20, 235, 255 }, { 24, 231, 255 }, { 28, 227, 255 }, { 32, 223, 255 }, { 36, 219, 255 }, { 40, 215, 255 }, { 45, 210, 255 }, { 49, 206, 255 }, { 53, 202, 255 }, { 57, 198, 255 }, { 61, 194, 255 }, { 65, 190, 255 }, { 69, 186, 255 }, { 73, 182, 255 }, { 77, 178, 255 }, { 81, 174, 255 }, { 85, 170, 255 }, { 89, 166, 255 }, { 93, 162, 255 }, { 97, 158, 255 }, { 101, 154, 255 }, { 105, 150, 255 }, { 109, 146, 255 }, { 113, 142, 255 }, { 117, 138, 255 }, { 121, 134, 255 }, { 125, 130, 255 }, { 130, 125, 255 }, { 134, 121, 255 }, { 138, 117, 255 }, { 142, 113, 255 }, { 146, 109, 255 }, { 150, 105, 255 }, { 154, 101, 255 }, { 158, 97, 255 }, { 162, 93, 255 }, { 166, 89, 255 }, { 170, 85, 255 }, { 174, 81, 255 }, { 178, 77, 255 }, { 182, 73, 255 }, { 186, 69, 255 }, { 190, 65, 255 }, { 194, 61, 255 }, { 198, 57, 255 }, { 202, 53, 255 }, { 206, 49, 255 }, { 210, 45, 255 }, { 215, 40, 255 }, { 219, 36, 255 }, { 223, 32, 255 }, { 227, 28, 255 }, { 231, 24, 255 }, { 235, 20, 255 }, { 239, 16, 255 }, { 243, 12, 255 }, { 247, 8, 255 }, { 251, 4, 255 }, { 255, 0, 255 } }; break; + case eColorScheme.Copper: u8_RGB = new byte[,] { { 0, 0, 0 }, { 5, 3, 2 }, { 10, 6, 4 }, { 15, 9, 6 }, { 20, 13, 8 }, { 25, 16, 10 }, { 30, 19, 12 }, { 35, 22, 14 }, { 40, 25, 16 }, { 46, 28, 18 }, { 51, 32, 20 }, { 56, 35, 22 }, { 61, 38, 24 }, { 66, 41, 26 }, { 71, 44, 28 }, { 76, 47, 30 }, { 81, 51, 32 }, { 86, 54, 34 }, { 91, 57, 36 }, { 96, 60, 38 }, { 101, 63, 40 }, { 106, 66, 42 }, { 111, 70, 44 }, { 116, 73, 46 }, { 121, 76, 48 }, { 126, 79, 50 }, { 132, 82, 52 }, { 137, 85, 54 }, { 142, 89, 56 }, { 147, 92, 58 }, { 152, 95, 60 }, { 157, 98, 62 }, { 162, 101, 64 }, { 167, 104, 66 }, { 172, 108, 68 }, { 177, 111, 70 }, { 182, 114, 72 }, { 187, 117, 75 }, { 192, 120, 77 }, { 197, 123, 79 }, { 202, 126, 81 }, { 207, 130, 83 }, { 212, 133, 85 }, { 218, 136, 87 }, { 223, 139, 89 }, { 228, 142, 91 }, { 233, 145, 93 }, { 238, 149, 95 }, { 243, 152, 97 }, { 248, 155, 99 }, { 253, 158, 101 }, { 255, 161, 103 }, { 255, 164, 105 }, { 255, 168, 107 }, { 255, 171, 109 }, { 255, 174, 111 }, { 255, 177, 113 }, { 255, 180, 115 }, { 255, 183, 117 }, { 255, 187, 119 }, { 255, 190, 121 }, { 255, 193, 123 }, { 255, 196, 125 }, { 255, 199, 127 } }; break; + case eColorScheme.Hot: u8_RGB = new byte[,] { { 11, 0, 0 }, { 21, 0, 0 }, { 32, 0, 0 }, { 43, 0, 0 }, { 53, 0, 0 }, { 64, 0, 0 }, { 74, 0, 0 }, { 85, 0, 0 }, { 96, 0, 0 }, { 106, 0, 0 }, { 117, 0, 0 }, { 128, 0, 0 }, { 138, 0, 0 }, { 149, 0, 0 }, { 159, 0, 0 }, { 170, 0, 0 }, { 181, 0, 0 }, { 191, 0, 0 }, { 202, 0, 0 }, { 213, 0, 0 }, { 223, 0, 0 }, { 234, 0, 0 }, { 244, 0, 0 }, { 255, 0, 0 }, { 255, 11, 0 }, { 255, 21, 0 }, { 255, 32, 0 }, { 255, 43, 0 }, { 255, 53, 0 }, { 255, 64, 0 }, { 255, 74, 0 }, { 255, 85, 0 }, { 255, 96, 0 }, { 255, 106, 0 }, { 255, 117, 0 }, { 255, 128, 0 }, { 255, 138, 0 }, { 255, 149, 0 }, { 255, 159, 0 }, { 255, 170, 0 }, { 255, 181, 0 }, { 255, 191, 0 }, { 255, 202, 0 }, { 255, 213, 0 }, { 255, 223, 0 }, { 255, 234, 0 }, { 255, 244, 0 }, { 255, 255, 0 }, { 255, 255, 16 }, { 255, 255, 32 }, { 255, 255, 48 }, { 255, 255, 64 }, { 255, 255, 80 }, { 255, 255, 96 }, { 255, 255, 112 }, { 255, 255, 128 }, { 255, 255, 143 }, { 255, 255, 159 }, { 255, 255, 175 }, { 255, 255, 191 }, { 255, 255, 207 }, { 255, 255, 223 }, { 255, 255, 239 }, { 255, 255, 255 } }; break; + case eColorScheme.Hsv: u8_RGB = new byte[,] { { 255, 0, 0 }, { 255, 24, 0 }, { 255, 48, 0 }, { 255, 72, 0 }, { 255, 96, 0 }, { 255, 120, 0 }, { 255, 143, 0 }, { 255, 167, 0 }, { 255, 191, 0 }, { 255, 215, 0 }, { 255, 239, 0 }, { 247, 255, 0 }, { 223, 255, 0 }, { 199, 255, 0 }, { 175, 255, 0 }, { 151, 255, 0 }, { 128, 255, 0 }, { 104, 255, 0 }, { 80, 255, 0 }, { 56, 255, 0 }, { 32, 255, 0 }, { 8, 255, 0 }, { 0, 255, 16 }, { 0, 255, 40 }, { 0, 255, 64 }, { 0, 255, 88 }, { 0, 255, 112 }, { 0, 255, 135 }, { 0, 255, 159 }, { 0, 255, 183 }, { 0, 255, 207 }, { 0, 255, 231 }, { 0, 255, 255 }, { 0, 231, 255 }, { 0, 207, 255 }, { 0, 183, 255 }, { 0, 159, 255 }, { 0, 135, 255 }, { 0, 112, 255 }, { 0, 88, 255 }, { 0, 64, 255 }, { 0, 40, 255 }, { 0, 16, 255 }, { 8, 0, 255 }, { 32, 0, 255 }, { 56, 0, 255 }, { 80, 0, 255 }, { 104, 0, 255 }, { 128, 0, 255 }, { 151, 0, 255 }, { 175, 0, 255 }, { 199, 0, 255 }, { 223, 0, 255 }, { 247, 0, 255 }, { 255, 0, 239 }, { 255, 0, 215 }, { 255, 0, 191 }, { 255, 0, 167 }, { 255, 0, 143 }, { 255, 0, 120 }, { 255, 0, 96 }, { 255, 0, 72 }, { 255, 0, 48 }, { 255, 0, 24 } }; break; + case eColorScheme.Rainbow_Dark: u8_RGB = new byte[,] { { 0, 0, 143 }, { 0, 0, 159 }, { 0, 0, 175 }, { 0, 0, 191 }, { 0, 0, 207 }, { 0, 0, 223 }, { 0, 0, 239 }, { 0, 0, 255 }, { 0, 16, 255 }, { 0, 32, 255 }, { 0, 48, 255 }, { 0, 64, 255 }, { 0, 80, 255 }, { 0, 96, 255 }, { 0, 112, 255 }, { 0, 128, 255 }, { 0, 143, 255 }, { 0, 159, 255 }, { 0, 175, 255 }, { 0, 191, 255 }, { 0, 207, 255 }, { 0, 223, 255 }, { 0, 239, 255 }, { 0, 255, 255 }, { 16, 255, 239 }, { 32, 255, 223 }, { 48, 255, 207 }, { 64, 255, 191 }, { 80, 255, 175 }, { 96, 255, 159 }, { 112, 255, 143 }, { 128, 255, 128 }, { 143, 255, 112 }, { 159, 255, 96 }, { 175, 255, 80 }, { 191, 255, 64 }, { 207, 255, 48 }, { 223, 255, 32 }, { 239, 255, 16 }, { 255, 255, 0 }, { 255, 239, 0 }, { 255, 223, 0 }, { 255, 207, 0 }, { 255, 191, 0 }, { 255, 175, 0 }, { 255, 159, 0 }, { 255, 143, 0 }, { 255, 128, 0 }, { 255, 112, 0 }, { 255, 96, 0 }, { 255, 80, 0 }, { 255, 64, 0 }, { 255, 48, 0 }, { 255, 32, 0 }, { 255, 16, 0 }, { 255, 0, 0 }, { 239, 0, 0 }, { 223, 0, 0 }, { 207, 0, 0 }, { 191, 0, 0 }, { 175, 0, 0 }, { 159, 0, 0 }, { 143, 0, 0 }, { 128, 0, 0 } }; break; + case eColorScheme.Pink: u8_RGB = new byte[,] { { 30, 0, 0 }, { 50, 26, 26 }, { 64, 37, 37 }, { 75, 45, 45 }, { 85, 52, 52 }, { 94, 59, 59 }, { 102, 64, 64 }, { 110, 69, 69 }, { 117, 74, 74 }, { 123, 79, 79 }, { 130, 83, 83 }, { 136, 87, 87 }, { 141, 91, 91 }, { 147, 95, 95 }, { 152, 98, 98 }, { 157, 102, 102 }, { 162, 105, 105 }, { 167, 108, 108 }, { 172, 111, 111 }, { 176, 114, 114 }, { 181, 117, 117 }, { 185, 120, 120 }, { 189, 123, 123 }, { 194, 126, 126 }, { 195, 132, 129 }, { 197, 138, 131 }, { 199, 144, 134 }, { 201, 149, 136 }, { 202, 154, 139 }, { 204, 159, 141 }, { 206, 164, 144 }, { 207, 169, 146 }, { 209, 174, 148 }, { 211, 178, 151 }, { 212, 183, 153 }, { 214, 187, 155 }, { 216, 191, 157 }, { 217, 195, 160 }, { 219, 199, 162 }, { 220, 203, 164 }, { 222, 207, 166 }, { 223, 211, 168 }, { 225, 215, 170 }, { 226, 218, 172 }, { 228, 222, 174 }, { 229, 225, 176 }, { 231, 229, 178 }, { 232, 232, 180 }, { 234, 234, 185 }, { 235, 235, 191 }, { 237, 237, 196 }, { 238, 238, 201 }, { 240, 240, 206 }, { 241, 241, 211 }, { 243, 243, 216 }, { 244, 244, 221 }, { 245, 245, 225 }, { 247, 247, 230 }, { 248, 248, 234 }, { 250, 250, 238 }, { 251, 251, 243 }, { 252, 252, 247 }, { 254, 254, 251 }, { 255, 255, 255 } }; break; + case eColorScheme.Spring: u8_RGB = new byte[,] { { 255, 0, 255 }, { 255, 4, 251 }, { 255, 8, 247 }, { 255, 12, 243 }, { 255, 16, 239 }, { 255, 20, 235 }, { 255, 24, 231 }, { 255, 28, 227 }, { 255, 32, 223 }, { 255, 36, 219 }, { 255, 40, 215 }, { 255, 45, 210 }, { 255, 49, 206 }, { 255, 53, 202 }, { 255, 57, 198 }, { 255, 61, 194 }, { 255, 65, 190 }, { 255, 69, 186 }, { 255, 73, 182 }, { 255, 77, 178 }, { 255, 81, 174 }, { 255, 85, 170 }, { 255, 89, 166 }, { 255, 93, 162 }, { 255, 97, 158 }, { 255, 101, 154 }, { 255, 105, 150 }, { 255, 109, 146 }, { 255, 113, 142 }, { 255, 117, 138 }, { 255, 121, 134 }, { 255, 125, 130 }, { 255, 130, 125 }, { 255, 134, 121 }, { 255, 138, 117 }, { 255, 142, 113 }, { 255, 146, 109 }, { 255, 150, 105 }, { 255, 154, 101 }, { 255, 158, 97 }, { 255, 162, 93 }, { 255, 166, 89 }, { 255, 170, 85 }, { 255, 174, 81 }, { 255, 178, 77 }, { 255, 182, 73 }, { 255, 186, 69 }, { 255, 190, 65 }, { 255, 194, 61 }, { 255, 198, 57 }, { 255, 202, 53 }, { 255, 206, 49 }, { 255, 210, 45 }, { 255, 215, 40 }, { 255, 219, 36 }, { 255, 223, 32 }, { 255, 227, 28 }, { 255, 231, 24 }, { 255, 235, 20 }, { 255, 239, 16 }, { 255, 243, 12 }, { 255, 247, 8 }, { 255, 251, 4 }, { 255, 255, 0 } }; break; + case eColorScheme.Summer: u8_RGB = new byte[,] { { 0, 128, 102 }, { 4, 130, 102 }, { 8, 132, 102 }, { 12, 134, 102 }, { 16, 136, 102 }, { 20, 138, 102 }, { 24, 140, 102 }, { 28, 142, 102 }, { 32, 144, 102 }, { 36, 146, 102 }, { 40, 148, 102 }, { 45, 150, 102 }, { 49, 152, 102 }, { 53, 154, 102 }, { 57, 156, 102 }, { 61, 158, 102 }, { 65, 160, 102 }, { 69, 162, 102 }, { 73, 164, 102 }, { 77, 166, 102 }, { 81, 168, 102 }, { 85, 170, 102 }, { 89, 172, 102 }, { 93, 174, 102 }, { 97, 176, 102 }, { 101, 178, 102 }, { 105, 180, 102 }, { 109, 182, 102 }, { 113, 184, 102 }, { 117, 186, 102 }, { 121, 188, 102 }, { 125, 190, 102 }, { 130, 192, 102 }, { 134, 194, 102 }, { 138, 196, 102 }, { 142, 198, 102 }, { 146, 200, 102 }, { 150, 202, 102 }, { 154, 204, 102 }, { 158, 206, 102 }, { 162, 208, 102 }, { 166, 210, 102 }, { 170, 212, 102 }, { 174, 215, 102 }, { 178, 217, 102 }, { 182, 219, 102 }, { 186, 221, 102 }, { 190, 223, 102 }, { 194, 225, 102 }, { 198, 227, 102 }, { 202, 229, 102 }, { 206, 231, 102 }, { 210, 233, 102 }, { 215, 235, 102 }, { 219, 237, 102 }, { 223, 239, 102 }, { 227, 241, 102 }, { 231, 243, 102 }, { 235, 245, 102 }, { 239, 247, 102 }, { 243, 249, 102 }, { 247, 251, 102 }, { 251, 253, 102 }, { 255, 255, 102 } }; break; + case eColorScheme.Winter: u8_RGB = new byte[,] { { 0, 0, 255 }, { 0, 4, 253 }, { 0, 8, 251 }, { 0, 12, 249 }, { 0, 16, 247 }, { 0, 20, 245 }, { 0, 24, 243 }, { 0, 28, 241 }, { 0, 32, 239 }, { 0, 36, 237 }, { 0, 40, 235 }, { 0, 45, 233 }, { 0, 49, 231 }, { 0, 53, 229 }, { 0, 57, 227 }, { 0, 61, 225 }, { 0, 65, 223 }, { 0, 69, 221 }, { 0, 73, 219 }, { 0, 77, 217 }, { 0, 81, 215 }, { 0, 85, 213 }, { 0, 89, 210 }, { 0, 93, 208 }, { 0, 97, 206 }, { 0, 101, 204 }, { 0, 105, 202 }, { 0, 109, 200 }, { 0, 113, 198 }, { 0, 117, 196 }, { 0, 121, 194 }, { 0, 125, 192 }, { 0, 130, 190 }, { 0, 134, 188 }, { 0, 138, 186 }, { 0, 142, 184 }, { 0, 146, 182 }, { 0, 150, 180 }, { 0, 154, 178 }, { 0, 158, 176 }, { 0, 162, 174 }, { 0, 166, 172 }, { 0, 170, 170 }, { 0, 174, 168 }, { 0, 178, 166 }, { 0, 182, 164 }, { 0, 186, 162 }, { 0, 190, 160 }, { 0, 194, 158 }, { 0, 198, 156 }, { 0, 202, 154 }, { 0, 206, 152 }, { 0, 210, 150 }, { 0, 215, 148 }, { 0, 219, 146 }, { 0, 223, 144 }, { 0, 227, 142 }, { 0, 231, 140 }, { 0, 235, 138 }, { 0, 239, 136 }, { 0, 243, 134 }, { 0, 247, 132 }, { 0, 251, 130 }, { 0, 255, 128 } }; break; + default: + throw new ArgumentException("Invalid enum"); + } + + Color[] c_Schema = new Color[u8_RGB.GetLength(0)]; + for (int i = 0; i < c_Schema.Length; i++) + { + c_Schema[i] = Color.FromArgb(u8_RGB[i, 0], u8_RGB[i, 1], u8_RGB[i, 2]); + } + return c_Schema; + } + + /// + /// s32_Sweeps = 4 --> Colors from blue to red, not including magenta (1024 colors) + /// s32_Sweeps = 6 --> Complete sweep with all rainbow colors, also magenta (1536 colors) + /// + private static Color[] CalcRainbow(int s32_Sweeps) + { + Color[] c_Colors = new Color[s32_Sweeps * 256]; + int R, G, B, P = 0; + for (int L = 0; L < s32_Sweeps; L++) + { + for (int E = 0; E < 256; E++) + { + switch (L) + { + case 0: R = 0; G = E; B = 255; break; // Blue...Cyan + case 1: R = 0; G = 255; B = 255 - E; break; // Cyan...Green + case 2: R = E; G = 255; B = 0; break; // Green...Yellow + case 3: R = 255; G = 255 - E; B = 0; break; // Yellow...Red + case 4: R = 255; G = 0; B = E; break; // Red...Magenta + case 5: R = 255 - E; G = 0; B = 255; break; // Magenta...Blue + default: throw new ArgumentException(); + } + c_Colors[P++] = Color.FromArgb(255, R, G, B); + } + } + return c_Colors; + } + + // ========================= MEMBER =========================== + + private Brush[] mi_Brushes; + private Pen[] mi_Pens; + + public int ColorCount + { + get { return mi_Brushes.Length; } + } + + /// + /// Constructor 1 + /// + public cColorScheme(eColorScheme e_Scheme) + : this(GetSchema(e_Scheme)) + { + } + + /// + /// Constructor 2 If you want to draw the entire graph with only one color, you can pass + /// a single Color here. + /// + public cColorScheme(params Color[] c_Colors) + { + if (c_Colors.Length == 0) + throw new ArgumentException("A ColorScheme needs at least one color."); + + mi_Brushes = new SolidBrush[c_Colors.Length]; + mi_Pens = new Pen[c_Colors.Length]; + + for (int i = 0; i < c_Colors.Length; i++) + { + mi_Brushes[i] = new SolidBrush(c_Colors[i]); + mi_Pens[i] = new Pen(c_Colors[i], 1); // the width of the Pen will be modified later. + } + } + + public Brush GetBrush(int s32_Index) + { + s32_Index = Math.Max(0, s32_Index); + return mi_Brushes[s32_Index % mi_Brushes.Length]; + } + + public Pen GetPen(int s32_Index) + { + s32_Index = Math.Max(0, s32_Index); + return mi_Pens[s32_Index % mi_Pens.Length]; + } + + public int CalcIndex(double d_FactorZ) + { + if (double.IsNaN(d_FactorZ)) + return -1; + + d_FactorZ = Math.Min(1.0, d_FactorZ); + d_FactorZ = Math.Max(0.0, d_FactorZ); + + // d_FactorZ is a value between 0.0 and 1.0 + return (int)(d_FactorZ * (mi_Brushes.Length - 1)); + } + } + + #endregion cColorScheme + + #region cUserInput + + public class cUserInput + { + private MouseButtons me_MouseButton; + private Keys me_Modifiers; + private eMouseAction me_Action; + private Cursor mi_Cursor; + + /// + /// A unique identifier for the combination of mouse button and modifier(s) Keys.Shift = + /// 0x00010000 Keys.Control = 0x00020000 Keys.Alt = 0x00040000 MouseButtons.Left = + /// 0x00100000 MouseButtons.Right = 0x00200000 MouseButtons.Middle = 0x00400000 + /// MouseButtons.XButton1 = 0x00800000 MouseButtons.XButton2 = 0x01000000 + /// + public int UID + { + get { return (int)me_MouseButton | (int)me_Modifiers; } + } + + public MouseButtons MouseButton + { + get { return me_MouseButton; } + } + + public Keys Modifiers + { + get { return me_Modifiers; } + } + + public eMouseAction Action + { + get { return me_Action; } + } + + public Cursor Cursor + { + get { return mi_Cursor; } + } + + public cUserInput(MouseButtons e_MouseButton, Keys e_Modifiers, eMouseAction e_Action, Cursor i_Cursor = null) + { + Debug.Assert((e_Modifiers & Keys.KeyCode) == 0, "Invalid parameter e_Modifiers"); + Debug.Assert(e_MouseButton != MouseButtons.None, "Invalid parameter e_MouseButton"); + Debug.Assert(e_Action != eMouseAction.None, "Invalid parameter e_Action"); + + me_MouseButton = e_MouseButton; + me_Modifiers = e_Modifiers; + me_Action = e_Action; + mi_Cursor = i_Cursor; + + if (mi_Cursor == null) + { + switch (e_Action) + { + case eMouseAction.ThetaAndPhi: mi_Cursor = Cursors.SizeAll; break; + case eMouseAction.Theta: mi_Cursor = Cursors.NoMoveVert; break; + case eMouseAction.Phi: mi_Cursor = Cursors.NoMoveHoriz; break; + case eMouseAction.Rho: mi_Cursor = Cursors.SizeNS; break; + case eMouseAction.Move: mi_Cursor = Cursors.NoMove2D; break; + default: mi_Cursor = Cursors.Arrow; break; + } + } + } + + /// + /// For debugging in Visual Studio + /// + public override string ToString() + { + return String.Format("MouseButton: {0}, Modifiers: {1} --> Action: {2}", me_MouseButton, me_Modifiers, me_Action); + } + } + + #endregion cUserInput + + // --------------------- + + #region cRenderData + + /// + /// Base class for cSurfaceData, cScatterData, cLineData, cPolygonData + /// + public abstract class cRenderData + { + public virtual void AddDrawObjects(Editor3D i_Inst) + { + throw new NotImplementedException(); + } + + /// + /// The width and color of the Pen may be modified later. So the immutable framework + /// collection like Pens.Black,... cannot be used here. + /// + protected static void CheckPenMutable(Pen i_Pen, cColorScheme i_ColorScheme) + { + if (i_Pen != null && i_ColorScheme != null) + { + try { i_Pen.Color = Color.BlanchedAlmond; } + catch { throw new ArgumentException("To use a color scheme create a new Pen. Do not use the immutable Pens.XYZ collection."); } + } + } + } + + #endregion cRenderData + + #region cSurfaceData + + public class cSurfaceData : cRenderData + { + private bool mb_Fill; + private bool mb_Missing; + private int ms32_Cols; + private int ms32_Rows; + private int ms32_Radius; + private Pen mi_Pen; + private cPoint3D[,] mi_PointArray; + private cPolygon3D[,] mi_PolygonArray; + private cColorScheme mi_ColorScheme; + + public cColorScheme ColorScheme + { + get { return mi_ColorScheme; } + } + + /// + /// The count of points in one column of the surface grid + /// + public int Cols + { + get { return ms32_Cols; } + } + + /// + /// The count of points in one row of the surface grid + /// + public int Rows + { + get { return ms32_Rows; } + } + + /// + /// Fill Mode: ------------ Polygons are filled with a color from the ColorScheme. If + /// you want only one color, set a ColorScheme which contains only one color. The Pen is + /// used to draw the thin lines between the polygons (mostly black, 1 pixel) If Pen is + /// null, no lines are drawn. + /// + /// Line Mode: ------------ Only the border lines of the polygons are drawn. The Pen is + /// used to draw these lines. The Pen's color and width will be modified. + /// + /// Missing Mode: -------------- s32_Radius > 0 allows missing points. s32_Radius + /// defines the radius of cicles that represent points which have not enough neigbours + /// to draw a polygon. + /// + public cSurfaceData(int s32_Cols, int s32_Rows, ePolygonMode e_Mode, Pen i_Pen, cColorScheme i_ColorScheme, + int s32_Radius = 0) + { + if (s32_Cols < 3 || s32_Rows < 3) + throw new ArgumentException("cSurfaceData needs at least 3 columns and 3 rows"); + + if (e_Mode == ePolygonMode.Fill) + { + if (i_ColorScheme == null) + throw new ArgumentException("In Fill mode you must specify a ColorScheme"); + + // The border pen is allowed to be immutable. It will not be changed. + } + else // Lines + { + if (i_Pen == null) + throw new ArgumentException("In Line mode you must specify a Line Pen"); + + CheckPenMutable(i_Pen, i_ColorScheme); + } + + mb_Fill = e_Mode == ePolygonMode.Fill; + mb_Missing = s32_Radius > 0; + ms32_Radius = s32_Radius; + ms32_Cols = s32_Cols; + ms32_Rows = s32_Rows; + mi_Pen = i_Pen; + mi_ColorScheme = i_ColorScheme; + mi_PointArray = new cPoint3D[s32_Cols, s32_Rows]; + } + + /// + /// Here you can set a callback function which will be called with X,Y to calculate the + /// Z values of the points. + /// + public void ExecuteFunction(delRendererFunction f_Function, PointF k_Start, PointF k_End) + { + mb_Missing = false; + + double d_StepX = (k_End.X - k_Start.X) / (ms32_Cols - 1); + double d_StepY = (k_End.Y - k_Start.Y) / (ms32_Rows - 1); + + for (int C = 0; C < ms32_Cols; C++) + { + double d_X = k_Start.X + d_StepX * C; + + for (int R = 0; R < ms32_Rows; R++) + { + double d_Y = k_Start.Y + d_StepY * R; + double d_Z = f_Function(d_X, d_Y); + + SetPointAt(C, R, new cPoint3D(d_X, d_Y, d_Z)); + } + } + } + + /// + /// IMPORTANT: The X coordinate of the point must be related to the column. The Y + /// coordinate of the point must be related to the row. + /// + public void SetPointAt(int s32_Column, int s32_Row, cPoint3D i_Point3D) + { + if (mi_PolygonArray != null) + throw new Exception("You cannot call cSurfaceData.SetPointAt() or ExecuteFunction() anymore after calling GetPolygonAt() " + + "or Editor3D.AddRenderData(). To modify a point after the polygons have been created call " + + "GetPointAt() and modify the X,Y,Z values of the returned point."); + + mi_PointArray[s32_Column, s32_Row] = i_Point3D; + } + + /// + /// ATTENTION: In mode 'Missing' null may be returned or polygons with only 3 corners! + /// + public cPoint3D GetPointAt(int s32_Column, int s32_Row) + { + return mi_PointArray[s32_Column, s32_Row]; + } + + /// + /// ATTENTION: The polygons have one row less than cSurfaceData.Rows and one column less + /// than cSurfaceData.Cols + /// + public cPolygon3D GetPolygonAt(int s32_Column, int s32_Row) + { + CreatePolygons(); + return mi_PolygonArray[s32_Column, s32_Row]; + } + + private void CreatePolygons() + { + if (mi_PolygonArray != null) + return; + + cPolygon3D[,] i_TempArr = new cPolygon3D[ms32_Cols - 1, ms32_Rows - 1]; + + List i_Valid = new List(); + for (int C = 0; C < ms32_Cols - 1; C++) + { + for (int R = 0; R < ms32_Rows - 1; R++) + { + i_Valid.Clear(); + if (mi_PointArray[C, R] != null) i_Valid.Add(mi_PointArray[C, R]); + if (mi_PointArray[C, R + 1] != null) i_Valid.Add(mi_PointArray[C, R + 1]); + if (mi_PointArray[C + 1, R + 1] != null) i_Valid.Add(mi_PointArray[C + 1, R + 1]); + if (mi_PointArray[C + 1, R] != null) i_Valid.Add(mi_PointArray[C + 1, R]); + + if (i_Valid.Count < 4 && !mb_Missing) + throw new Exception("cSurfaceData: You must call cSurfaceData.SetPointAt() for all points!"); + + if (i_Valid.Count < 3) + continue; // A polygon needs at least 3 points + + i_TempArr[C, R] = new cPolygon3D(C, R, null, i_Valid.ToArray()); + } + } + + mi_PolygonArray = i_TempArr; + } + + // ============================================================================= + + /// + /// Called from AddRenderData() + /// + public override void AddDrawObjects(Editor3D i_Inst) + { + CreatePolygons(); + + bool b_Added = false; + List i_Used = new List(); + + foreach (cPolygon3D i_Poly3D in mi_PolygonArray) + { + if (i_Poly3D == null) + continue; + + i_Inst.AddDrawObject(new cPolygon(mb_Fill, i_Poly3D, mi_Pen, mi_ColorScheme)); + b_Added = true; + + foreach (cPoint3D i_Point3D in i_Poly3D.Points) + { + if (!i_Used.Contains(i_Point3D)) + i_Used.Add(i_Point3D); + } + } + + // Add all the remaining points as Scatter circles that are not part of a polygon. + for (int C = 0; C < ms32_Cols; C++) + { + for (int R = 0; R < ms32_Rows; R++) + { + cPoint3D i_Point3D = mi_PointArray[C, R]; + if (i_Point3D == null || i_Used.Contains(i_Point3D)) + continue; + + cShape3D i_Shape3D = new cShape3D(C, R, i_Point3D, eScatterShape.Circle, ms32_Radius, null); + i_Inst.AddDrawObject(new cShape(i_Shape3D, mi_ColorScheme)); + b_Added = true; + } + } + + if (!b_Added) + throw new Exception("You cannot draw a completely empty SurfaceData"); + } + } + + #endregion cSurfaceData + + #region cScatterData + + public class cScatterData : cRenderData + { + private List mi_Shapes3D = new List(); + private cColorScheme mi_ColorScheme; + + public cShape3D[] AllShapes + { + get { return mi_Shapes3D.ToArray(); } + } + + public cColorScheme ColorScheme + { + get { return mi_ColorScheme; } + } + + /// + /// Constructor If all Scatter shapes contain a valid Brush, you can pass i_ColorScheme + /// == null here + /// + public cScatterData(cColorScheme i_ColorScheme) + { + mi_ColorScheme = i_ColorScheme; + } + + public cScatterData() + { + } /// /// s32_Radius defines the size of the shape and i_Brush the color /// public cShape3D AddShape(cPoint3D i_Point, eScatterShape e_Shape, int s32_Radius, Brush i_Brush, Object o_Tag = null) - { - cShape3D i_Shape3D = new cShape3D(-1, -1, i_Point, e_Shape, s32_Radius, i_Brush, o_Tag); - mi_Shapes3D.Add(i_Shape3D); - return i_Shape3D; - } - - // ============================================================================= - - /// - /// Called from AddRenderData() - /// - public override void AddDrawObjects(Editor3D i_Inst) - { - foreach (cShape3D i_Shape3D in mi_Shapes3D) - { - i_Inst.AddDrawObject(new cShape(i_Shape3D, mi_ColorScheme)); - } - } - } - - #endregion - - #region cLineData - - public class cLineData : cRenderData - { - private List mi_Lines3D = new List(); - private cColorScheme mi_ColorScheme; - - public cLine3D[] AllLines - { - get { return mi_Lines3D.ToArray(); } - } - - public cColorScheme ColorScheme - { - get { return mi_ColorScheme; } - } - - /// - /// Constructor - /// If you use only solid lines and specify a valid Pen, you can pass i_ColorScheme == null here - /// - public cLineData(cColorScheme i_ColorScheme) - { - mi_ColorScheme = i_ColorScheme; - } - - /// - /// Add a line which will be drawn entirely in one color. - /// - public cLine3D AddSolidLine(cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, Object o_Tag = null) - { - CheckPenMutable(i_Pen, mi_ColorScheme); - - cLine3D i_Line3D = new cLine3D(i_Start, i_End, s32_Width, i_Pen, 1, o_Tag); - mi_Lines3D.Add(i_Line3D); - return i_Line3D; - } - - /// - /// Add a line which will appear with multiple colors of the ColorScheme by drawing it in multiple parts. - /// If s32_Parts = 50, the line is rendered in 50 parts where each part has it's own color depending on the Z coordinate. - /// - public cLine3D AddMultiColorLine(int s32_Parts, cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, Object o_Tag = null) - { - if (s32_Parts < 3) - throw new ArgumentException("Multi color lines require at least 3 parts"); - - if (mi_ColorScheme == null) - throw new Exception("To create a multi-color line you must specify a ColorScheme"); - - CheckPenMutable(i_Pen, mi_ColorScheme); - - cLine3D i_Line3D = new cLine3D(i_Start, i_End, s32_Width, i_Pen, s32_Parts, o_Tag); - mi_Lines3D.Add(i_Line3D); - return i_Line3D; - } - - /// - /// Creates connected lines from the points in the given order - /// - public cLine3D[] AddConnectedLines(List i_Points, int s32_Width, Pen i_Pen) - { - CheckPenMutable(i_Pen, mi_ColorScheme); - - List i_NewLines = new List(); - - cPoint3D i_Prev = null; - for (int i=0; i - /// Called from AddRenderData() - /// - public override void AddDrawObjects(Editor3D i_Inst) - { - foreach (cLine3D i_Line3D in mi_Lines3D) - { - i_Inst.AddDrawObject(new cLine(i_Line3D, mi_ColorScheme)); - } - } - } - - #endregion - - #region cPolygonData - - public class cPolygonData : cRenderData - { - private bool mb_Fill; - private Pen mi_Pen; - private cColorScheme mi_ColorScheme; - private List mi_Polygons3D = new List(); - - public cPolygon3D[] AllPolygons - { - get { return mi_Polygons3D.ToArray(); } - } - - public cColorScheme ColorScheme - { - get { return mi_ColorScheme; } - } - - /// - /// Fill Mode: - /// ------------ - /// Polygons are filled with a color from the ColorScheme. - /// If you want only one color, set a ColorScheme which contains only one color. - /// The Pen is used to draw the thin lines between the polygons (mostly black, 1 pixel) - /// If Pen is null, no lines are drawn. - /// - /// Line Mode: - /// ------------ - /// Only the border lines of the polygons are drawn. - /// The Pen is used to draw these lines. The Pen's color and width will be modified. - /// - public cPolygonData(ePolygonMode e_Mode, Pen i_Pen, cColorScheme i_ColorScheme) - { - if (e_Mode == ePolygonMode.Lines) - { - if (i_Pen == null) - throw new ArgumentException("In Line mode you must specify a Line Pen"); - - CheckPenMutable(i_Pen, i_ColorScheme); - } - - mb_Fill = e_Mode == ePolygonMode.Fill; - mi_Pen = i_Pen; - mi_ColorScheme = i_ColorScheme; - } - - /// - /// In contrast to other drawing libraries (like WPF or Direct3D) you can add polygons of any dimension here. - /// A polygon can have any amount of corners (minimum 3). - /// The Brush can be specified in Fill mode. To use a Brush from the ColorScheme set i_Brush = null. - /// - public cPolygon3D AddPolygon(Brush i_Brush, params cPoint3D[] i_Points3D) - { - cPolygon3D i_Polygon3D = new cPolygon3D(-1, -1, i_Brush, i_Points3D); - mi_Polygons3D.Add(i_Polygon3D); - return i_Polygon3D; - } - - // ============================================================================= - - /// - /// Called from AddRenderData() - /// - public override void AddDrawObjects(Editor3D i_Inst) - { - foreach (cPolygon3D i_Polygon3D in mi_Polygons3D) - { - i_Inst.AddDrawObject(new cPolygon(mb_Fill, i_Polygon3D, mi_Pen, mi_ColorScheme)); - } - } - } - - #endregion - - #region cMessgData - - public class cMessgData - { - private String ms_Text; - private Brush mi_Brush; - private int ms32_PosX; - private int ms32_PosY; - private Font mi_Font; - private SizeF mk_Size; - - /// - /// Here you can change the text without loading all the render objects again. - /// The change will become visible the next time you call Invalidate() - /// - public String Text - { - set - { - ms_Text = value; - mk_Size = SizeF.Empty; - } - } - - /// - /// Here you can change the text color without loading all the render objects again. - /// The change will become visible the next time you call Invalidate() - /// - public Color TextColor - { - set { mi_Brush = new SolidBrush(value); } - } - - /// - /// If X is negative, it is displayed right aligned at X pixels from the right - /// If Y is negative, it is displayed bottom aligned at Y pixels from the bottom - /// - public cMessgData(String s_Text, int X, int Y, Color c_Color, - FontStyle e_FontStyle = FontStyle.Bold, - int s32_FontSize = 9, - String s_FontFace = "Tahoma") - { - ms_Text = s_Text; - ms32_PosX = X; - ms32_PosY = Y; - mi_Brush = new SolidBrush(c_Color); - mi_Font = new Font(s_FontFace, s32_FontSize, e_FontStyle); - } - - public void Draw(Graphics i_Graph, Rectangle k_Client) - { - if (String.IsNullOrEmpty(ms_Text)) - return; - - float X = ms32_PosX; - float Y = ms32_PosY; - - if (X < 0 || Y < 0) - { - // Speed optimization: Measure the size only once. - if (mk_Size.IsEmpty) - mk_Size = i_Graph.MeasureString(ms_Text, mi_Font); - - if (X < 0) X += k_Client.Width - mk_Size.Width; - if (Y < 0) Y += k_Client.Height - mk_Size.Height; - } - - i_Graph.DrawString(ms_Text, mi_Font, mi_Brush, X, Y); - } - } - - #endregion - - // ================= PRIVATE ================= - - #region cPoint2D - - /// - /// This class represents a point in the 2D space, in pixels. - /// - private class cPoint2D - { - public double md_X; - public double md_Y; - - public cPoint2D() - { - } - - public cPoint2D(double X, double Y) - { - md_X = X; - md_Y = Y; - } - - public cPoint2D Clone() - { - return new cPoint2D(md_X, md_Y); - } - - public PointF Coord - { - get { return new PointF((float)md_X, (float)md_Y); } - } - - public bool IsValid - { - get - { - // The screen will always be smaller than 9999 pixels - return (!Double.IsNaN(md_X) && Math.Abs(md_X) < 9999.9 && - !Double.IsNaN(md_Y) && Math.Abs(md_Y) < 9999.9); - } - } - - /// - /// Use the good old Pythagoras to calculate the pixel distance between this point and X, Y. - /// - public int CalcDistanceTo(int X, int Y) - { - int s32_DiffX = (int)md_X - X; - int s32_DiffY = (int)md_Y - Y; - return (int)Math.Sqrt(s32_DiffX * s32_DiffX + s32_DiffY * s32_DiffY); - } - - // For debugging in Visual Studio - public override string ToString() - { - return String.Format("cPoint2D (X={0}, Y={1})", FormatDouble(md_X), FormatDouble(md_Y)); - } - } - - #endregion - - #region cPoint - - /// - /// This class contains the 3D point and it's projection into the 2D space. - /// - private class cPoint - { - public cPoint3D mi_P3D; - public cPoint2D mi_P2D; - public int ms32_RadiusTip; // if 0 --> no tooltip - - public cPoint(double d_X, double d_Y, double d_Z) - { - mi_P3D = new cPoint3D(d_X, d_Y, d_Z); - mi_P2D = new cPoint2D(); - } - - /// - /// The radius defines at which distance of the mouse from the 2D point the tooltip pops up. - /// Radius = 0 --> no tooltip - /// - public cPoint(cPoint3D i_Point3D, int s32_RadiusTip) - { - mi_P3D = i_Point3D; - mi_P2D = new cPoint2D(); - - if (s32_RadiusTip > 0) - ms32_RadiusTip = s32_RadiusTip + TOOLTIP_RADIUS; - } - - /// - /// Projects the 3D coordinates into the 2D space (pixels on the screen). - /// - public void Project3D(Editor3D i_Inst, eMirror e_Mirror) - { - mi_P2D = i_Inst.mi_Transform.Project3D(mi_P3D, e_Mirror); - } - - /// - /// For Debugging in Visual Studio - /// - public override string ToString() - { - return String.Format("cPoint [{0} = {1}]", mi_P3D, mi_P2D); - } - } - - #endregion - - #region cTooltip - - private class cTooltip - { - private Editor3D mi_Inst = null; - private eTooltip me_Mode = eTooltip.All; - private ToolTip mi_Tooltip = new ToolTip(); - private List mi_Points = new List(); - private cPoint mi_Last = null; - - public eTooltip Mode - { - get { return me_Mode; } - set { me_Mode = value; } - } - - // Constructor - public cTooltip(Editor3D i_Inst) - { - mi_Inst = i_Inst; - - mi_Tooltip.AutoPopDelay = 30000; // The maximum that Windows allows are 32.767 seconds (0x7FFF milliseconds) - mi_Tooltip.InitialDelay = 50; - mi_Tooltip.ReshowDelay = 50; - } - - public void Clear() - { - mi_Points.Clear(); - mi_Last = null; - Hide(); - } - - public void AddPoint(cPoint i_Point) - { - // ATTENTION adding a - // && !mi_Points.Contains(i_Point) - // here would make this function 40 times slower! - // The more points are already in mi_Points, the slower it would become. - // Several points will be added multiple times here, but this does not affect the functioning of the tooltip. - if (i_Point != null && i_Point.ms32_RadiusTip > 0) - mi_Points.Add(i_Point); - } - - public void Hide() - { - mi_Tooltip.Hide(mi_Inst); - } - - public void OnMouseMove(MouseEventArgs e) - { - if (me_Mode == eTooltip.Off) - { - Hide(); - return; - } - - int s32_MouseX = e.X - mi_Inst.mi_Mouse.mk_OffMove.X - mi_Inst.mi_Mouse.mk_OffCoord.X; - int s32_MouseY = e.Y - mi_Inst.mi_Mouse.mk_OffMove.Y - mi_Inst.mi_Mouse.mk_OffCoord.Y; - - int s32_MinDist = int.MaxValue; - cPoint i_Nearest = null; - foreach (cPoint i_Point in mi_Points) - { - int s32_Dist = i_Point.mi_P2D.CalcDistanceTo(s32_MouseX, s32_MouseY); - if (s32_Dist < s32_MinDist) - { - s32_MinDist = s32_Dist; - i_Nearest = i_Point; - } - } - - if (i_Nearest != null && s32_MinDist < i_Nearest.ms32_RadiusTip) - { - if (mi_Last == i_Nearest) - return; // The mouse is still over the same point - - mi_Last = i_Nearest; - - String s_TT = ""; - if ((me_Mode & eTooltip.Coord) > 0) - s_TT = String.Format("X = {0}\nY = {1}\nZ = {2}\n", FormatDouble(i_Nearest.mi_P3D.X), - FormatDouble(i_Nearest.mi_P3D.Y), - FormatDouble(i_Nearest.mi_P3D.Z)); - - if ((me_Mode & eTooltip.UserText) > 0 && i_Nearest.mi_P3D.Tooltip != null) - s_TT += i_Nearest.mi_P3D.Tooltip; - - s_TT = s_TT.Trim(); - if (s_TT.Length > 0) - { - mi_Tooltip.Show(s_TT, mi_Inst, e.X + 10, e.Y + 10); - return; - } - } - - mi_Last = null; - Hide(); - } - } - - #endregion - - #region cBackup - - private class cBackup - { - public Dictionary mi_Changes = new Dictionary(); - - public void StoreProperty(eUndoProp e_Property, Object o_OldValue, Object o_NewValue) - { - Object[] o_Values; - if (mi_Changes.TryGetValue(e_Property, out o_Values)) - { - // The property has changed multiple times (dragging) --> store only the last value - o_Values[1] = o_NewValue; - } - else - { - // The property has changed for the first time - mi_Changes.Add(e_Property, new Object[] { o_OldValue, o_NewValue }); - } - } - - public void Restore(cObject3D i_Object3D, bool b_Undo) - { - foreach (KeyValuePair i_Pair in mi_Changes) - { - if (b_Undo) i_Object3D.Restore(i_Pair.Key, i_Pair.Value[0]); // Undo: old value - else i_Object3D.Restore(i_Pair.Key, i_Pair.Value[1]); // Redo: new value - } - } - - // for debugging in Visual Studio - public override string ToString() - { - StringBuilder i_Dbg = new StringBuilder(); - foreach (eUndoProp e_Property in mi_Changes.Keys) - { - if (i_Dbg.Length > 0) i_Dbg.Append(", "); - i_Dbg.Append(e_Property); - } - return "cBackup Properties: " + i_Dbg.ToString(); - } - } - - #endregion - - #region cUndoEntry - - private class cUndoEntry - { - public Dictionary mi_Backups = new Dictionary(); - public List mi_Added = new List(); - public List mi_Removed = new List(); - - public bool IsEmpty - { - get { return mi_Backups.Count == 0 && mi_Added.Count == 0 && mi_Removed.Count == 0; } - } - - // for debugging in Visual Studio - public override string ToString() - { - return String.Format("Backups: {0}, Added: {1}, Removed: {2}", (mi_Backups == null) ? 0 : mi_Backups.Count, - (mi_Added == null) ? 0 : mi_Added .Count, - (mi_Removed == null) ? 0 : mi_Removed.Count); - } - } - - #endregion - - #region cUndoImpl - - /// - /// This private class stores the changes after each edit operation that the user has made. - /// - private class cUndoImpl : cUndoBuffer - { - private Editor3D mi_Inst; - private int ms32_UndoIdx = 0; // Points to the next Undo item in mi_UndoList - private int ms32_RedoIdx = 0; // Points to the next Redo item in mi_UndoList - private bool mb_Enabled = false; // The Undo Buffer is by default disabled - private bool mb_DoInit = false; // After Clear() execute Init() once. - private bool mb_Pending = false; // User actions are pending that have not yet been stored - private cUndoEntry mi_CurUndo = new cUndoEntry(); - private List mi_UndoList = new List(); - - /// - /// Do not enable the Undo buffer if you don't need the Undo functionality. - /// By default it is disabled. - /// - public override bool Enabled - { - get { return mb_Enabled; } - set - { - Clear(); - mb_Enabled = value; - } - } - - /// - /// Constructor - /// - public cUndoImpl(Editor3D i_Inst) - { - mi_Inst = i_Inst; - } - - public override void Clear() - { - ms32_UndoIdx = 0; - ms32_RedoIdx = 0; - mb_DoInit = true; - mb_Pending = false; - mi_CurUndo = new cUndoEntry(); - mi_UndoList.Clear(); - } - - /// - /// This must only be called from Editor3D.Render() ! - /// The first time calling Invalidate() after Clear() --> clear all draw objets that have been added. - /// Otherwise all added objects would be stored. (This class stores only user changes.) - /// - public void Init() - { - if (mb_DoInit) - { - Clear(); - mb_DoInit = false; - } - } - - /// - /// Called when any public property of a 3D object was changed (e.g. X,Y,Z coordinates of a cPoint3D). - /// These changes will be stored when Store() is called. - /// - public void Backup(cObject3D i_Object3D, eUndoProp e_Property, Object o_OldValue, Object o_NewValue) - { - if (!mb_Enabled) - return; - - cBackup i_Backup; - if (!mi_CurUndo.mi_Backups.TryGetValue(i_Object3D, out i_Backup)) - { - i_Backup = new cBackup(); - mi_CurUndo.mi_Backups.Add(i_Object3D, i_Backup); - } - - i_Backup.StoreProperty(e_Property, o_OldValue, o_NewValue); - mb_Pending = true; - } - - /// - /// Called when the user removes a draw object. - /// These changes will be stored when Store() is called. - /// - public void DrawObjectRemoved(cDrawObj i_DrawObject) - { - if (!mb_Enabled) - return; - - mi_CurUndo.mi_Removed.Add(i_DrawObject); - mb_Pending = true; - } - - /// - /// Called when the user adds a draw object. - /// These changes will be stored when Store() is called. - /// - public void DrawObjectAdded(cDrawObj i_DrawObject) - { - if (!mb_Enabled) - return; - - mi_CurUndo.mi_Added.Add(i_DrawObject); - mb_Pending = true; - } - - /// - /// Store the changes on user objects in the Undo buffer on mouse-up. - /// - public void Store() - { - if (!mb_Enabled || !mb_Pending || mi_CurUndo.IsEmpty) - return; - - // Remove any entries in the Undo Buffer behind the current index - int s32_Remove = mi_UndoList.Count - ms32_UndoIdx - 1; - if (s32_Remove > 0) - mi_UndoList.RemoveRange(ms32_UndoIdx + 1, s32_Remove); - - mi_UndoList.Add(mi_CurUndo); - mi_CurUndo = new cUndoEntry(); - - ms32_UndoIdx = mi_UndoList.Count - 1; - ms32_RedoIdx = mi_UndoList.Count - 1; - mb_Pending = false; - } - - /// - /// This is also called when hitting CTRL + Z while the 3D Editor has the keyboard focus - /// - public override bool Undo() - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - - if (!mb_Enabled || mi_UndoList.Count == 0 || ms32_UndoIdx < 0) - return false; - - Restore(true, ms32_UndoIdx); - - ms32_RedoIdx = ms32_UndoIdx; - ms32_UndoIdx --; - return true; - } - - /// - /// This is also called when hitting CTRL + Y while the 3D Editor has the keyboard focus - /// - public override bool Redo() - { - Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread - - if (!mb_Enabled || mi_UndoList.Count == 0 || ms32_RedoIdx >= mi_UndoList.Count) - return false; - - Restore(false, ms32_RedoIdx); - - ms32_UndoIdx = ms32_RedoIdx; - ms32_RedoIdx ++; - return true; - } - - private void Restore(bool b_Undo, int s32_Index) - { - s32_Index = Math.Max(s32_Index, 0); - s32_Index = Math.Min(s32_Index, mi_UndoList.Count - 1); - - cUndoEntry i_UndoItem = mi_UndoList[s32_Index]; - - foreach (cDrawObj i_Removed in i_UndoItem.mi_Removed) - { - if (b_Undo) mi_Inst.mi_UserObjects.Add (i_Removed); - else mi_Inst.mi_UserObjects.Remove(i_Removed); - - mi_Inst.me_Recalculate |= eRecalculate.AddRemove | eRecalculate.Objects | eRecalculate.CoordSystem; - } - - foreach (cDrawObj i_Added in i_UndoItem.mi_Added) - { - if (b_Undo) mi_Inst.mi_UserObjects.Remove(i_Added); - else mi_Inst.mi_UserObjects.Add (i_Added); - - mi_Inst.me_Recalculate |= eRecalculate.AddRemove | eRecalculate.Objects | eRecalculate.CoordSystem; - } - - foreach (KeyValuePair i_Pair in i_UndoItem.mi_Backups) - { - i_Pair.Value.Restore(i_Pair.Key, b_Undo); - - mi_Inst.me_Recalculate |= eRecalculate.Objects | eRecalculate.CoordSystem; - } - - mi_Inst.Invalidate(); - } - } - - #endregion - - // ----- DrawObjects ----- - - #region cDrawObj - - /// - /// Base class for cLine, cShape, cPolygon - /// - private abstract class cDrawObj : IComparable - { - public Editor3D mi_Inst; - public SmoothingMode me_SmoothMode; - public cPoint[] mi_Points; - public cObject3D mi_Object3D; // This is a cLine3D, cShape3D or cPolygon3D - public double md_Sort; // sorting is important. Always draw first back, then front objects. - public bool mb_IsAxis; // This is a line from the coordinate system - protected double md_AvrgZ; // 3D center of the Z coordinates of all points in this object - protected bool mb_IsValid; - protected eMirror me_Mirror; - - // -------------------------------------------------- - - /// - /// Set after conversion 3D --> 2D. If invalid coordinates are found (NaN or > 9999) this returns false. - /// If projection results in lines of thousands of pixels length, the drawing will become extremely slow. - /// Do not draw lines or polygons outside the screen area. - /// - public bool IsValid - { - get { return mb_IsValid; } - } - - /// - /// The object is selected - /// - public bool Selected - { - get { return mi_Object3D.Selected; } - set { mi_Object3D.Selected = value; } - } - - /// - /// The type of this object - /// - public virtual eSelType SelType - { - get { throw new NotImplementedException(); } - } - - /// - /// Calculates colors from color scheme - /// - public virtual void ProcessColors() - { - throw new NotImplementedException(); - } - - /// - /// Calculates the 2D screen coordinates for each 3D point. - /// - public virtual void Project3D() - { - throw new NotImplementedException(); - } - - /// - /// Draw into Graphics - /// - public virtual void Render(Graphics i_Graph) - { - throw new NotImplementedException(); - } - - /// - /// Check if a user click at mouse point X, Y matches this draw object - /// - public virtual cObject3D MatchesPoint2D(int X, int Y) - { - throw new NotImplementedException(); - } - - /// - /// Uses the center of a draw object (e.g. the middle of a line) to calculate in which order the draw objects are rendered. - /// This is called for all user defined objects, but not for the coordinate system which has it's own logic. - /// - public void CalcSortOrder() - { - Debug.Assert(!mb_IsAxis); // axes have their own value for md_Sort - - double d_AvrgX = 0.0; - double d_AvrgY = 0.0; - md_AvrgZ = 0.0; - - foreach (cPoint i_Point in mi_Points) - { - d_AvrgX += i_Point.mi_P3D.X; - d_AvrgY += i_Point.mi_P3D.Y; - md_AvrgZ += i_Point.mi_P3D.Z; - } - - d_AvrgX /= mi_Points.Length; - d_AvrgY /= mi_Points.Length; - md_AvrgZ /= mi_Points.Length; - - if (mi_Object3D.Row > -1) // only Polygons and Scatter circles created by cSurfaceData - { - int X = mi_Object3D.Column + 1; - int Y = mi_Object3D.Row + 1; - - // Mirror axis values increasing / decreasing - if (mi_Inst.AxisX.Mirror && (me_Mirror & eMirror.X) > 0) X = 5000 - mi_Object3D.Column; - if (mi_Inst.AxisY.Mirror && (me_Mirror & eMirror.Y) > 0) Y = 5000 - mi_Object3D.Row; - - // In case of a surface grid the Z value must be ignored because sorting is ALWAYS based on the position in the grid. - // Using the Z value here may even result in wrong sort order. - md_Sort = mi_Inst.mi_Transform.ProjectXY(X, Y); - } - else - { - double X = d_AvrgX; - double Y = d_AvrgY; - double Z = md_AvrgZ; - - // Mirror axis values increasing / decreasing - if (mi_Inst.AxisX.Mirror && (me_Mirror & eMirror.X) > 0) X = mi_Inst.mi_Bounds.X.Max - (X - mi_Inst.mi_Bounds.X.Min); - if (mi_Inst.AxisY.Mirror && (me_Mirror & eMirror.Y) > 0) Y = mi_Inst.mi_Bounds.Y.Max - (Y - mi_Inst.mi_Bounds.Y.Min); - if (mi_Inst.AxisZ.Mirror && (me_Mirror & eMirror.Z) > 0) Z = mi_Inst.mi_Bounds.Z.Max - (Z - mi_Inst.mi_Bounds.Z.Min); - - // In case of any other 3D object the Z value must also be included in the calculation to avoid artifacts. - // Demo Sphere shows that the Z value is required if you move Theta to an extreme. - X = (X - mi_Inst.mi_Transform.mi_Center3D.X) * mi_Inst.mi_Transform.md_NormalizeX; - Y = (Y - mi_Inst.mi_Transform.mi_Center3D.Y) * mi_Inst.mi_Transform.md_NormalizeY; - Z = (Z - mi_Inst.mi_Transform.mi_Center3D.Z) * mi_Inst.mi_Transform.md_NormalizeZ; - - md_Sort = mi_Inst.mi_Transform.ProjectXY(X, Y, Z); - } - } - - /// - /// Used for sorting all DrawObjects from back to front - /// - int IComparable.CompareTo(Object o_Comp) - { - return md_Sort.CompareTo(((cDrawObj)o_Comp).md_Sort); - } - } - - #endregion - - #region cLine - - private class cLine : cDrawObj - { - private cLine3D mi_Line3D; // object passed to and from the user - private Pen mi_Pen; - private Brush mi_Brush; // assigned to Pen - private float mf_LineWidth; // Linewidth with zoom factor - private float mf_SelSize; // size of selection points - - // -------- coordinate axes -------- - public double md_Angle; // needed to calculate current rotation quadrant of coordinate axis - public eCoord me_Line = eCoord.Invalid; // main coordinate in coordinate direction - public eCoord me_Offset = eCoord.Invalid; // secondary coordinate in coordinate direction - public String ms_Label; // Label for axis - - // ---------- multicolor ----------- - private cPoint[] mi_ColorPoints; // all points on the line that are drawn separately - private Brush[] mi_ColorBrushes; // all Brushes which are assigned to the Pen - private cColorScheme mi_ColorScheme; - - public override eSelType SelType - { - get { return eSelType.Line; } - } - - /// - /// Constructor 1 for coordinate system. - /// LineWidth is always 1. - /// - public cLine(Editor3D i_Inst, eCoord e_Line, eCoord e_Offset, eMirror e_Mirror) - { - if (e_Line == e_Offset) mi_Pen = i_Inst.mi_Axis[(int)e_Line] .AxisPen; // Main axis - else mi_Pen = i_Inst.mi_Axis[(int)e_Offset].RasterPen; // Raster line - - mi_Inst = i_Inst; - me_Line = e_Line; - me_Offset = e_Offset; - mi_Brush = mi_Pen.Brush; - mi_Points = new cPoint[2]; - mi_Points[0] = new cPoint(0, 0, 0); - mi_Points[1] = new cPoint(0, 0, 0); - mb_IsAxis = true; - me_Mirror = e_Mirror; - me_SmoothMode = SmoothingMode.AntiAlias; - } - - /// - /// Constructor 2 for user lines - /// if i_Line3D.Pen == null --> Pen from ColorScheme is used - /// if i_Line3D.Pen != null --> ColorScheme will be ignored, even if a ColorScheme is specified - /// An indvidual LineWidth can be defined in each cLine3D. - /// - public cLine(cLine3D i_Line3D, cColorScheme i_ColorScheme) - { - if (i_Line3D.Pen == null && i_ColorScheme == null) - throw new ArgumentException("You must specify a Pen or a ColorScheme"); - - mi_ColorScheme = i_ColorScheme; - mi_Line3D = i_Line3D; - mi_Object3D = i_Line3D; - mi_Pen = i_Line3D.Pen; // get user's Pen or null - mi_Points = new cPoint[2]; - mi_Points[0] = new cPoint(i_Line3D.Points[0], 1); - mi_Points[1] = new cPoint(i_Line3D.Points[1], 1); - mb_IsAxis = false; - me_Mirror = eMirror.All; - me_SmoothMode = SmoothingMode.AntiAlias; - - if (mi_Pen != null) - { - // The original Brush must be stored separately because the same Pen may be used for multiple instances of cLine - // Changing the color of one line would affect all the others. - mi_Brush = mi_Pen.Brush; - } - else - { - mi_Pen = new Pen(Brushes.Black); // color and width will be changed below - mi_Brush = null; // Brush will be taken from colorscheme - } - mi_Pen.StartCap = LineCap.Round; - mi_Pen.EndCap = LineCap.Round; - - // ---------- multi color --------- - - if (i_Line3D.ColorParts > 1) - { - mi_ColorBrushes = new Brush [i_Line3D.ColorParts]; - mi_ColorPoints = new cPoint[i_Line3D.ColorParts]; - } - } - - public override void ProcessColors() - { - Debug.Assert(!mb_IsAxis); // axes have their own color - - // If the user has changed the Pen --> use the new Pen and it's Brush - if (mi_Line3D.Pen != null) - { - mi_Pen = mi_Line3D.Pen; - mi_Brush = mi_Pen.Brush; - - mi_Pen.StartCap = LineCap.Round; - mi_Pen.EndCap = LineCap.Round; - } - - if (mi_ColorPoints != null) // multicolor line - { - double d_X = mi_Points[0].mi_P3D.X; - double d_Y = mi_Points[0].mi_P3D.Y; - double d_Z = mi_Points[0].mi_P3D.Z; - - double d_DeltaX = (mi_Points[1].mi_P3D.X - d_X) / mi_ColorPoints.Length; - double d_DeltaY = (mi_Points[1].mi_P3D.Y - d_Y) / mi_ColorPoints.Length; - double d_DeltaZ = (mi_Points[1].mi_P3D.Z - d_Z) / mi_ColorPoints.Length; - - mi_ColorPoints[0] = mi_Points[0]; // Set Start Point - - cPoint i_Prev = mi_Points[0]; - for (int i=1; i - /// Check if a user click at X, Y matches this draw object - /// - public override cObject3D MatchesPoint2D(int X, int Y) - { - if (mb_IsAxis) - return null; // do not allow to select axis lines - - int s32_MaxDist = Math.Max(1, (int)mi_Pen.Width / 2) + SELECT_RADIUS; - - if (mi_Inst.Selection.SinglePoints) - { - foreach (cPoint i_Point in mi_Points) - { - if (i_Point.mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) - return i_Point.mi_P3D; - } - } - else // select entire line - { - if (IsPointOnLine(mi_Points[0].mi_P2D, mi_Points[1].mi_P2D, X, Y, s32_MaxDist)) - return mi_Line3D; - } - - return null; - } - - // ---------------- Coord System --------------- - - /// - /// Used while creating coordinate system - /// Check if 2 lines have the same coordinates. - /// - public bool CoordEquals(cLine i_Line) - { - return mi_Points[0].mi_P3D.CoordEquals(i_Line.mi_Points[0].mi_P3D) && - mi_Points[1].mi_P3D.CoordEquals(i_Line.mi_Points[1].mi_P3D); - } - - /// - /// Used while creating coordinate system - /// Calculate the angle of the 3 main axes on the screen in a range from 0 to 360 degree. - /// - public void CalcAngle2D() - { - double d_DX = mi_Points[1].mi_P2D.md_X - mi_Points[0].mi_P2D.md_X; - double d_DY = mi_Points[1].mi_P2D.md_Y - mi_Points[0].mi_P2D.md_Y; - md_Angle = Math.Atan2(d_DY, d_DX) * 180.0 / Math.PI; - if (md_Angle < 0.0) md_Angle += 360.0; - } - - // For debugging in Visual Studio - public override string ToString() - { - String s_Dbg = String.Format("cLine from {0} to {1}", mi_Points[0], mi_Points[1]); - if (mb_IsAxis) - s_Dbg += String.Format(" (Axis {0}, {1})", me_Line, me_Offset); - - return s_Dbg; - } - } - - #endregion - - #region cShape - - private class cShape : cDrawObj - { - private float mf_Radius; // radius of shape adapted with Zoom factor - private float mf_Diameter; // diameter of shape adapted with Zoom factor - private PointF mk_TopLeft; // top left corner in screen coordinates for all types of shapes - private PointF[] mk_Polygon; // used for triangles or any future user objects - private Brush mi_Brush; - private cShape3D mi_Shape3D; - private cColorScheme mi_ColorScheme; - - public override eSelType SelType - { - get { return eSelType.Shape; } - } - - /// - /// Constructor - /// - public cShape(cShape3D i_Shape3D, cColorScheme i_ColorScheme) - { - if (i_Shape3D.Brush == null && i_ColorScheme == null) - throw new ArgumentException("You must specify a Brush or a ColorScheme"); - - mi_Shape3D = i_Shape3D; - mi_Object3D = i_Shape3D; - mi_Points = new cPoint[1]; - mi_Points[0] = new cPoint(i_Shape3D.Points[0], i_Shape3D.Radius); - mi_ColorScheme = i_ColorScheme; - me_SmoothMode = SmoothingMode.AntiAlias; - me_Mirror = eMirror.All; - } - - public override void ProcessColors() - { - // If the user has specified an individual brush for this Shape --> always use it - mi_Brush = mi_Shape3D.Brush; - - // Otherwise use Brush from ColorScheme - if (mi_Brush == null) - { - double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(md_AvrgZ); - int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); - mi_Brush = mi_ColorScheme.GetBrush (s32_Index); - } - } - - public override void Project3D() - { - mi_Points[0].Project3D(mi_Inst, me_Mirror); - - mb_IsValid = mi_Points[0].mi_P2D.IsValid; - mf_Radius = (float)(mi_Shape3D.Radius * mi_Inst.mi_Transform.md_Zoom); - mf_Diameter = mf_Radius * 2.0f; - - // Move coordinate from center to upper left corner of circle - mk_TopLeft = mi_Points[0].mi_P2D.Coord; - mk_TopLeft.X -= mf_Radius; - mk_TopLeft.Y -= mf_Radius; - - switch (mi_Shape3D.Shape) - { - case eScatterShape.Triangle: - mk_Polygon = new PointF[3]; - // top center - mk_Polygon[0].X = mk_TopLeft.X + mf_Radius; - mk_Polygon[0].Y = mk_TopLeft.Y; - // bottom left - mk_Polygon[1].X = mk_TopLeft.X; - mk_Polygon[1].Y = mk_TopLeft.Y + mf_Diameter; - // bottom right - mk_Polygon[2].X = mk_TopLeft.X + mf_Diameter; - mk_Polygon[2].Y = mk_TopLeft.Y + mf_Diameter; - break; - - // case eScatterShape.Star: - // Here you can implement your own shapes - // break; - } - } - - public override void Render(Graphics i_Graph) - { - bool b_DrawSel = Selected && mi_Inst.mi_Selection.HighlightBrush != null; - Brush i_DrawBrush = b_DrawSel ? mi_Inst.mi_Selection.HighlightBrush : mi_Brush; - - switch (mi_Shape3D.Shape) - { - case eScatterShape.Circle: - i_Graph.FillEllipse (i_DrawBrush, mk_TopLeft.X, mk_TopLeft.Y, mf_Diameter, mf_Diameter); - break; - case eScatterShape.Square: - i_Graph.FillRectangle(i_DrawBrush, mk_TopLeft.X, mk_TopLeft.Y, mf_Diameter, mf_Diameter); - break; - default: - i_Graph.FillPolygon (i_DrawBrush, mk_Polygon); - break; - } - } - - /// - /// Check if a user click at X, Y matches this draw object - /// - public override cObject3D MatchesPoint2D(int X, int Y) - { - int s32_MaxDist = (int)mf_Radius + SELECT_RADIUS; - - if (mi_Points[0].mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) - return mi_Shape3D; - - return null; - } - - /// - /// For Debugging in Visual Studio - /// - public override string ToString() - { - return String.Format("cShape {0} at {1}, Diameter {2}", mi_Shape3D.Shape, mi_Points[0], FormatDouble(mf_Diameter)); - } - } - - #endregion - - #region cPolygon - - private class cPolygon : cDrawObj - { - private bool mb_Fill; // Fill / Line mode - private float mf_SelSize; // size of selection points - private PointF[] mk_Screen; // the 2D polygon corner points in screen coordinates - private int ms32_OrgWidth; // original line width for Line Pen - private float mf_LineWidth; // zoomed line width for Line Pen - private Pen mi_LinePen; // used in Line mode - private Pen mi_BorderPen; // used in Fill mode (not zoomed) - private Brush mi_Brush; // used in Fill mode - private cPolygon3D mi_Polygon3D; - private cColorScheme mi_ColorScheme; - - public override eSelType SelType - { - get { return eSelType.Polygon; } - } - - /// - /// Constructor - /// - public cPolygon(bool b_Fill, cPolygon3D i_Polygon3D, Pen i_Pen, cColorScheme i_ColorScheme) - { - mi_Points = new cPoint[i_Polygon3D.Points.Length]; - for (int i=0; i always use it - if (mi_Polygon3D.Brush != null) - { - mi_Brush = mi_Polygon3D.Brush; - } - else if (mi_ColorScheme != null) // ColorScheme is never null in Fill mode - { - double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(md_AvrgZ); - int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); - mi_Brush = mi_ColorScheme.GetBrush (s32_Index); // used for Fill and assigned to LinePen - } - } - - public override void Project3D() - { - foreach (cPoint i_Point in mi_Points) - { - i_Point.Project3D(mi_Inst, me_Mirror); - } - - // Line width for Line mode - mf_LineWidth = (float)(ms32_OrgWidth * mi_Inst.mi_Transform.md_Zoom); - - // Diameter of circle for selected points - mf_SelSize = (float)(Math.Max(6, ms32_OrgWidth * 2) * mi_Inst.mi_Transform.md_Zoom); - - mb_IsValid = true; - for (int i=0; i draw thin black border lines around the polygons - // Line mode --> draw thicker lines around transparent polygons - if (i_DrawPen != null) - { - // ATTENTION: Graphics.DrawPolygon() with a Pen > 1 pixel is buggy in the .NET framework (artifacts)! - // The lines must be drawn one by one manually here. - int T = mk_Screen.Length - 1; - for (int F=0; F - /// Check if a user click at X, Y matches this draw object - /// - public override cObject3D MatchesPoint2D(int X, int Y) - { - // ATTENTION: Selecting entire polygons makes only sense with eSurfaceMode.Fill - // In line mode polygons are transparent and a click into the polygon would go to the background. - if (!mb_Fill || mi_Inst.mi_Selection.SinglePoints) - { - int s32_MaxDist = (int)mf_SelSize / 2 + SELECT_RADIUS; - foreach (cPoint i_Point in mi_Points) - { - if (i_Point.mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) - return i_Point.mi_P3D; - } - } - else // select entire polygon - { - // Detect if the point is inside the polygon. Here SELECT_RADIUS is ignored. - // But the user must only click into the middle of the polygon, which is easier than clicking a thin line. - bool b_Result = false; - int k = mi_Points.Length - 1; - for (int i = 0; i < mi_Points.Length; i++) - { - cPoint2D i_Point1 = mi_Points[i].mi_P2D; - cPoint2D i_Point2 = mi_Points[k].mi_P2D; - - if (i_Point1.md_Y < Y && i_Point2.md_Y >= Y || - i_Point2.md_Y < Y && i_Point1.md_Y >= Y) - { - if (i_Point1.md_X + (Y - i_Point1.md_Y) / - (i_Point2.md_Y - i_Point1.md_Y) * - (i_Point2.md_X - i_Point1.md_X) < X) - { - b_Result = !b_Result; - } - } - k = i; - } - if (b_Result) - return mi_Polygon3D; - } - return null; - } - - /// - /// For debugging in Visual Studio - /// - public override string ToString() - { - return String.Format("cPolygon ({0} points)", mk_Screen.Length); - } - } - - #endregion - - // ----- Math Stuff ------ - - #region cMouse - - private class cMouse - { - public eMouseAction me_Action; // left mouse button action - public Point mk_LastPos; // last mouse location - public Point mk_OffMove; // Mouse offset after moving the graph with the mouse - public Point mk_OffCoord; // Offset caused by labels in coordinate system - public TrackBar mi_TrackRho; // Rho trackbar (optional) - public TrackBar mi_TrackTheta; // Theta trackbar (optional) - public TrackBar mi_TrackPhi; // Phi trackbar (optional) - public double md_Rho = VALUES_RHO .Default; - public double md_Theta = VALUES_THETA.Default; - public double md_Phi = VALUES_PHI .Default; - - public void AssignTrackbar(eMouseAction e_Trackbar, TrackBar i_Trackbar, EventHandler i_OnScroll) - { - if (i_Trackbar == null) - return; - - cDefault i_Default = null; - switch (e_Trackbar) - { - case eMouseAction.Rho: - i_Default = VALUES_RHO; - mi_TrackRho = i_Trackbar; - break; - case eMouseAction.Theta: - i_Default = VALUES_THETA; - mi_TrackTheta = i_Trackbar; - break; - case eMouseAction.Phi: - i_Default = VALUES_PHI; - mi_TrackPhi = i_Trackbar; - break; - } - - i_Trackbar.Minimum = (int)i_Default.Min; - i_Trackbar.Maximum = (int)i_Default.Max; - i_Trackbar.Value = (int)i_Default.Default; - i_Trackbar.Scroll += i_OnScroll; - } - - /// - /// User has moved the TrackBar - /// - public void OnTrackBarScroll() - { - if (mi_TrackRho != null) md_Rho = mi_TrackRho .Value; - if (mi_TrackTheta != null) md_Theta = mi_TrackTheta.Value; - if (mi_TrackPhi != null) md_Phi = mi_TrackPhi .Value; - } - - public bool OnMouseWheel(int s32_Delta) - { - if (me_Action != eMouseAction.None) - return false; - - me_Action = eMouseAction.Rho; - OnMouseMove(0, s32_Delta / 10); - me_Action = eMouseAction.None; - return true; - } - - /// - /// User has dragged the mouse over the 3D control - /// - public void OnMouseMove(int s32_DiffX, int s32_DiffY) - { - if (me_Action == eMouseAction.Rho) - { - md_Rho += s32_DiffY * VALUES_RHO.MouseFactor; - SetRho(md_Rho); - } - if (me_Action == eMouseAction.Theta || me_Action == eMouseAction.ThetaAndPhi) - { - md_Theta -= s32_DiffY * VALUES_THETA.MouseFactor; - SetTheta(md_Theta); - } - if (me_Action == eMouseAction.Phi || me_Action == eMouseAction.ThetaAndPhi) - { - md_Phi -= s32_DiffX * VALUES_PHI.MouseFactor; - SetPhi(md_Phi); - } - } - - public void SetRho(double d_Rho) - { - md_Rho = d_Rho; - md_Rho = Math.Max(md_Rho, VALUES_RHO.Min); - md_Rho = Math.Min(md_Rho, VALUES_RHO.Max); - if (mi_TrackRho != null) - mi_TrackRho.Value = (int)md_Rho; - } - public void SetTheta(double d_Theta) - { - md_Theta = d_Theta; - md_Theta = Math.Max(md_Theta, VALUES_THETA.Min); - md_Theta = Math.Min(md_Theta, VALUES_THETA.Max); - if (mi_TrackTheta != null) - mi_TrackTheta.Value = (int)md_Theta; - } - public void SetPhi(double d_Phi) - { - md_Phi = d_Phi; - while (md_Phi > 360.0) md_Phi -= 360.0; // continuous rotation - while (md_Phi < 0.0) md_Phi += 360.0; // continuous rotation - if (mi_TrackPhi != null) - mi_TrackPhi.Value = (int)md_Phi; - } - } - - #endregion - - #region cRange - - private class cRange - { - private double md_Min, md_Max; - - public double Min - { - get { return md_Min; } - } - public double Max - { - get { return md_Max; } - } - public double Range - { - get { return md_Max - md_Min; } - } - - /// - /// Constructor - /// - public cRange(double d_Min, double d_Max, bool b_IncludeZero, eRaster e_Raster) - { - md_Min = d_Min; - md_Max = d_Max; - - if (md_Max == md_Min) - { - md_Min -= 1.0; - md_Max += 1.0; - } - - if (e_Raster == eRaster.Off) - return; - - if (b_IncludeZero) - { - md_Min = Math.Min(0.0, md_Min); - md_Max = Math.Max(0.0, md_Max); - } - - // Add 10 % excess to all axes - if (md_Min < 0.0 || (md_Min > 0.0 && !b_IncludeZero)) md_Min -= Math.Abs(md_Min) * AXIS_EXCESS; - if (md_Max > 0.0 || (md_Max < 0.0 && !b_IncludeZero)) md_Max += Math.Abs(md_Max) * AXIS_EXCESS; - } - } - - #endregion - - #region cBounds - - private class cBounds - { - private Editor3D mi_Inst; - private cRange mi_RangeX; - private cRange mi_RangeY; - private cRange mi_RangeZ; - private double md_MinX, md_MaxX, md_MinY, md_MaxY, md_MinZ, md_MaxZ; - - public cRange X - { - get { return mi_RangeX; } - } - public cRange Y - { - get { return mi_RangeY; } - } - public cRange Z - { - get { return mi_RangeZ; } - } - - /// - /// Constructor - /// - public cBounds(Editor3D i_Inst) - { - mi_Inst = i_Inst; - } - - /// - /// Also assigns mi_Inst to all draw objects - /// - public void Calculate() - { - md_MinX = double.PositiveInfinity; - md_MaxX = double.NegativeInfinity; - md_MinY = double.PositiveInfinity; - md_MaxY = double.NegativeInfinity; - md_MinZ = double.PositiveInfinity; - md_MaxZ = double.NegativeInfinity; - - foreach (cDrawObj i_DrawObj in mi_Inst.mi_UserObjects) - { - i_DrawObj.mi_Inst = mi_Inst; - i_DrawObj.mi_Object3D.mi_Inst = mi_Inst; - - foreach (cPoint i_Point in i_DrawObj.mi_Points) - { - cPoint3D i_Point3D = i_Point.mi_P3D; - i_Point3D.mi_Inst = mi_Inst; - - md_MinX = Math.Min(md_MinX, i_Point3D.X); - md_MaxX = Math.Max(md_MaxX, i_Point3D.X); - md_MinY = Math.Min(md_MinY, i_Point3D.Y); - md_MaxY = Math.Max(md_MaxY, i_Point3D.Y); - md_MinZ = Math.Min(md_MinZ, i_Point3D.Z); - md_MaxZ = Math.Max(md_MaxZ, i_Point3D.Z); - } - } - - mi_RangeX = new cRange(md_MinX, md_MaxX, mi_Inst.AxisX.IncludeZero, mi_Inst.me_Raster); - mi_RangeY = new cRange(md_MinY, md_MaxY, mi_Inst.AxisY.IncludeZero, mi_Inst.me_Raster); - mi_RangeZ = new cRange(md_MinZ, md_MaxZ, mi_Inst.AxisZ.IncludeZero, mi_Inst.me_Raster); - } - - /// - /// Used to get the color from the ColorScheme - /// - public double CalcFactorZ(double d_Value) - { - return (d_Value - md_MinZ) / (md_MaxZ - md_MinZ); - } - } - - #endregion - - #region cQuadrant - - private class cQuadrant - { - public double md_SortXY; // Sort order of raster in area XY (red) - public double md_SortXZ; // Sort order of X axis and raster in area XZ (blue) - public double md_SortYZ; // Sort order of Y axis and raster in area YZ (green) - public int ms32_Quadrant; - public bool mb_BottomView; - - public void Calculate(double d_Phi, cLine i_AxisX, cLine i_AxisY, cLine i_AxisZ) - { - // Split rotation into 4 sections (0...3) which increment every 90° starting at 45° - int s32_Section45 = (int)d_Phi + 45; - if (s32_Section45 > 360) s32_Section45 -= 360; - s32_Section45 = Math.Min(3, s32_Section45 / 90); - - // Theta elevation lets the camera watch the graph from the top or bottom - switch (s32_Section45) - { - case 0: mb_BottomView = i_AxisX.md_Angle < 180.0; break; - case 1: mb_BottomView = i_AxisY.md_Angle < 180.0; break; - case 2: mb_BottomView = i_AxisX.md_Angle > 180.0; break; - case 3: mb_BottomView = i_AxisY.md_Angle > 180.0; break; - } - - // The quadrant changes when the 2D transformed Z axis is in line with the X or Y axis - if (mb_BottomView) - { - switch (s32_Section45) - { - case 0: ms32_Quadrant = i_AxisX.md_Angle + 180.0 < i_AxisZ.md_Angle ? 1 : 0; break; - case 1: ms32_Quadrant = i_AxisY.md_Angle + 180.0 < i_AxisZ.md_Angle ? 2 : 1; break; - case 2: ms32_Quadrant = i_AxisX.md_Angle < i_AxisZ.md_Angle ? 3 : 2; break; - case 3: ms32_Quadrant = i_AxisY.md_Angle < i_AxisZ.md_Angle ? 0 : 3; break; - } - } - else // Top View - { - switch (s32_Section45) - { - case 0: ms32_Quadrant = i_AxisX.md_Angle > i_AxisZ.md_Angle ? 1 : 0; break; - case 1: ms32_Quadrant = i_AxisY.md_Angle > i_AxisZ.md_Angle ? 2 : 1; break; - case 2: ms32_Quadrant = i_AxisX.md_Angle + 180.0 > i_AxisZ.md_Angle ? 3 : 2; break; - case 3: ms32_Quadrant = i_AxisY.md_Angle + 180.0 > i_AxisZ.md_Angle ? 0 : 3; break; - } - } - - md_SortXY = (mb_BottomView) ? 99999.9 : -99999.9; - md_SortXZ = (ms32_Quadrant == 1 || ms32_Quadrant == 2) ? 99999.9 : -99999.9; - md_SortYZ = (ms32_Quadrant == 0 || ms32_Quadrant == 1) ? 99999.9 : -99999.9; - - i_AxisX.md_Sort = md_SortXZ; - i_AxisY.md_Sort = md_SortYZ; - i_AxisZ.md_Sort = (ms32_Quadrant == 3) ? -99999.9 : 99999.9; - - // Debug.WriteLine(String.Format("Section: {0} Quadrant: {1}", s32_Section45, ms32_Quadrant)); - } - } - - #endregion - - #region cTransform - - private class cTransform - { - // Camera distance. Smaller values result in ugly stretched egdes when rotating. - private const double DISTANCE = 0.55; - - private double md_sf; // sf = sinus fi - private double md_st; // st = sinus theta - private double md_cf; // cf = cosinus fi - private double md_ct; // ct = cosinus theta - private double md_Rho; - // ---------------- - private double md_FactX; - private double md_OffsX; - private double md_FactY; - private double md_OffsY; - private double md_Resize = 1.0; - // ---------------- - public cPoint3D mi_Center3D = new cPoint3D(0,0,0); - public double md_NormalizeX; - public double md_NormalizeY; - public double md_NormalizeZ; - public double md_Zoom; - // ---------------- - Size mk_InitialSize = Size.Empty; - Editor3D mi_Inst; - - public cTransform(Editor3D i_Inst) - { - mi_Inst = i_Inst; - } - - public void SetCoefficients(cMouse i_Mouse) - { - md_Rho = i_Mouse.md_Rho; // Distance of viewer (zoom) - double d_Theta = i_Mouse.md_Theta * Math.PI / 180.0; // Height of viewer (elevation) - double d_Phi = (i_Mouse.md_Phi -180.0) * Math.PI / 180.0; // Rotation around center (-Pi ... +Pi) - - // Speed optimization: precalculate factors - md_sf = Math.Sin(d_Phi); - md_cf = Math.Cos(d_Phi); - md_st = Math.Sin(d_Theta); // Theta = 0...pi --> st = 0 .. 1 .. 0 - md_ct = Math.Cos(d_Theta); // Theta = 0...pi --> ct = 1 .. 0 .. -1 - - CalcZoom(); - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - - /// - /// The initial size is needed to calculate the user resizing factor. - /// To assure that it is correct it must be set when the control has already been created. - /// Then it will be the size that was defined in Visual Studio Form Designer. - /// - public void SetInitialSize(Size k_Size) - { - mk_InitialSize = k_Size; - SetSize(k_Size); - } - - /// - /// The control has been resized. - /// This may be called with an invalid size before the control is created! - /// - public void SetSize(Size k_Size) // Control.ClientSize - { - if (mk_InitialSize == Size.Empty) - return; - - double d_Width = k_Size.Width * 0.0254 / 96.0; // 0.0254 meter = 1 inch. Screen has 96 DPI - double d_Height = k_Size.Height * 0.0254 / 96.0; - - // linear transformation coefficients - md_FactX = k_Size.Width / d_Width; - md_FactY = -k_Size.Height / d_Height; - - md_OffsX = md_FactX * d_Width / 2.0; - md_OffsY = -md_FactY * d_Height / 2.0; - - // ----------------------------------- - - double d_ResizeX = (double)k_Size.Width / mk_InitialSize.Width; - double d_ResizeY = (double)k_Size.Height / mk_InitialSize.Height; - md_Resize = Math.Min(d_ResizeX, d_ResizeY); - - md_FactX *= md_Resize; - md_FactY *= md_Resize; - - CalcZoom(); - mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - - // Required for correct painting order of polygons (always from back to front) - public double ProjectXY(double X, double Y, double Z = 0.0) - { - return X * md_cf + Y * md_sf + Z * md_ct; - } - - // Used to convert mouse movements back into the 3D space depending on the current rotation angle - public double ReverseProject(double X, double Y, double Z) - { - if (mi_Inst.AxisX.Mirror) X = -X; - if (mi_Inst.AxisY.Mirror) Y = -Y; - if (mi_Inst.AxisZ.Mirror) Z = -Z; - - // If Theta has the correct range from 10 to 170 degree --> Sinus(Theta) will never become zero. - // This can only happen if VALUES_THETA has been manipulated to invalid Min/Max values. - double d_Divide = Math.Max(0.1, md_st); - return (-X * md_sf + Y * md_cf + Z / d_Divide) / md_Zoom; - } - - /// - /// This approximates a zoom factor that depends on Rho and the resize window factor. - /// Used to adapt the size of lines, shapes and selected points. - /// - private void CalcZoom() - { - md_Zoom = md_Resize * (1800.0 / (md_Rho + 300)); - } - - // Performs projection. Calculates 2D screen coordinates from 3D point. - public cPoint2D Project3D(cPoint3D i_Point3D, eMirror e_Mirror) - { - double X = i_Point3D.X; - double Y = i_Point3D.Y; - double Z = i_Point3D.Z; - - // Mirror axis values increasing / decreasing - if (mi_Inst.AxisX.Mirror && (e_Mirror & eMirror.X) > 0) X = mi_Inst.mi_Bounds.X.Max - (X - mi_Inst.mi_Bounds.X.Min); - if (mi_Inst.AxisY.Mirror && (e_Mirror & eMirror.Y) > 0) Y = mi_Inst.mi_Bounds.Y.Max - (Y - mi_Inst.mi_Bounds.Y.Min); - if (mi_Inst.AxisZ.Mirror && (e_Mirror & eMirror.Z) > 0) Z = mi_Inst.mi_Bounds.Z.Max - (Z - mi_Inst.mi_Bounds.Z.Min); - - X = (X - mi_Center3D.X) * md_NormalizeX; - Y = (Y - mi_Center3D.Y) * md_NormalizeY; - Z = (Z - mi_Center3D.Z) * md_NormalizeZ; - - // 3D coordinates with center point in the middle of the screen - // X positive to the right, X negative to the left - // Y positive to the top, Y negative to the bottom - double xn = -md_sf * X + md_cf * Y; - double yn = -md_cf * md_ct * X - md_sf * md_ct * Y + md_st * Z; - double zn = -md_cf * md_st * X - md_sf * md_st * Y - md_ct * Z + md_Rho; - - zn = Math.Max(zn, 0.01); // avoid division by zero - - // Thales' theorem - cPoint2D i_Point2D = new cPoint2D(xn * DISTANCE / zn, yn * DISTANCE / zn); - - i_Point2D.md_X = i_Point2D.md_X * md_FactX + md_OffsX; - i_Point2D.md_Y = i_Point2D.md_Y * md_FactY + md_OffsY; - return i_Point2D; - } - } - - #endregion - - #region cDefault - - /// - /// Stores defauls for Rho, Theta, Phi - /// - private class cDefault - { - public readonly double Min; - public readonly double Max; - public readonly double Default; - public readonly double MouseFactor; - - public cDefault(double d_Min, double d_Max, double d_Default, double d_MouseFactor) - { - Min = d_Min; - Max = d_Max; - Default = d_Default; - MouseFactor = d_MouseFactor; - } - } - - #endregion - - // Limits and default values for mouse actions and trackbars. - // ATTENTION: It is strongly recommended not to change the MIN, MAX values. - // The mouse factor defines how much mouse movement you need for a change. - // A movement of mouse by approx 1000 pixels on the screen results in getting from Min to Max or vice versa. - static readonly cDefault VALUES_RHO = new cDefault(300, 1800, 1350, 2 ); - static readonly cDefault VALUES_THETA = new cDefault( 10, 170, 70, 0.25); // degree - static readonly cDefault VALUES_PHI = new cDefault( 0, 360, 230, 0.4 ); // degree (continuous rotation) - - // The coordinate axes are 10 % longer than the bounds of the X,Y,Z values - const double AXIS_EXCESS = 0.1; - - // For any strange reason the graph is not centered vertically - const int VERT_OFFSET = -30; - - // The maximum distance between mouse pointer and a 2D point to display the tooltip - const int TOOLTIP_RADIUS = 6; - - // The maximum distance between mouse pointer and a 2D point to allow a match when selecting a 3D object. - const int SELECT_RADIUS = 3; - - // Calculate 3-dimensional Z value from X,Y values - public delegate double delRendererFunction(double X, double Y); - - // IMPORTANT: Read the detailed comment of function SelectionCallback() at the end of this class. - public delegate eInvalidate delSelectHandler(eSelEvent e_Event, Keys e_Modifiers, int s32_DeltaX, int s32_DeltaY, cObject3D i_Object); - - Pen[] mi_BorderPens = new Pen[2]; - SolidBrush mi_TopLegendBrush = null; - eRaster me_Raster = eRaster.Labels; - cAxis[] mi_Axis = new cAxis[3]; - cMouse mi_Mouse = new cMouse(); - List mi_MessageData = new List(); - eRecalculate me_Recalculate = eRecalculate.Nothing; - eNormalize me_Normalize = eNormalize.Separate; - eLegendPos me_LegendPos = eLegendPos.BottomLeft; - List mi_AxisLines = new List(); // 0, 3, or 45 axis lines of coordinate system - List mi_UserObjects = new List(); // Draw objects from the user (cLine, cShape, cPolygon) - List mi_AllObjects = new List(); // mi_UserObjects + mi_AxisLines - cQuadrant mi_Quadrant = new cQuadrant(); - Dictionary mi_UserInputs = new Dictionary(); - cUndoImpl mi_UndoBuffer; - cTransform mi_Transform; - cBounds mi_Bounds; - cTooltip mi_Tooltip; - cSelection mi_Selection; - cObject3D mi_DragObject; - - #region Properties - - /// - /// See comment of enum eTooltip. - /// This property can also be set in the Visual Studio Designer - /// - public eTooltip TooltipMode - { - get { return mi_Tooltip.Mode; } - set { mi_Tooltip.Mode = value; } - } - - /// - /// See comment of enum eLegendPos. - /// This property can also be set in the Visual Studio Designer - /// - public eLegendPos LegendPos - { - get { return me_LegendPos; } - set { me_LegendPos = value; } - } - - /// - /// See comment of enum eNormalize. - /// This change will become visible the next time you call Invalidate() - /// - public eNormalize Normalize - { - get { return me_Normalize; } - set - { - if (me_Normalize != value) - { - me_Normalize = value; - me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - } - } - - /// - /// See comment of enum eRaster - /// This property can also be set in the Visual Studio Designer - /// This change will become visible the next time you call Invalidate() - /// - public eRaster Raster - { - set - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - if (me_Raster != value) - { - me_Raster = value; - me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; - } - } - get - { - return me_Raster; - } - } - - /// - /// Sets the border color when the 3D Editor does not have the keyboard focus - /// Setting BorderColor = Color.Empty turns off the border - /// This change will become visible the next time you call Invalidate() - /// This property can also be set in the Visual Studio Designer - /// - public Color BorderColorNormal - { - set - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - if (value.A > 0) mi_BorderPens[0] = new Pen(value, 1); - else mi_BorderPens[0] = null; // transparent color - } - get - { - if (mi_BorderPens[0] != null) return mi_BorderPens[0].Color; - else return Color.Empty; - } - } - - /// - /// Sets the border color when the 3D Editor has the keyboard focus - /// Setting BorderColorFocus = Color.Empty turns off the highlighting on focus. - /// This change will become visible the next time you call Invalidate() - /// This property can also be set in the Visual Studio Designer - /// - public Color BorderColorFocus - { - set - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - if (value.A > 0) mi_BorderPens[1] = new Pen(value, 1); - else mi_BorderPens[1] = mi_BorderPens[0]; - } - get - { - if (mi_BorderPens[1] != null) return mi_BorderPens[1].Color; - else return BorderColorNormal; - } - } - - /// - /// Show a legend with Rotation, Elevation and Distance at the top left - /// Setting LegendColor = Color.Empty turns off the top legend - /// This property can also be set in the Visual Studio Designer - /// This change will become visible the next time you call Invalidate() - /// - public Color TopLegendColor - { - set - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - mi_TopLegendBrush = new SolidBrush(value); - } - get - { - if (mi_TopLegendBrush != null) return mi_TopLegendBrush.Color; - else return Color.Empty; - } - } - - /// - /// returns the total count of loaded draw objects (lines, shapes and polygons) - /// - [Browsable(false)] - public String ObjectStatistics - { - get - { - int s32_Lines = 0; - int s32_Shapes = 0; - int s32_Polygons = 0; - foreach (cDrawObj i_Obj in mi_UserObjects) - { - if (i_Obj is cLine) s32_Lines ++; - if (i_Obj is cShape) s32_Shapes ++; - if (i_Obj is cPolygon) s32_Polygons ++; - } - StringBuilder i_Out = new StringBuilder(); - if (s32_Lines > 0) i_Out.Append(s32_Lines + " Lines, "); - if (s32_Shapes > 0) i_Out.Append(s32_Shapes + " Shapes, "); - if (s32_Polygons > 0) i_Out.Append(s32_Polygons + " Polygons, "); - return i_Out.ToString().TrimEnd(' ', ','); - } - } - - /// - /// See comments for class cAxis - /// The properties of the class cAxis can be expanded in the Visual Studio designer - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public cAxis AxisX - { - get { return mi_Axis[(int)eCoord.X]; } - } - [TypeConverter(typeof(ExpandableObjectConverter))] - public cAxis AxisY - { - get { return mi_Axis[(int)eCoord.Y]; } - } - [TypeConverter(typeof(ExpandableObjectConverter))] - public cAxis AxisZ - { - get { return mi_Axis[(int)eCoord.Z]; } - } - - /// - /// This property controls if and how the user can select draw objects / points - /// The properties of the class cSelection can be expanded in the Visual Studio designer - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public cSelection Selection - { - get { return mi_Selection; } - } - - /// - /// This property contains the public methods for the Undo / Redo buffer - /// The property 'Enabled' can be expanded in the Visual Studio designer - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public cUndoBuffer UndoBuffer - { - get { return mi_UndoBuffer; } - } - - #endregion - - /// - /// b_ResetOffset = true --> reset the offset that the user has created with SHIFT + moving the 3D object - /// This change will become visible the next time you call Invalidate() - /// - public void SetCoefficients(double d_Rho, double d_Theta, double d_Phi, bool b_ResetOffset = true) - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - mi_Mouse.SetRho (d_Rho); - mi_Mouse.SetTheta(d_Theta); - mi_Mouse.SetPhi (d_Phi); - - if (b_ResetOffset) - { - mi_Mouse.mk_OffMove.X = 0; - mi_Mouse.mk_OffMove.Y = 0; - } - - mi_Transform.SetCoefficients(mi_Mouse); - } - - /// - /// Convert mouse movement in 2D space back into the 3D space depending on the current rotation angle and Min/Max values. - /// - public cPoint3D ReverseProject(int s32_MouseX, int s32_MouseY) - { - double d_FactX = mi_Transform.ReverseProject(mi_Bounds.X.Range, 0.0, 0.0); - double d_FactY = mi_Transform.ReverseProject(0.0, mi_Bounds.Y.Range, 0.0); - double d_FactZ = mi_Transform.ReverseProject(0.0, 0.0, mi_Bounds.Z.Range); - - return new cPoint3D(d_FactX * s32_MouseX / 300.0, - d_FactY * s32_MouseX / 300.0, - d_FactZ * s32_MouseY / 300.0); - } - - /// - /// Trackbars are optional for user interaction. - /// If this function is never called thetrackbars are not used. - /// - public void AssignTrackBars(TrackBar i_Rho, TrackBar i_Theta, TrackBar i_Phi) - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - mi_Mouse.AssignTrackbar(eMouseAction.Rho, i_Rho, new EventHandler(OnTrackbarScroll)); - mi_Mouse.AssignTrackbar(eMouseAction.Theta, i_Theta, new EventHandler(OnTrackbarScroll)); - mi_Mouse.AssignTrackbar(eMouseAction.Phi, i_Phi, new EventHandler(OnTrackbarScroll)); - } - - /// - /// Load one of the three pre-defined input control patterns - /// - public void SetUserInputs(eMouseCtrl e_MouseCtrl) - { - List i_Inputs = new List(); - - switch (e_MouseCtrl) - { - case eMouseCtrl.L_Theta_R_Phi: - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.None, eMouseAction.Theta)); - i_Inputs.Add(new cUserInput(MouseButtons.Right, Keys.None, eMouseAction.Phi)); - break; - case eMouseCtrl.L_Theta_L_Phi: - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.None, eMouseAction.ThetaAndPhi)); - break; - case eMouseCtrl.M_Theta_M_Phi: - i_Inputs.Add(new cUserInput(MouseButtons.Middle, Keys.None, eMouseAction.ThetaAndPhi)); - break; - } - - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Control, eMouseAction.Rho)); - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Shift, eMouseAction.Move)); - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt, eMouseAction.SelectObj)); - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt | Keys.Control, eMouseAction.Callback)); - i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt | Keys.Shift, eMouseAction.Callback)); - - SetUserInputs(i_Inputs.ToArray()); - } - - /// - /// Load fully user defined input control patterns. - /// Each user input must define a unique combination of mouse button and modifier key(s). - /// - public void SetUserInputs(cUserInput[] i_Inputs) - { - mi_UserInputs.Clear(); - foreach (cUserInput i_Input in i_Inputs) - { - // throws if same UID has already been added - mi_UserInputs.Add(i_Input.UID, i_Input); - } - } - - // ================================================================================== - - /// - /// Constructor - /// - public Editor3D() - { + { + cShape3D i_Shape3D = new cShape3D(-1, -1, i_Point, e_Shape, s32_Radius, i_Brush, o_Tag); + mi_Shapes3D.Add(i_Shape3D); + return i_Shape3D; + } + + // ============================================================================= + + /// + /// Called from AddRenderData() + /// + public override void AddDrawObjects(Editor3D i_Inst) + { + foreach (cShape3D i_Shape3D in mi_Shapes3D) + { + i_Inst.AddDrawObject(new cShape(i_Shape3D, mi_ColorScheme)); + } + } + } + + #endregion cScatterData + + #region cLineData + + public class cLineData : cRenderData + { + private List mi_Lines3D = new List(); + private cColorScheme mi_ColorScheme; + + public cLine3D[] AllLines + { + get { return mi_Lines3D.ToArray(); } + } + + public cColorScheme ColorScheme + { + get { return mi_ColorScheme; } + } + + /// + /// Constructor If you use only solid lines and specify a valid Pen, you can pass + /// i_ColorScheme == null here + /// + public cLineData(cColorScheme i_ColorScheme) + { + mi_ColorScheme = i_ColorScheme; + } + + /// + /// Add a line which will be drawn entirely in one color. + /// + public cLine3D AddSolidLine(cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, Object o_Tag = null) + { + CheckPenMutable(i_Pen, mi_ColorScheme); + + cLine3D i_Line3D = new cLine3D(i_Start, i_End, s32_Width, i_Pen, 1, o_Tag); + mi_Lines3D.Add(i_Line3D); + return i_Line3D; + } + + /// + /// Add a line which will appear with multiple colors of the ColorScheme by drawing it + /// in multiple parts. If s32_Parts = 50, the line is rendered in 50 parts where each + /// part has it's own color depending on the Z coordinate. + /// + public cLine3D AddMultiColorLine(int s32_Parts, cPoint3D i_Start, cPoint3D i_End, int s32_Width, Pen i_Pen, Object o_Tag = null) + { + if (s32_Parts < 3) + throw new ArgumentException("Multi color lines require at least 3 parts"); + + if (mi_ColorScheme == null) + throw new Exception("To create a multi-color line you must specify a ColorScheme"); + + CheckPenMutable(i_Pen, mi_ColorScheme); + + cLine3D i_Line3D = new cLine3D(i_Start, i_End, s32_Width, i_Pen, s32_Parts, o_Tag); + mi_Lines3D.Add(i_Line3D); + return i_Line3D; + } + + /// + /// Creates connected lines from the points in the given order + /// + public cLine3D[] AddConnectedLines(List i_Points, int s32_Width, Pen i_Pen) + { + CheckPenMutable(i_Pen, mi_ColorScheme); + + List i_NewLines = new List(); + + cPoint3D i_Prev = null; + for (int i = 0; i < i_Points.Count; i++) + { + cPoint3D i_Point = i_Points[i]; + if (i_Prev != null) + { + cLine3D i_Line3D = new cLine3D(i_Prev, i_Point, s32_Width, i_Pen, 1); + i_NewLines.Add(i_Line3D); + mi_Lines3D.Add(i_Line3D); + } + i_Prev = i_Point; + } + return i_NewLines.ToArray(); + } + + // ============================================================================= + + /// + /// Called from AddRenderData() + /// + public override void AddDrawObjects(Editor3D i_Inst) + { + foreach (cLine3D i_Line3D in mi_Lines3D) + { + i_Inst.AddDrawObject(new cLine(i_Line3D, mi_ColorScheme)); + } + } + } + + #endregion cLineData + + #region cPolygonData + + public class cPolygonData : cRenderData + { + private bool mb_Fill; + private Pen mi_Pen; + private cColorScheme mi_ColorScheme; + private List mi_Polygons3D = new List(); + + public cPolygon3D[] AllPolygons + { + get { return mi_Polygons3D.ToArray(); } + } + + public cColorScheme ColorScheme + { + get { return mi_ColorScheme; } + } + + /// + /// Fill Mode: ------------ Polygons are filled with a color from the ColorScheme. If + /// you want only one color, set a ColorScheme which contains only one color. The Pen is + /// used to draw the thin lines between the polygons (mostly black, 1 pixel) If Pen is + /// null, no lines are drawn. + /// + /// Line Mode: ------------ Only the border lines of the polygons are drawn. The Pen is + /// used to draw these lines. The Pen's color and width will be modified. + /// + public cPolygonData(ePolygonMode e_Mode, Pen i_Pen, cColorScheme i_ColorScheme) + { + if (e_Mode == ePolygonMode.Lines) + { + if (i_Pen == null) + throw new ArgumentException("In Line mode you must specify a Line Pen"); + + CheckPenMutable(i_Pen, i_ColorScheme); + } + + mb_Fill = e_Mode == ePolygonMode.Fill; + mi_Pen = i_Pen; + mi_ColorScheme = i_ColorScheme; + } + + /// + /// In contrast to other drawing libraries (like WPF or Direct3D) you can add polygons + /// of any dimension here. A polygon can have any amount of corners (minimum 3). The + /// Brush can be specified in Fill mode. To use a Brush from the ColorScheme set i_Brush + /// = null. + /// + public cPolygon3D AddPolygon(Brush i_Brush, params cPoint3D[] i_Points3D) + { + cPolygon3D i_Polygon3D = new cPolygon3D(-1, -1, i_Brush, i_Points3D); + mi_Polygons3D.Add(i_Polygon3D); + return i_Polygon3D; + } + + // ============================================================================= + + /// + /// Called from AddRenderData() + /// + public override void AddDrawObjects(Editor3D i_Inst) + { + foreach (cPolygon3D i_Polygon3D in mi_Polygons3D) + { + i_Inst.AddDrawObject(new cPolygon(mb_Fill, i_Polygon3D, mi_Pen, mi_ColorScheme)); + } + } + } + + #endregion cPolygonData + + #region cMessgData + + public class cMessgData + { + private String ms_Text; + private Brush mi_Brush; + private int ms32_PosX; + private int ms32_PosY; + private Font mi_Font; + private SizeF mk_Size; + + /// + /// Here you can change the text without loading all the render objects again. The + /// change will become visible the next time you call Invalidate() + /// + public String Text + { + set + { + ms_Text = value; + mk_Size = SizeF.Empty; + } + } + + /// + /// Here you can change the text color without loading all the render objects again. The + /// change will become visible the next time you call Invalidate() + /// + public Color TextColor + { + set { mi_Brush = new SolidBrush(value); } + } + + /// + /// If X is negative, it is displayed right aligned at X pixels from the right If Y is + /// negative, it is displayed bottom aligned at Y pixels from the bottom + /// + public cMessgData(String s_Text, int X, int Y, Color c_Color, + FontStyle e_FontStyle = FontStyle.Bold, + int s32_FontSize = 9, + String s_FontFace = "Tahoma") + { + ms_Text = s_Text; + ms32_PosX = X; + ms32_PosY = Y; + mi_Brush = new SolidBrush(c_Color); + mi_Font = new Font(s_FontFace, s32_FontSize, e_FontStyle); + } + + public void Draw(Graphics i_Graph, Rectangle k_Client) + { + if (String.IsNullOrEmpty(ms_Text)) + return; + + float X = ms32_PosX; + float Y = ms32_PosY; + + if (X < 0 || Y < 0) + { + // Speed optimization: Measure the size only once. + if (mk_Size.IsEmpty) + mk_Size = i_Graph.MeasureString(ms_Text, mi_Font); + + if (X < 0) X += k_Client.Width - mk_Size.Width; + if (Y < 0) Y += k_Client.Height - mk_Size.Height; + } + + i_Graph.DrawString(ms_Text, mi_Font, mi_Brush, X, Y); + } + } + + #endregion cMessgData + + // ================= PRIVATE ================= + + #region cPoint2D + + /// + /// This class represents a point in the 2D space, in pixels. + /// + private class cPoint2D + { + public double md_X; + public double md_Y; + + public cPoint2D() + { + } + + public cPoint2D(double X, double Y) + { + md_X = X; + md_Y = Y; + } + + public cPoint2D Clone() + { + return new cPoint2D(md_X, md_Y); + } + + public PointF Coord + { + get { return new PointF((float)md_X, (float)md_Y); } + } + + public bool IsValid + { + get + { + // The screen will always be smaller than 9999 pixels + return (!Double.IsNaN(md_X) && Math.Abs(md_X) < 9999.9 && + !Double.IsNaN(md_Y) && Math.Abs(md_Y) < 9999.9); + } + } + + /// + /// Use the good old Pythagoras to calculate the pixel distance between this point and + /// X, Y. + /// + public int CalcDistanceTo(int X, int Y) + { + int s32_DiffX = (int)md_X - X; + int s32_DiffY = (int)md_Y - Y; + return (int)Math.Sqrt(s32_DiffX * s32_DiffX + s32_DiffY * s32_DiffY); + } + + // For debugging in Visual Studio + public override string ToString() + { + return String.Format("cPoint2D (X={0}, Y={1})", FormatDouble(md_X), FormatDouble(md_Y)); + } + } + + #endregion cPoint2D + + #region cPoint + + /// + /// This class contains the 3D point and it's projection into the 2D space. + /// + private class cPoint + { + public cPoint3D mi_P3D; + public cPoint2D mi_P2D; + public int ms32_RadiusTip; // if 0 --> no tooltip + + public cPoint(double d_X, double d_Y, double d_Z) + { + mi_P3D = new cPoint3D(d_X, d_Y, d_Z); + mi_P2D = new cPoint2D(); + } + + /// + /// The radius defines at which distance of the mouse from the 2D point the tooltip pops + /// up. Radius = 0 --> no tooltip + /// + public cPoint(cPoint3D i_Point3D, int s32_RadiusTip) + { + mi_P3D = i_Point3D; + mi_P2D = new cPoint2D(); + + if (s32_RadiusTip > 0) + ms32_RadiusTip = s32_RadiusTip + TOOLTIP_RADIUS; + } + + /// + /// Projects the 3D coordinates into the 2D space (pixels on the screen). + /// + public void Project3D(Editor3D i_Inst, eMirror e_Mirror) + { + mi_P2D = i_Inst.mi_Transform.Project3D(mi_P3D, e_Mirror); + } + + /// + /// For Debugging in Visual Studio + /// + public override string ToString() + { + return String.Format("cPoint [{0} = {1}]", mi_P3D, mi_P2D); + } + } + + #endregion cPoint + + #region cTooltip + + private class cTooltip + { + private Editor3D mi_Inst = null; + private eTooltip me_Mode = eTooltip.All; + private ToolTip mi_Tooltip = new ToolTip(); + private List mi_Points = new List(); + private cPoint mi_Last = null; + + public eTooltip Mode + { + get { return me_Mode; } + set { me_Mode = value; } + } + + // Constructor + public cTooltip(Editor3D i_Inst) + { + mi_Inst = i_Inst; + + mi_Tooltip.AutoPopDelay = 30000; // The maximum that Windows allows are 32.767 seconds (0x7FFF milliseconds) + mi_Tooltip.InitialDelay = 50; + mi_Tooltip.ReshowDelay = 50; + } + + public void Clear() + { + mi_Points.Clear(); + mi_Last = null; + Hide(); + } + + public void AddPoint(cPoint i_Point) + { + // ATTENTION adding a && !mi_Points.Contains(i_Point) here would make this function + // 40 times slower! The more points are already in mi_Points, the slower it would + // become. Several points will be added multiple times here, but this does not + // affect the functioning of the tooltip. + if (i_Point != null && i_Point.ms32_RadiusTip > 0) + mi_Points.Add(i_Point); + } + + public void Hide() + { + mi_Tooltip.Hide(mi_Inst); + } + + public void OnMouseMove(MouseEventArgs e) + { + if (me_Mode == eTooltip.Off) + { + Hide(); + return; + } + + int s32_MouseX = e.X - mi_Inst.mi_Mouse.mk_OffMove.X - mi_Inst.mi_Mouse.mk_OffCoord.X; + int s32_MouseY = e.Y - mi_Inst.mi_Mouse.mk_OffMove.Y - mi_Inst.mi_Mouse.mk_OffCoord.Y; + + int s32_MinDist = int.MaxValue; + cPoint i_Nearest = null; + foreach (cPoint i_Point in mi_Points) + { + int s32_Dist = i_Point.mi_P2D.CalcDistanceTo(s32_MouseX, s32_MouseY); + if (s32_Dist < s32_MinDist) + { + s32_MinDist = s32_Dist; + i_Nearest = i_Point; + } + } + + if (i_Nearest != null && s32_MinDist < i_Nearest.ms32_RadiusTip) + { + if (mi_Last == i_Nearest) + return; // The mouse is still over the same point + + mi_Last = i_Nearest; + + String s_TT = ""; + if ((me_Mode & eTooltip.Coord) > 0) + s_TT = String.Format("X = {0}\nY = {1}\nZ = {2}\n", FormatDouble(i_Nearest.mi_P3D.X), + FormatDouble(i_Nearest.mi_P3D.Y), + FormatDouble(i_Nearest.mi_P3D.Z)); + + if ((me_Mode & eTooltip.UserText) > 0 && i_Nearest.mi_P3D.Tooltip != null) + s_TT += i_Nearest.mi_P3D.Tooltip; + + s_TT = s_TT.Trim(); + if (s_TT.Length > 0) + { + mi_Tooltip.Show(s_TT, mi_Inst, e.X + 10, e.Y + 10); + return; + } + } + + mi_Last = null; + Hide(); + } + } + + #endregion cTooltip + + #region cBackup + + private class cBackup + { + public Dictionary mi_Changes = new Dictionary(); + + public void StoreProperty(eUndoProp e_Property, Object o_OldValue, Object o_NewValue) + { + Object[] o_Values; + if (mi_Changes.TryGetValue(e_Property, out o_Values)) + { + // The property has changed multiple times (dragging) --> store only the last value + o_Values[1] = o_NewValue; + } + else + { + // The property has changed for the first time + mi_Changes.Add(e_Property, new Object[] { o_OldValue, o_NewValue }); + } + } + + public void Restore(cObject3D i_Object3D, bool b_Undo) + { + foreach (KeyValuePair i_Pair in mi_Changes) + { + if (b_Undo) i_Object3D.Restore(i_Pair.Key, i_Pair.Value[0]); // Undo: old value + else i_Object3D.Restore(i_Pair.Key, i_Pair.Value[1]); // Redo: new value + } + } + + // for debugging in Visual Studio + public override string ToString() + { + StringBuilder i_Dbg = new StringBuilder(); + foreach (eUndoProp e_Property in mi_Changes.Keys) + { + if (i_Dbg.Length > 0) i_Dbg.Append(", "); + i_Dbg.Append(e_Property); + } + return "cBackup Properties: " + i_Dbg.ToString(); + } + } + + #endregion cBackup + + #region cUndoEntry + + private class cUndoEntry + { + public Dictionary mi_Backups = new Dictionary(); + public List mi_Added = new List(); + public List mi_Removed = new List(); + + public bool IsEmpty + { + get { return mi_Backups.Count == 0 && mi_Added.Count == 0 && mi_Removed.Count == 0; } + } + + // for debugging in Visual Studio + public override string ToString() + { + return String.Format("Backups: {0}, Added: {1}, Removed: {2}", (mi_Backups == null) ? 0 : mi_Backups.Count, + (mi_Added == null) ? 0 : mi_Added.Count, + (mi_Removed == null) ? 0 : mi_Removed.Count); + } + } + + #endregion cUndoEntry + + #region cUndoImpl + + /// + /// This private class stores the changes after each edit operation that the user has made. + /// + private class cUndoImpl : cUndoBuffer + { + private Editor3D mi_Inst; + private int ms32_UndoIdx = 0; // Points to the next Undo item in mi_UndoList + private int ms32_RedoIdx = 0; // Points to the next Redo item in mi_UndoList + private bool mb_Enabled = false; // The Undo Buffer is by default disabled + private bool mb_DoInit = false; // After Clear() execute Init() once. + private bool mb_Pending = false; // User actions are pending that have not yet been stored + private cUndoEntry mi_CurUndo = new cUndoEntry(); + private List mi_UndoList = new List(); + + /// + /// Do not enable the Undo buffer if you don't need the Undo functionality. By default + /// it is disabled. + /// + public override bool Enabled + { + get { return mb_Enabled; } + set + { + Clear(); + mb_Enabled = value; + } + } + + /// + /// Constructor + /// + public cUndoImpl(Editor3D i_Inst) + { + mi_Inst = i_Inst; + } + + public override void Clear() + { + ms32_UndoIdx = 0; + ms32_RedoIdx = 0; + mb_DoInit = true; + mb_Pending = false; + mi_CurUndo = new cUndoEntry(); + mi_UndoList.Clear(); + } + + /// + /// This must only be called from Editor3D.Render() ! The first time calling + /// Invalidate() after Clear() --> clear all draw objets that have been added. + /// Otherwise all added objects would be stored. (This class stores only user changes.) + /// + public void Init() + { + if (mb_DoInit) + { + Clear(); + mb_DoInit = false; + } + } + + /// + /// Called when any public property of a 3D object was changed (e.g. X,Y,Z coordinates + /// of a cPoint3D). These changes will be stored when Store() is called. + /// + public void Backup(cObject3D i_Object3D, eUndoProp e_Property, Object o_OldValue, Object o_NewValue) + { + if (!mb_Enabled) + return; + + cBackup i_Backup; + if (!mi_CurUndo.mi_Backups.TryGetValue(i_Object3D, out i_Backup)) + { + i_Backup = new cBackup(); + mi_CurUndo.mi_Backups.Add(i_Object3D, i_Backup); + } + + i_Backup.StoreProperty(e_Property, o_OldValue, o_NewValue); + mb_Pending = true; + } + + /// + /// Called when the user removes a draw object. These changes will be stored when + /// Store() is called. + /// + public void DrawObjectRemoved(cDrawObj i_DrawObject) + { + if (!mb_Enabled) + return; + + mi_CurUndo.mi_Removed.Add(i_DrawObject); + mb_Pending = true; + } + + /// + /// Called when the user adds a draw object. These changes will be stored when Store() + /// is called. + /// + public void DrawObjectAdded(cDrawObj i_DrawObject) + { + if (!mb_Enabled) + return; + + mi_CurUndo.mi_Added.Add(i_DrawObject); + mb_Pending = true; + } + + /// + /// Store the changes on user objects in the Undo buffer on mouse-up. + /// + public void Store() + { + if (!mb_Enabled || !mb_Pending || mi_CurUndo.IsEmpty) + return; + + // Remove any entries in the Undo Buffer behind the current index + int s32_Remove = mi_UndoList.Count - ms32_UndoIdx - 1; + if (s32_Remove > 0) + mi_UndoList.RemoveRange(ms32_UndoIdx + 1, s32_Remove); + + mi_UndoList.Add(mi_CurUndo); + mi_CurUndo = new cUndoEntry(); + + ms32_UndoIdx = mi_UndoList.Count - 1; + ms32_RedoIdx = mi_UndoList.Count - 1; + mb_Pending = false; + } + + /// + /// This is also called when hitting CTRL + Z while the 3D Editor has the keyboard focus + /// + public override bool Undo() + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + + if (!mb_Enabled || mi_UndoList.Count == 0 || ms32_UndoIdx < 0) + return false; + + Restore(true, ms32_UndoIdx); + + ms32_RedoIdx = ms32_UndoIdx; + ms32_UndoIdx--; + return true; + } + + /// + /// This is also called when hitting CTRL + Y while the 3D Editor has the keyboard focus + /// + public override bool Redo() + { + Debug.Assert(!mi_Inst.InvokeRequired); // Call only from GUI thread + + if (!mb_Enabled || mi_UndoList.Count == 0 || ms32_RedoIdx >= mi_UndoList.Count) + return false; + + Restore(false, ms32_RedoIdx); + + ms32_UndoIdx = ms32_RedoIdx; + ms32_RedoIdx++; + return true; + } + + private void Restore(bool b_Undo, int s32_Index) + { + s32_Index = Math.Max(s32_Index, 0); + s32_Index = Math.Min(s32_Index, mi_UndoList.Count - 1); + + cUndoEntry i_UndoItem = mi_UndoList[s32_Index]; + + foreach (cDrawObj i_Removed in i_UndoItem.mi_Removed) + { + if (b_Undo) mi_Inst.mi_UserObjects.Add(i_Removed); + else mi_Inst.mi_UserObjects.Remove(i_Removed); + + mi_Inst.me_Recalculate |= eRecalculate.AddRemove | eRecalculate.Objects | eRecalculate.CoordSystem; + } + + foreach (cDrawObj i_Added in i_UndoItem.mi_Added) + { + if (b_Undo) mi_Inst.mi_UserObjects.Remove(i_Added); + else mi_Inst.mi_UserObjects.Add(i_Added); + + mi_Inst.me_Recalculate |= eRecalculate.AddRemove | eRecalculate.Objects | eRecalculate.CoordSystem; + } + + foreach (KeyValuePair i_Pair in i_UndoItem.mi_Backups) + { + i_Pair.Value.Restore(i_Pair.Key, b_Undo); + + mi_Inst.me_Recalculate |= eRecalculate.Objects | eRecalculate.CoordSystem; + } + + mi_Inst.Invalidate(); + } + } + + #endregion cUndoImpl + + // ----- DrawObjects ----- + + #region cDrawObj + + /// + /// Base class for cLine, cShape, cPolygon + /// + private abstract class cDrawObj : IComparable + { + public Editor3D mi_Inst; + public SmoothingMode me_SmoothMode; + public cPoint[] mi_Points; + public cObject3D mi_Object3D; // This is a cLine3D, cShape3D or cPolygon3D + public double md_Sort; // sorting is important. Always draw first back, then front objects. + public bool mb_IsAxis; // This is a line from the coordinate system + protected double md_AvrgZ; // 3D center of the Z coordinates of all points in this object + protected bool mb_IsValid; + protected eMirror me_Mirror; + + // -------------------------------------------------- + + /// + /// Set after conversion 3D --> 2D. If invalid coordinates are found (NaN or > + /// 9999) this returns false. If projection results in lines of thousands of pixels + /// length, the drawing will become extremely slow. Do not draw lines or polygons + /// outside the screen area. + /// + public bool IsValid + { + get { return mb_IsValid; } + } + + /// + /// The object is selected + /// + public bool Selected + { + get { return mi_Object3D.Selected; } + set { mi_Object3D.Selected = value; } + } + + /// + /// The type of this object + /// + public virtual eSelType SelType + { + get { throw new NotImplementedException(); } + } + + /// + /// Calculates colors from color scheme + /// + public virtual void ProcessColors() + { + throw new NotImplementedException(); + } + + /// + /// Calculates the 2D screen coordinates for each 3D point. + /// + public virtual void Project3D() + { + throw new NotImplementedException(); + } + + /// + /// Draw into Graphics + /// + public virtual void Render(Graphics i_Graph) + { + throw new NotImplementedException(); + } + + /// + /// Check if a user click at mouse point X, Y matches this draw object + /// + public virtual cObject3D MatchesPoint2D(int X, int Y) + { + throw new NotImplementedException(); + } + + /// + /// Uses the center of a draw object (e.g. the middle of a line) to calculate in which + /// order the draw objects are rendered. This is called for all user defined objects, + /// but not for the coordinate system which has it's own logic. + /// + public void CalcSortOrder() + { + Debug.Assert(!mb_IsAxis); // axes have their own value for md_Sort + + double d_AvrgX = 0.0; + double d_AvrgY = 0.0; + md_AvrgZ = 0.0; + + foreach (cPoint i_Point in mi_Points) + { + d_AvrgX += i_Point.mi_P3D.X; + d_AvrgY += i_Point.mi_P3D.Y; + md_AvrgZ += i_Point.mi_P3D.Z; + } + + d_AvrgX /= mi_Points.Length; + d_AvrgY /= mi_Points.Length; + md_AvrgZ /= mi_Points.Length; + + if (mi_Object3D.Row > -1) // only Polygons and Scatter circles created by cSurfaceData + { + int X = mi_Object3D.Column + 1; + int Y = mi_Object3D.Row + 1; + + // Mirror axis values increasing / decreasing + if (mi_Inst.AxisX.Mirror && (me_Mirror & eMirror.X) > 0) X = 5000 - mi_Object3D.Column; + if (mi_Inst.AxisY.Mirror && (me_Mirror & eMirror.Y) > 0) Y = 5000 - mi_Object3D.Row; + + // In case of a surface grid the Z value must be ignored because sorting is + // ALWAYS based on the position in the grid. Using the Z value here may even + // result in wrong sort order. + md_Sort = mi_Inst.mi_Transform.ProjectXY(X, Y); + } + else + { + double X = d_AvrgX; + double Y = d_AvrgY; + double Z = md_AvrgZ; + + // Mirror axis values increasing / decreasing + if (mi_Inst.AxisX.Mirror && (me_Mirror & eMirror.X) > 0) X = mi_Inst.mi_Bounds.X.Max - (X - mi_Inst.mi_Bounds.X.Min); + if (mi_Inst.AxisY.Mirror && (me_Mirror & eMirror.Y) > 0) Y = mi_Inst.mi_Bounds.Y.Max - (Y - mi_Inst.mi_Bounds.Y.Min); + if (mi_Inst.AxisZ.Mirror && (me_Mirror & eMirror.Z) > 0) Z = mi_Inst.mi_Bounds.Z.Max - (Z - mi_Inst.mi_Bounds.Z.Min); + + // In case of any other 3D object the Z value must also be included in the + // calculation to avoid artifacts. Demo Sphere shows that the Z value is + // required if you move Theta to an extreme. + X = (X - mi_Inst.mi_Transform.mi_Center3D.X) * mi_Inst.mi_Transform.md_NormalizeX; + Y = (Y - mi_Inst.mi_Transform.mi_Center3D.Y) * mi_Inst.mi_Transform.md_NormalizeY; + Z = (Z - mi_Inst.mi_Transform.mi_Center3D.Z) * mi_Inst.mi_Transform.md_NormalizeZ; + + md_Sort = mi_Inst.mi_Transform.ProjectXY(X, Y, Z); + } + } + + /// + /// Used for sorting all DrawObjects from back to front + /// + int IComparable.CompareTo(Object o_Comp) + { + return md_Sort.CompareTo(((cDrawObj)o_Comp).md_Sort); + } + } + + #endregion cDrawObj + + #region cLine + + private class cLine : cDrawObj + { + private cLine3D mi_Line3D; // object passed to and from the user + private Pen mi_Pen; + private Brush mi_Brush; // assigned to Pen + private float mf_LineWidth; // Linewidth with zoom factor + private float mf_SelSize; // size of selection points + + // -------- coordinate axes -------- + public double md_Angle; // needed to calculate current rotation quadrant of coordinate axis + + public eCoord me_Line = eCoord.Invalid; // main coordinate in coordinate direction + public eCoord me_Offset = eCoord.Invalid; // secondary coordinate in coordinate direction + public String ms_Label; // Label for axis + + // ---------- multicolor ----------- + private cPoint[] mi_ColorPoints; // all points on the line that are drawn separately + + private Brush[] mi_ColorBrushes; // all Brushes which are assigned to the Pen + private cColorScheme mi_ColorScheme; + + public override eSelType SelType + { + get { return eSelType.Line; } + } + + /// + /// Constructor 1 for coordinate system. LineWidth is always 1. + /// + public cLine(Editor3D i_Inst, eCoord e_Line, eCoord e_Offset, eMirror e_Mirror) + { + if (e_Line == e_Offset) mi_Pen = i_Inst.mi_Axis[(int)e_Line].AxisPen; // Main axis + else mi_Pen = i_Inst.mi_Axis[(int)e_Offset].RasterPen; // Raster line + + mi_Inst = i_Inst; + me_Line = e_Line; + me_Offset = e_Offset; + mi_Brush = mi_Pen.Brush; + mi_Points = new cPoint[2]; + mi_Points[0] = new cPoint(0, 0, 0); + mi_Points[1] = new cPoint(0, 0, 0); + mb_IsAxis = true; + me_Mirror = e_Mirror; + me_SmoothMode = SmoothingMode.AntiAlias; + } + + /// + /// Constructor 2 for user lines if i_Line3D.Pen == null --> Pen from ColorScheme is + /// used if i_Line3D.Pen != null --> ColorScheme will be ignored, even if a + /// ColorScheme is specified An indvidual LineWidth can be defined in each cLine3D. + /// + public cLine(cLine3D i_Line3D, cColorScheme i_ColorScheme) + { + if (i_Line3D.Pen == null && i_ColorScheme == null) + throw new ArgumentException("You must specify a Pen or a ColorScheme"); + + mi_ColorScheme = i_ColorScheme; + mi_Line3D = i_Line3D; + mi_Object3D = i_Line3D; + mi_Pen = i_Line3D.Pen; // get user's Pen or null + mi_Points = new cPoint[2]; + mi_Points[0] = new cPoint(i_Line3D.Points[0], 1); + mi_Points[1] = new cPoint(i_Line3D.Points[1], 1); + mb_IsAxis = false; + me_Mirror = eMirror.All; + me_SmoothMode = SmoothingMode.AntiAlias; + + if (mi_Pen != null) + { + // The original Brush must be stored separately because the same Pen may be used + // for multiple instances of cLine Changing the color of one line would affect + // all the others. + mi_Brush = mi_Pen.Brush; + } + else + { + mi_Pen = new Pen(Brushes.Black); // color and width will be changed below + mi_Brush = null; // Brush will be taken from colorscheme + } + mi_Pen.StartCap = LineCap.Round; + mi_Pen.EndCap = LineCap.Round; + + // ---------- multi color --------- + + if (i_Line3D.ColorParts > 1) + { + mi_ColorBrushes = new Brush[i_Line3D.ColorParts]; + mi_ColorPoints = new cPoint[i_Line3D.ColorParts]; + } + } + + public override void ProcessColors() + { + Debug.Assert(!mb_IsAxis); // axes have their own color + + // If the user has changed the Pen --> use the new Pen and it's Brush + if (mi_Line3D.Pen != null) + { + mi_Pen = mi_Line3D.Pen; + mi_Brush = mi_Pen.Brush; + + mi_Pen.StartCap = LineCap.Round; + mi_Pen.EndCap = LineCap.Round; + } + + if (mi_ColorPoints != null) // multicolor line + { + double d_X = mi_Points[0].mi_P3D.X; + double d_Y = mi_Points[0].mi_P3D.Y; + double d_Z = mi_Points[0].mi_P3D.Z; + + double d_DeltaX = (mi_Points[1].mi_P3D.X - d_X) / mi_ColorPoints.Length; + double d_DeltaY = (mi_Points[1].mi_P3D.Y - d_Y) / mi_ColorPoints.Length; + double d_DeltaZ = (mi_Points[1].mi_P3D.Z - d_Z) / mi_ColorPoints.Length; + + mi_ColorPoints[0] = mi_Points[0]; // Set Start Point + + cPoint i_Prev = mi_Points[0]; + for (int i = 1; i < mi_ColorPoints.Length; i++) + { + d_X += d_DeltaX; + d_Y += d_DeltaY; + d_Z += d_DeltaZ; + + cPoint i_Point = new cPoint(new cPoint3D(d_X, d_Y, d_Z), 0); + + double d_AvrgZ = (i_Prev.mi_P3D.Z + i_Point.mi_P3D.Z) / 2.0; + double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(d_AvrgZ); + int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); + mi_ColorBrushes[i] = mi_ColorScheme.GetBrush(s32_Index); + mi_ColorPoints[i] = i_Point; + + i_Prev = i_Point; + } + + mi_ColorPoints[mi_ColorPoints.Length - 1] = mi_Points[1]; // Set End Point + } + else // solid line + { + if (mi_ColorScheme != null) + { + // Load missing Pen from ColorScheme which the user has not specified. + double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(md_AvrgZ); + int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); + mi_Brush = mi_ColorScheme.GetBrush(s32_Index); + } + } + } + + public override void Project3D() + { + cPoint[] i_PointArr = (mi_ColorPoints != null) ? mi_ColorPoints : mi_Points; + foreach (cPoint i_Point in i_PointArr) + { + i_Point.Project3D(mi_Inst, me_Mirror); + } + + mb_IsValid = mi_Points[0].mi_P2D.IsValid && mi_Points[1].mi_P2D.IsValid; + + if (!mb_IsAxis) + { + mf_LineWidth = (float)(mi_Line3D.Width * mi_Inst.mi_Transform.md_Zoom); + // Diameter of circle for selected points + mf_SelSize = (float)(Math.Max(6, mi_Line3D.Width * 2) * mi_Inst.mi_Transform.md_Zoom); + } + } + + public override void Render(Graphics i_Graph) + { + // b_LineSel depends on the selection of the line only. It does not matter if an end + // point is selected or not. + bool b_LineSel = !mb_IsAxis && Selected && mi_Inst.mi_Selection.HighlightPen != null; + + if (mi_ColorPoints == null || b_LineSel) // draw solid line + { + Pen i_DrawPen; + if (b_LineSel) + { + i_DrawPen = mi_Inst.mi_Selection.HighlightPen; + } + else + { + i_DrawPen = mi_Pen; + i_DrawPen.Brush = mi_Brush; // mi_Brush = null for multicolor lines! + } + + // Axis lines have always 1 pixel width + if (!mb_IsAxis) + i_DrawPen.Width = mf_LineWidth; + + i_Graph.DrawLine(i_DrawPen, mi_Points[0].mi_P2D.Coord, mi_Points[1].mi_P2D.Coord); + } + else // multi color + { + mi_Pen.Width = mf_LineWidth; + + cPoint i_Prev = mi_ColorPoints[0]; + for (int i = 1; i < mi_ColorPoints.Length; i++) + { + cPoint i_Point = mi_ColorPoints[i]; + mi_Pen.Brush = mi_ColorBrushes[i]; + i_Graph.DrawLine(mi_Pen, i_Prev.mi_P2D.Coord, i_Point.mi_P2D.Coord); + + i_Prev = i_Point; + } + } + + // Draw circle for selected points + foreach (cPoint i_Point in mi_Points) + { + if (!i_Point.mi_P3D.Selected) + continue; + + float X = (float)i_Point.mi_P2D.md_X - mf_SelSize / 2.0f; + float Y = (float)i_Point.mi_P2D.md_Y - mf_SelSize / 2.0f; + i_Graph.FillEllipse(mi_Inst.mi_Selection.HighlightBrush, X, Y, mf_SelSize, mf_SelSize); + } + } + + /// + /// Check if a user click at X, Y matches this draw object + /// + public override cObject3D MatchesPoint2D(int X, int Y) + { + if (mb_IsAxis) + return null; // do not allow to select axis lines + + int s32_MaxDist = Math.Max(1, (int)mi_Pen.Width / 2) + SELECT_RADIUS; + + if (mi_Inst.Selection.SinglePoints) + { + foreach (cPoint i_Point in mi_Points) + { + if (i_Point.mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) + return i_Point.mi_P3D; + } + } + else // select entire line + { + if (IsPointOnLine(mi_Points[0].mi_P2D, mi_Points[1].mi_P2D, X, Y, s32_MaxDist)) + return mi_Line3D; + } + + return null; + } + + // ---------------- Coord System --------------- + + /// + /// Used while creating coordinate system Check if 2 lines have the same coordinates. + /// + public bool CoordEquals(cLine i_Line) + { + return mi_Points[0].mi_P3D.CoordEquals(i_Line.mi_Points[0].mi_P3D) && + mi_Points[1].mi_P3D.CoordEquals(i_Line.mi_Points[1].mi_P3D); + } + + /// + /// Used while creating coordinate system Calculate the angle of the 3 main axes on the + /// screen in a range from 0 to 360 degree. + /// + public void CalcAngle2D() + { + double d_DX = mi_Points[1].mi_P2D.md_X - mi_Points[0].mi_P2D.md_X; + double d_DY = mi_Points[1].mi_P2D.md_Y - mi_Points[0].mi_P2D.md_Y; + md_Angle = Math.Atan2(d_DY, d_DX) * 180.0 / Math.PI; + if (md_Angle < 0.0) md_Angle += 360.0; + } + + // For debugging in Visual Studio + public override string ToString() + { + String s_Dbg = String.Format("cLine from {0} to {1}", mi_Points[0], mi_Points[1]); + if (mb_IsAxis) + s_Dbg += String.Format(" (Axis {0}, {1})", me_Line, me_Offset); + + return s_Dbg; + } + } + + #endregion cLine + + #region cShape + + private class cShape : cDrawObj + { + private float mf_Radius; // radius of shape adapted with Zoom factor + private float mf_Diameter; // diameter of shape adapted with Zoom factor + private PointF mk_TopLeft; // top left corner in screen coordinates for all types of shapes + private PointF[] mk_Polygon; // used for triangles or any future user objects + private Brush mi_Brush; + private cShape3D mi_Shape3D; + private cColorScheme mi_ColorScheme; + + public override eSelType SelType + { + get { return eSelType.Shape; } + } + + /// + /// Constructor + /// + public cShape(cShape3D i_Shape3D, cColorScheme i_ColorScheme) + { + if (i_Shape3D.Brush == null && i_ColorScheme == null) + throw new ArgumentException("You must specify a Brush or a ColorScheme"); + + mi_Shape3D = i_Shape3D; + mi_Object3D = i_Shape3D; + mi_Points = new cPoint[1]; + mi_Points[0] = new cPoint(i_Shape3D.Points[0], i_Shape3D.Radius); + mi_ColorScheme = i_ColorScheme; + me_SmoothMode = SmoothingMode.AntiAlias; + me_Mirror = eMirror.All; + } + + public override void ProcessColors() + { + // If the user has specified an individual brush for this Shape --> always use it + mi_Brush = mi_Shape3D.Brush; + + // Otherwise use Brush from ColorScheme + if (mi_Brush == null) + { + double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(md_AvrgZ); + int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); + mi_Brush = mi_ColorScheme.GetBrush(s32_Index); + } + } + + public override void Project3D() + { + mi_Points[0].Project3D(mi_Inst, me_Mirror); + + mb_IsValid = mi_Points[0].mi_P2D.IsValid; + mf_Radius = (float)(mi_Shape3D.Radius * mi_Inst.mi_Transform.md_Zoom); + mf_Diameter = mf_Radius * 2.0f; + + // Move coordinate from center to upper left corner of circle + mk_TopLeft = mi_Points[0].mi_P2D.Coord; + mk_TopLeft.X -= mf_Radius; + mk_TopLeft.Y -= mf_Radius; + + switch (mi_Shape3D.Shape) + { + case eScatterShape.Triangle: + mk_Polygon = new PointF[3]; + // top center + mk_Polygon[0].X = mk_TopLeft.X + mf_Radius; + mk_Polygon[0].Y = mk_TopLeft.Y; + // bottom left + mk_Polygon[1].X = mk_TopLeft.X; + mk_Polygon[1].Y = mk_TopLeft.Y + mf_Diameter; + // bottom right + mk_Polygon[2].X = mk_TopLeft.X + mf_Diameter; + mk_Polygon[2].Y = mk_TopLeft.Y + mf_Diameter; + break; + + // case eScatterShape.Star: Here you can implement your own shapes break; + } + } + + public override void Render(Graphics i_Graph) + { + bool b_DrawSel = Selected && mi_Inst.mi_Selection.HighlightBrush != null; + Brush i_DrawBrush = b_DrawSel ? mi_Inst.mi_Selection.HighlightBrush : mi_Brush; + + switch (mi_Shape3D.Shape) + { + case eScatterShape.Circle: + i_Graph.FillEllipse(i_DrawBrush, mk_TopLeft.X, mk_TopLeft.Y, mf_Diameter, mf_Diameter); + break; + + case eScatterShape.Square: + i_Graph.FillRectangle(i_DrawBrush, mk_TopLeft.X, mk_TopLeft.Y, mf_Diameter, mf_Diameter); + break; + + default: + i_Graph.FillPolygon(i_DrawBrush, mk_Polygon); + break; + } + } + + /// + /// Check if a user click at X, Y matches this draw object + /// + public override cObject3D MatchesPoint2D(int X, int Y) + { + int s32_MaxDist = (int)mf_Radius + SELECT_RADIUS; + + if (mi_Points[0].mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) + return mi_Shape3D; + + return null; + } + + /// + /// For Debugging in Visual Studio + /// + public override string ToString() + { + return String.Format("cShape {0} at {1}, Diameter {2}", mi_Shape3D.Shape, mi_Points[0], FormatDouble(mf_Diameter)); + } + } + + #endregion cShape + + #region cPolygon + + private class cPolygon : cDrawObj + { + private bool mb_Fill; // Fill / Line mode + private float mf_SelSize; // size of selection points + private PointF[] mk_Screen; // the 2D polygon corner points in screen coordinates + private int ms32_OrgWidth; // original line width for Line Pen + private float mf_LineWidth; // zoomed line width for Line Pen + private Pen mi_LinePen; // used in Line mode + private Pen mi_BorderPen; // used in Fill mode (not zoomed) + private Brush mi_Brush; // used in Fill mode + private cPolygon3D mi_Polygon3D; + private cColorScheme mi_ColorScheme; + + public override eSelType SelType + { + get { return eSelType.Polygon; } + } + + /// + /// Constructor + /// + public cPolygon(bool b_Fill, cPolygon3D i_Polygon3D, Pen i_Pen, cColorScheme i_ColorScheme) + { + mi_Points = new cPoint[i_Polygon3D.Points.Length]; + for (int i = 0; i < mi_Points.Length; i++) + { + mi_Points[i] = new cPoint(i_Polygon3D.Points[i], 1); + } + + mb_Fill = b_Fill; + mi_Polygon3D = i_Polygon3D; + mi_Object3D = i_Polygon3D; + mi_ColorScheme = i_ColorScheme; + mk_Screen = new PointF[mi_Points.Length]; + me_Mirror = eMirror.All; + + if (b_Fill) mi_BorderPen = i_Pen; + else mi_LinePen = i_Pen; + + if (i_Pen != null) + ms32_OrgWidth = (int)i_Pen.Width; + + // Drawing polygon border lines with antialias makes them very thick and black. Do + // not smooth the lines when the polygons are filled with color and the lines are + // only separators. But use smooth mode if only the lines are drawn. + me_SmoothMode = (b_Fill) ? SmoothingMode.None : SmoothingMode.AntiAlias; + } + + public override void ProcessColors() + { + // If the user has specified an individual brush for this Polygon --> always use it + if (mi_Polygon3D.Brush != null) + { + mi_Brush = mi_Polygon3D.Brush; + } + else if (mi_ColorScheme != null) // ColorScheme is never null in Fill mode + { + double d_FactorZ = mi_Inst.mi_Bounds.CalcFactorZ(md_AvrgZ); + int s32_Index = mi_ColorScheme.CalcIndex(d_FactorZ); + mi_Brush = mi_ColorScheme.GetBrush(s32_Index); // used for Fill and assigned to LinePen + } + } + + public override void Project3D() + { + foreach (cPoint i_Point in mi_Points) + { + i_Point.Project3D(mi_Inst, me_Mirror); + } + + // Line width for Line mode + mf_LineWidth = (float)(ms32_OrgWidth * mi_Inst.mi_Transform.md_Zoom); + + // Diameter of circle for selected points + mf_SelSize = (float)(Math.Max(6, ms32_OrgWidth * 2) * mi_Inst.mi_Transform.md_Zoom); + + mb_IsValid = true; + for (int i = 0; i < mi_Points.Length; i++) + { + if (mi_Points[i].mi_P2D.IsValid) + mk_Screen[i] = mi_Points[i].mi_P2D.Coord; + else + mb_IsValid = false; + } + } + + public override void Render(Graphics i_Graph) + { + // Fill polygon with solid color + if (mb_Fill) + { + Brush i_FillBrush = mi_Brush; + if (Selected && mi_Inst.mi_Selection.HighlightBrush != null) + i_FillBrush = mi_Inst.mi_Selection.HighlightBrush; + + i_Graph.FillPolygon(i_FillBrush, mk_Screen); + } + + Pen i_DrawPen; + if (mb_Fill) + { + i_DrawPen = mi_BorderPen; + } + else // Line mode + { + i_DrawPen = mi_LinePen; + i_DrawPen.Width = mf_LineWidth; + + if (mi_Brush != null) + i_DrawPen.Brush = mi_Brush; + } + + // Fill mode --> draw thin black border lines around the polygons Line mode --> draw + // thicker lines around transparent polygons + if (i_DrawPen != null) + { + // ATTENTION: Graphics.DrawPolygon() with a Pen > 1 pixel is buggy in the .NET + // framework (artifacts)! The lines must be drawn one by one manually here. + int T = mk_Screen.Length - 1; + for (int F = 0; F < mk_Screen.Length; F++) + { + i_Graph.DrawLine(i_DrawPen, mk_Screen[F], mk_Screen[T]); + T = F; + } + } + + // Draw selected points + foreach (cPoint i_Point in mi_Points) + { + if (!i_Point.mi_P3D.Selected) + continue; + + float X = (float)i_Point.mi_P2D.md_X - mf_SelSize / 2.0f; + float Y = (float)i_Point.mi_P2D.md_Y - mf_SelSize / 2.0f; + i_Graph.FillEllipse(mi_Inst.mi_Selection.HighlightBrush, X, Y, mf_SelSize, mf_SelSize); + } + } + + /// + /// Check if a user click at X, Y matches this draw object + /// + public override cObject3D MatchesPoint2D(int X, int Y) + { + // ATTENTION: Selecting entire polygons makes only sense with eSurfaceMode.Fill In + // line mode polygons are transparent and a click into the polygon would go to the background. + if (!mb_Fill || mi_Inst.mi_Selection.SinglePoints) + { + int s32_MaxDist = (int)mf_SelSize / 2 + SELECT_RADIUS; + foreach (cPoint i_Point in mi_Points) + { + if (i_Point.mi_P2D.CalcDistanceTo(X, Y) <= s32_MaxDist) + return i_Point.mi_P3D; + } + } + else // select entire polygon + { + // Detect if the point is inside the polygon. Here SELECT_RADIUS is ignored. But + // the user must only click into the middle of the polygon, which is easier than + // clicking a thin line. + bool b_Result = false; + int k = mi_Points.Length - 1; + for (int i = 0; i < mi_Points.Length; i++) + { + cPoint2D i_Point1 = mi_Points[i].mi_P2D; + cPoint2D i_Point2 = mi_Points[k].mi_P2D; + + if (i_Point1.md_Y < Y && i_Point2.md_Y >= Y || + i_Point2.md_Y < Y && i_Point1.md_Y >= Y) + { + if (i_Point1.md_X + (Y - i_Point1.md_Y) / + (i_Point2.md_Y - i_Point1.md_Y) * + (i_Point2.md_X - i_Point1.md_X) < X) + { + b_Result = !b_Result; + } + } + k = i; + } + if (b_Result) + return mi_Polygon3D; + } + return null; + } + + /// + /// For debugging in Visual Studio + /// + public override string ToString() + { + return String.Format("cPolygon ({0} points)", mk_Screen.Length); + } + } + + #endregion cPolygon + + // ----- Math Stuff ------ + + #region cMouse + + private class cMouse + { + public eMouseAction me_Action; // left mouse button action + public Point mk_LastPos; // last mouse location + public Point mk_OffMove; // Mouse offset after moving the graph with the mouse + public Point mk_OffCoord; // Offset caused by labels in coordinate system + public TrackBar mi_TrackRho; // Rho trackbar (optional) + public TrackBar mi_TrackTheta; // Theta trackbar (optional) + public TrackBar mi_TrackPhi; // Phi trackbar (optional) + public double md_Rho = VALUES_RHO.Default; + public double md_Theta = VALUES_THETA.Default; + public double md_Phi = VALUES_PHI.Default; + + public void AssignTrackbar(eMouseAction e_Trackbar, TrackBar i_Trackbar, EventHandler i_OnScroll) + { + if (i_Trackbar == null) + return; + + cDefault i_Default = null; + switch (e_Trackbar) + { + case eMouseAction.Rho: + i_Default = VALUES_RHO; + mi_TrackRho = i_Trackbar; + break; + + case eMouseAction.Theta: + i_Default = VALUES_THETA; + mi_TrackTheta = i_Trackbar; + break; + + case eMouseAction.Phi: + i_Default = VALUES_PHI; + mi_TrackPhi = i_Trackbar; + break; + } + + i_Trackbar.Minimum = (int)i_Default.Min; + i_Trackbar.Maximum = (int)i_Default.Max; + i_Trackbar.Value = (int)i_Default.Default; + i_Trackbar.Scroll += i_OnScroll; + } + + /// + /// User has moved the TrackBar + /// + public void OnTrackBarScroll() + { + if (mi_TrackRho != null) md_Rho = mi_TrackRho.Value; + if (mi_TrackTheta != null) md_Theta = mi_TrackTheta.Value; + if (mi_TrackPhi != null) md_Phi = mi_TrackPhi.Value; + } + + public bool OnMouseWheel(int s32_Delta) + { + if (me_Action != eMouseAction.None) + return false; + + me_Action = eMouseAction.Rho; + OnMouseMove(0, s32_Delta / 10); + me_Action = eMouseAction.None; + return true; + } + + /// + /// User has dragged the mouse over the 3D control + /// + public void OnMouseMove(int s32_DiffX, int s32_DiffY) + { + if (me_Action == eMouseAction.Rho) + { + md_Rho += s32_DiffY * VALUES_RHO.MouseFactor; + SetRho(md_Rho); + } + if (me_Action == eMouseAction.Theta || me_Action == eMouseAction.ThetaAndPhi) + { + md_Theta -= s32_DiffY * VALUES_THETA.MouseFactor; + SetTheta(md_Theta); + } + if (me_Action == eMouseAction.Phi || me_Action == eMouseAction.ThetaAndPhi) + { + md_Phi -= s32_DiffX * VALUES_PHI.MouseFactor; + SetPhi(md_Phi); + } + } + + public void SetRho(double d_Rho) + { + md_Rho = d_Rho; + md_Rho = Math.Max(md_Rho, VALUES_RHO.Min); + md_Rho = Math.Min(md_Rho, VALUES_RHO.Max); + if (mi_TrackRho != null) + mi_TrackRho.Value = (int)md_Rho; + } + + public void SetTheta(double d_Theta) + { + md_Theta = d_Theta; + md_Theta = Math.Max(md_Theta, VALUES_THETA.Min); + md_Theta = Math.Min(md_Theta, VALUES_THETA.Max); + if (mi_TrackTheta != null) + mi_TrackTheta.Value = (int)md_Theta; + } + + public void SetPhi(double d_Phi) + { + md_Phi = d_Phi; + while (md_Phi > 360.0) md_Phi -= 360.0; // continuous rotation + while (md_Phi < 0.0) md_Phi += 360.0; // continuous rotation + if (mi_TrackPhi != null) + mi_TrackPhi.Value = (int)md_Phi; + } + } + + #endregion cMouse + + #region cRange + + private class cRange + { + private double md_Min, md_Max; + + public double Min + { + get { return md_Min; } + } + + public double Max + { + get { return md_Max; } + } + + public double Range + { + get { return md_Max - md_Min; } + } + + /// + /// Constructor + /// + public cRange(double d_Min, double d_Max, bool b_IncludeZero, eRaster e_Raster) + { + md_Min = d_Min; + md_Max = d_Max; + + if (md_Max == md_Min) + { + md_Min -= 1.0; + md_Max += 1.0; + } + + if (e_Raster == eRaster.Off) + return; + + if (b_IncludeZero) + { + md_Min = Math.Min(0.0, md_Min); + md_Max = Math.Max(0.0, md_Max); + } + + // Add 10 % excess to all axes + if (md_Min < 0.0 || (md_Min > 0.0 && !b_IncludeZero)) md_Min -= Math.Abs(md_Min) * AXIS_EXCESS; + if (md_Max > 0.0 || (md_Max < 0.0 && !b_IncludeZero)) md_Max += Math.Abs(md_Max) * AXIS_EXCESS; + } + } + + #endregion cRange + + #region cBounds + + private class cBounds + { + private Editor3D mi_Inst; + private cRange mi_RangeX; + private cRange mi_RangeY; + private cRange mi_RangeZ; + private double md_MinX, md_MaxX, md_MinY, md_MaxY, md_MinZ, md_MaxZ; + + public cRange X + { + get { return mi_RangeX; } + } + + public cRange Y + { + get { return mi_RangeY; } + } + + public cRange Z + { + get { return mi_RangeZ; } + } + + /// + /// Constructor + /// + public cBounds(Editor3D i_Inst) + { + mi_Inst = i_Inst; + } + + /// + /// Also assigns mi_Inst to all draw objects + /// + public void Calculate() + { + md_MinX = double.PositiveInfinity; + md_MaxX = double.NegativeInfinity; + md_MinY = double.PositiveInfinity; + md_MaxY = double.NegativeInfinity; + md_MinZ = double.PositiveInfinity; + md_MaxZ = double.NegativeInfinity; + + foreach (cDrawObj i_DrawObj in mi_Inst.mi_UserObjects) + { + i_DrawObj.mi_Inst = mi_Inst; + i_DrawObj.mi_Object3D.mi_Inst = mi_Inst; + + foreach (cPoint i_Point in i_DrawObj.mi_Points) + { + cPoint3D i_Point3D = i_Point.mi_P3D; + i_Point3D.mi_Inst = mi_Inst; + + md_MinX = Math.Min(md_MinX, i_Point3D.X); + md_MaxX = Math.Max(md_MaxX, i_Point3D.X); + md_MinY = Math.Min(md_MinY, i_Point3D.Y); + md_MaxY = Math.Max(md_MaxY, i_Point3D.Y); + md_MinZ = Math.Min(md_MinZ, i_Point3D.Z); + md_MaxZ = Math.Max(md_MaxZ, i_Point3D.Z); + } + } + + mi_RangeX = new cRange(md_MinX, md_MaxX, mi_Inst.AxisX.IncludeZero, mi_Inst.me_Raster); + mi_RangeY = new cRange(md_MinY, md_MaxY, mi_Inst.AxisY.IncludeZero, mi_Inst.me_Raster); + mi_RangeZ = new cRange(md_MinZ, md_MaxZ, mi_Inst.AxisZ.IncludeZero, mi_Inst.me_Raster); + } + + /// + /// Used to get the color from the ColorScheme + /// + public double CalcFactorZ(double d_Value) + { + return (d_Value - md_MinZ) / (md_MaxZ - md_MinZ); + } + } + + #endregion cBounds + + #region cQuadrant + + private class cQuadrant + { + public double md_SortXY; // Sort order of raster in area XY (red) + public double md_SortXZ; // Sort order of X axis and raster in area XZ (blue) + public double md_SortYZ; // Sort order of Y axis and raster in area YZ (green) + public int ms32_Quadrant; + public bool mb_BottomView; + + public void Calculate(double d_Phi, cLine i_AxisX, cLine i_AxisY, cLine i_AxisZ) + { + // Split rotation into 4 sections (0...3) which increment every 90° starting at 45° + int s32_Section45 = (int)d_Phi + 45; + if (s32_Section45 > 360) s32_Section45 -= 360; + s32_Section45 = Math.Min(3, s32_Section45 / 90); + + // Theta elevation lets the camera watch the graph from the top or bottom + switch (s32_Section45) + { + case 0: mb_BottomView = i_AxisX.md_Angle < 180.0; break; + case 1: mb_BottomView = i_AxisY.md_Angle < 180.0; break; + case 2: mb_BottomView = i_AxisX.md_Angle > 180.0; break; + case 3: mb_BottomView = i_AxisY.md_Angle > 180.0; break; + } + + // The quadrant changes when the 2D transformed Z axis is in line with the X or Y axis + if (mb_BottomView) + { + switch (s32_Section45) + { + case 0: ms32_Quadrant = i_AxisX.md_Angle + 180.0 < i_AxisZ.md_Angle ? 1 : 0; break; + case 1: ms32_Quadrant = i_AxisY.md_Angle + 180.0 < i_AxisZ.md_Angle ? 2 : 1; break; + case 2: ms32_Quadrant = i_AxisX.md_Angle < i_AxisZ.md_Angle ? 3 : 2; break; + case 3: ms32_Quadrant = i_AxisY.md_Angle < i_AxisZ.md_Angle ? 0 : 3; break; + } + } + else // Top View + { + switch (s32_Section45) + { + case 0: ms32_Quadrant = i_AxisX.md_Angle > i_AxisZ.md_Angle ? 1 : 0; break; + case 1: ms32_Quadrant = i_AxisY.md_Angle > i_AxisZ.md_Angle ? 2 : 1; break; + case 2: ms32_Quadrant = i_AxisX.md_Angle + 180.0 > i_AxisZ.md_Angle ? 3 : 2; break; + case 3: ms32_Quadrant = i_AxisY.md_Angle + 180.0 > i_AxisZ.md_Angle ? 0 : 3; break; + } + } + + md_SortXY = (mb_BottomView) ? 99999.9 : -99999.9; + md_SortXZ = (ms32_Quadrant == 1 || ms32_Quadrant == 2) ? 99999.9 : -99999.9; + md_SortYZ = (ms32_Quadrant == 0 || ms32_Quadrant == 1) ? 99999.9 : -99999.9; + + i_AxisX.md_Sort = md_SortXZ; + i_AxisY.md_Sort = md_SortYZ; + i_AxisZ.md_Sort = (ms32_Quadrant == 3) ? -99999.9 : 99999.9; + + // Debug.WriteLine(String.Format("Section: {0} Quadrant: {1}", s32_Section45, ms32_Quadrant)); + } + } + + #endregion cQuadrant + + #region cTransform + + private class cTransform + { + // Camera distance. Smaller values result in ugly stretched egdes when rotating. + private const double DISTANCE = 0.55; + + private double md_sf; // sf = sinus fi + private double md_st; // st = sinus theta + private double md_cf; // cf = cosinus fi + private double md_ct; // ct = cosinus theta + private double md_Rho; + + // ---------------- + private double md_FactX; + + private double md_OffsX; + private double md_FactY; + private double md_OffsY; + private double md_Resize = 1.0; + + // ---------------- + public cPoint3D mi_Center3D = new cPoint3D(0, 0, 0); + + public double md_NormalizeX; + public double md_NormalizeY; + public double md_NormalizeZ; + public double md_Zoom; + + // ---------------- + private Size mk_InitialSize = Size.Empty; + + private Editor3D mi_Inst; + + public cTransform(Editor3D i_Inst) + { + mi_Inst = i_Inst; + } + + public void SetCoefficients(cMouse i_Mouse) + { + md_Rho = i_Mouse.md_Rho; // Distance of viewer (zoom) + double d_Theta = i_Mouse.md_Theta * Math.PI / 180.0; // Height of viewer (elevation) + double d_Phi = (i_Mouse.md_Phi - 180.0) * Math.PI / 180.0; // Rotation around center (-Pi ... +Pi) + + // Speed optimization: precalculate factors + md_sf = Math.Sin(d_Phi); + md_cf = Math.Cos(d_Phi); + md_st = Math.Sin(d_Theta); // Theta = 0...pi --> st = 0 .. 1 .. 0 + md_ct = Math.Cos(d_Theta); // Theta = 0...pi --> ct = 1 .. 0 .. -1 + + CalcZoom(); + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + + /// + /// The initial size is needed to calculate the user resizing factor. To assure that it + /// is correct it must be set when the control has already been created. Then it will be + /// the size that was defined in Visual Studio Form Designer. + /// + public void SetInitialSize(Size k_Size) + { + mk_InitialSize = k_Size; + SetSize(k_Size); + } + + /// + /// The control has been resized. This may be called with an invalid size before the + /// control is created! + /// + public void SetSize(Size k_Size) // Control.ClientSize + { + if (mk_InitialSize == Size.Empty) + return; + + double d_Width = k_Size.Width * 0.0254 / 96.0; // 0.0254 meter = 1 inch. Screen has 96 DPI + double d_Height = k_Size.Height * 0.0254 / 96.0; + + // linear transformation coefficients + md_FactX = k_Size.Width / d_Width; + md_FactY = -k_Size.Height / d_Height; + + md_OffsX = md_FactX * d_Width / 2.0; + md_OffsY = -md_FactY * d_Height / 2.0; + + // ----------------------------------- + + double d_ResizeX = (double)k_Size.Width / mk_InitialSize.Width; + double d_ResizeY = (double)k_Size.Height / mk_InitialSize.Height; + md_Resize = Math.Min(d_ResizeX, d_ResizeY); + + md_FactX *= md_Resize; + md_FactY *= md_Resize; + + CalcZoom(); + mi_Inst.me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + + // Required for correct painting order of polygons (always from back to front) + public double ProjectXY(double X, double Y, double Z = 0.0) + { + return X * md_cf + Y * md_sf + Z * md_ct; + } + + // Used to convert mouse movements back into the 3D space depending on the current + // rotation angle + public double ReverseProject(double X, double Y, double Z) + { + if (mi_Inst.AxisX.Mirror) X = -X; + if (mi_Inst.AxisY.Mirror) Y = -Y; + if (mi_Inst.AxisZ.Mirror) Z = -Z; + + // If Theta has the correct range from 10 to 170 degree --> Sinus(Theta) will never + // become zero. This can only happen if VALUES_THETA has been manipulated to invalid + // Min/Max values. + double d_Divide = Math.Max(0.1, md_st); + return (-X * md_sf + Y * md_cf + Z / d_Divide) / md_Zoom; + } + + /// + /// This approximates a zoom factor that depends on Rho and the resize window factor. + /// Used to adapt the size of lines, shapes and selected points. + /// + private void CalcZoom() + { + md_Zoom = md_Resize * (1800.0 / (md_Rho + 300)); + } + + // Performs projection. Calculates 2D screen coordinates from 3D point. + public cPoint2D Project3D(cPoint3D i_Point3D, eMirror e_Mirror) + { + double X = i_Point3D.X; + double Y = i_Point3D.Y; + double Z = i_Point3D.Z; + + // Mirror axis values increasing / decreasing + if (mi_Inst.AxisX.Mirror && (e_Mirror & eMirror.X) > 0) X = mi_Inst.mi_Bounds.X.Max - (X - mi_Inst.mi_Bounds.X.Min); + if (mi_Inst.AxisY.Mirror && (e_Mirror & eMirror.Y) > 0) Y = mi_Inst.mi_Bounds.Y.Max - (Y - mi_Inst.mi_Bounds.Y.Min); + if (mi_Inst.AxisZ.Mirror && (e_Mirror & eMirror.Z) > 0) Z = mi_Inst.mi_Bounds.Z.Max - (Z - mi_Inst.mi_Bounds.Z.Min); + + X = (X - mi_Center3D.X) * md_NormalizeX; + Y = (Y - mi_Center3D.Y) * md_NormalizeY; + Z = (Z - mi_Center3D.Z) * md_NormalizeZ; + + // 3D coordinates with center point in the middle of the screen X positive to the + // right, X negative to the left Y positive to the top, Y negative to the bottom + double xn = -md_sf * X + md_cf * Y; + double yn = -md_cf * md_ct * X - md_sf * md_ct * Y + md_st * Z; + double zn = -md_cf * md_st * X - md_sf * md_st * Y - md_ct * Z + md_Rho; + + zn = Math.Max(zn, 0.01); // avoid division by zero + + // Thales' theorem + cPoint2D i_Point2D = new cPoint2D(xn * DISTANCE / zn, yn * DISTANCE / zn); + + i_Point2D.md_X = i_Point2D.md_X * md_FactX + md_OffsX; + i_Point2D.md_Y = i_Point2D.md_Y * md_FactY + md_OffsY; + return i_Point2D; + } + } + + #endregion cTransform + + #region cDefault + + /// + /// Stores defauls for Rho, Theta, Phi + /// + private class cDefault + { + public readonly double Min; + public readonly double Max; + public readonly double Default; + public readonly double MouseFactor; + + public cDefault(double d_Min, double d_Max, double d_Default, double d_MouseFactor) + { + Min = d_Min; + Max = d_Max; + Default = d_Default; + MouseFactor = d_MouseFactor; + } + } + + #endregion cDefault + + // Limits and default values for mouse actions and trackbars. + // ATTENTION: It is strongly recommended not to change the MIN, MAX values. The mouse factor + // defines how much mouse movement you need for a change. A movement of mouse by approx 1000 + // pixels on the screen results in getting from Min to Max or vice versa. + private static readonly cDefault VALUES_RHO = new cDefault(300, 1800, 1350, 2); + + private static readonly cDefault VALUES_THETA = new cDefault(10, 170, 70, 0.25); // degree + private static readonly cDefault VALUES_PHI = new cDefault(0, 360, 230, 0.4); // degree (continuous rotation) + + // The coordinate axes are 10 % longer than the bounds of the X,Y,Z values + private const double AXIS_EXCESS = 0.1; + + // For any strange reason the graph is not centered vertically + private const int VERT_OFFSET = -30; + + // The maximum distance between mouse pointer and a 2D point to display the tooltip + private const int TOOLTIP_RADIUS = 6; + + // The maximum distance between mouse pointer and a 2D point to allow a match when selecting + // a 3D object. + private const int SELECT_RADIUS = 3; + + // Calculate 3-dimensional Z value from X,Y values + public delegate double delRendererFunction(double X, double Y); + + // IMPORTANT: Read the detailed comment of function SelectionCallback() at the end of this class. + public delegate eInvalidate delSelectHandler(eSelEvent e_Event, Keys e_Modifiers, int s32_DeltaX, int s32_DeltaY, cObject3D i_Object); + + private Pen[] mi_BorderPens = new Pen[2]; + private SolidBrush mi_TopLegendBrush = null; + private eRaster me_Raster = eRaster.Labels; + private cAxis[] mi_Axis = new cAxis[3]; + private cMouse mi_Mouse = new cMouse(); + private List mi_MessageData = new List(); + private eRecalculate me_Recalculate = eRecalculate.Nothing; + private eNormalize me_Normalize = eNormalize.Separate; + private eLegendPos me_LegendPos = eLegendPos.BottomLeft; + private List mi_AxisLines = new List(); // 0, 3, or 45 axis lines of coordinate system + private List mi_UserObjects = new List(); // Draw objects from the user (cLine, cShape, cPolygon) + private List mi_AllObjects = new List(); // mi_UserObjects + mi_AxisLines + private cQuadrant mi_Quadrant = new cQuadrant(); + private Dictionary mi_UserInputs = new Dictionary(); + private cUndoImpl mi_UndoBuffer; + private cTransform mi_Transform; + private cBounds mi_Bounds; + private cTooltip mi_Tooltip; + private cSelection mi_Selection; + private cObject3D mi_DragObject; + + #region Properties + + /// + /// See comment of enum eTooltip. This property can also be set in the Visual Studio Designer + /// + public eTooltip TooltipMode + { + get { return mi_Tooltip.Mode; } + set { mi_Tooltip.Mode = value; } + } + + /// + /// See comment of enum eLegendPos. This property can also be set in the Visual Studio Designer + /// + public eLegendPos LegendPos + { + get { return me_LegendPos; } + set { me_LegendPos = value; } + } + + /// + /// See comment of enum eNormalize. This change will become visible the next time you call Invalidate() + /// + public eNormalize Normalize + { + get { return me_Normalize; } + set + { + if (me_Normalize != value) + { + me_Normalize = value; + me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + } + + /// + /// See comment of enum eRaster This property can also be set in the Visual Studio Designer + /// This change will become visible the next time you call Invalidate() + /// + public eRaster Raster + { + set + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + if (me_Raster != value) + { + me_Raster = value; + me_Recalculate |= eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + get + { + return me_Raster; + } + } + + /// + /// Sets the border color when the 3D Editor does not have the keyboard focus Setting + /// BorderColor = Color.Empty turns off the border This change will become visible the next + /// time you call Invalidate() This property can also be set in the Visual Studio Designer + /// + public Color BorderColorNormal + { + set + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + if (value.A > 0) mi_BorderPens[0] = new Pen(value, 1); + else mi_BorderPens[0] = null; // transparent color + } + get + { + if (mi_BorderPens[0] != null) return mi_BorderPens[0].Color; + else return Color.Empty; + } + } + + /// + /// Sets the border color when the 3D Editor has the keyboard focus Setting BorderColorFocus + /// = Color.Empty turns off the highlighting on focus. This change will become visible the + /// next time you call Invalidate() This property can also be set in the Visual Studio Designer + /// + public Color BorderColorFocus + { + set + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + if (value.A > 0) mi_BorderPens[1] = new Pen(value, 1); + else mi_BorderPens[1] = mi_BorderPens[0]; + } + get + { + if (mi_BorderPens[1] != null) return mi_BorderPens[1].Color; + else return BorderColorNormal; + } + } + + /// + /// Show a legend with Rotation, Elevation and Distance at the top left Setting LegendColor + /// = Color.Empty turns off the top legend This property can also be set in the Visual + /// Studio Designer This change will become visible the next time you call Invalidate() + /// + public Color TopLegendColor + { + set + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + mi_TopLegendBrush = new SolidBrush(value); + } + get + { + if (mi_TopLegendBrush != null) return mi_TopLegendBrush.Color; + else return Color.Empty; + } + } + + /// + /// returns the total count of loaded draw objects (lines, shapes and polygons) + /// + [Browsable(false)] + public String ObjectStatistics + { + get + { + int s32_Lines = 0; + int s32_Shapes = 0; + int s32_Polygons = 0; + foreach (cDrawObj i_Obj in mi_UserObjects) + { + if (i_Obj is cLine) s32_Lines++; + if (i_Obj is cShape) s32_Shapes++; + if (i_Obj is cPolygon) s32_Polygons++; + } + StringBuilder i_Out = new StringBuilder(); + if (s32_Lines > 0) i_Out.Append(s32_Lines + " Lines, "); + if (s32_Shapes > 0) i_Out.Append(s32_Shapes + " Shapes, "); + if (s32_Polygons > 0) i_Out.Append(s32_Polygons + " Polygons, "); + return i_Out.ToString().TrimEnd(' ', ','); + } + } + + /// + /// See comments for class cAxis The properties of the class cAxis can be expanded in the + /// Visual Studio designer + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public cAxis AxisX + { + get { return mi_Axis[(int)eCoord.X]; } + } + + [TypeConverter(typeof(ExpandableObjectConverter))] + public cAxis AxisY + { + get { return mi_Axis[(int)eCoord.Y]; } + } + + [TypeConverter(typeof(ExpandableObjectConverter))] + public cAxis AxisZ + { + get { return mi_Axis[(int)eCoord.Z]; } + } + + /// + /// This property controls if and how the user can select draw objects / points The + /// properties of the class cSelection can be expanded in the Visual Studio designer + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public cSelection Selection + { + get { return mi_Selection; } + } + + /// + /// This property contains the public methods for the Undo / Redo buffer The property + /// 'Enabled' can be expanded in the Visual Studio designer + /// + [TypeConverter(typeof(ExpandableObjectConverter))] + public cUndoBuffer UndoBuffer + { + get { return mi_UndoBuffer; } + } + + #endregion Properties + + /// + /// b_ResetOffset = true --> reset the offset that the user has created with SHIFT + + /// moving the 3D object This change will become visible the next time you call Invalidate() + /// + public void SetCoefficients(double d_Rho, double d_Theta, double d_Phi, bool b_ResetOffset = true) + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + mi_Mouse.SetRho(d_Rho); + mi_Mouse.SetTheta(d_Theta); + mi_Mouse.SetPhi(d_Phi); + + if (b_ResetOffset) + { + mi_Mouse.mk_OffMove.X = 0; + mi_Mouse.mk_OffMove.Y = 0; + } + + mi_Transform.SetCoefficients(mi_Mouse); + } + + /// + /// Convert mouse movement in 2D space back into the 3D space depending on the current + /// rotation angle and Min/Max values. + /// + public cPoint3D ReverseProject(int s32_MouseX, int s32_MouseY) + { + double d_FactX = mi_Transform.ReverseProject(mi_Bounds.X.Range, 0.0, 0.0); + double d_FactY = mi_Transform.ReverseProject(0.0, mi_Bounds.Y.Range, 0.0); + double d_FactZ = mi_Transform.ReverseProject(0.0, 0.0, mi_Bounds.Z.Range); + + return new cPoint3D(d_FactX * s32_MouseX / 300.0, + d_FactY * s32_MouseX / 300.0, + d_FactZ * s32_MouseY / 300.0); + } + + /// + /// Trackbars are optional for user interaction. If this function is never called + /// thetrackbars are not used. + /// + public void AssignTrackBars(TrackBar i_Rho, TrackBar i_Theta, TrackBar i_Phi) + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + mi_Mouse.AssignTrackbar(eMouseAction.Rho, i_Rho, new EventHandler(OnTrackbarScroll)); + mi_Mouse.AssignTrackbar(eMouseAction.Theta, i_Theta, new EventHandler(OnTrackbarScroll)); + mi_Mouse.AssignTrackbar(eMouseAction.Phi, i_Phi, new EventHandler(OnTrackbarScroll)); + } + + /// + /// Load one of the three pre-defined input control patterns + /// + public void SetUserInputs(eMouseCtrl e_MouseCtrl) + { + List i_Inputs = new List(); + + switch (e_MouseCtrl) + { + case eMouseCtrl.L_Theta_R_Phi: + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.None, eMouseAction.Theta)); + i_Inputs.Add(new cUserInput(MouseButtons.Right, Keys.None, eMouseAction.Phi)); + break; + + case eMouseCtrl.L_Theta_L_Phi: + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.None, eMouseAction.ThetaAndPhi)); + break; + + case eMouseCtrl.M_Theta_M_Phi: + i_Inputs.Add(new cUserInput(MouseButtons.Middle, Keys.None, eMouseAction.ThetaAndPhi)); + break; + } + + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Control, eMouseAction.Rho)); + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Shift, eMouseAction.Move)); + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt, eMouseAction.SelectObj)); + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt | Keys.Control, eMouseAction.Callback)); + i_Inputs.Add(new cUserInput(MouseButtons.Left, Keys.Alt | Keys.Shift, eMouseAction.Callback)); + + SetUserInputs(i_Inputs.ToArray()); + } + + /// + /// Load fully user defined input control patterns. Each user input must define a unique + /// combination of mouse button and modifier key(s). + /// + public void SetUserInputs(cUserInput[] i_Inputs) + { + mi_UserInputs.Clear(); + foreach (cUserInput i_Input in i_Inputs) + { + // throws if same UID has already been added + mi_UserInputs.Add(i_Input.UID, i_Input); + } + } + + // ================================================================================== + + /// + /// Constructor + /// + public Editor3D() + { this.DoubleBuffered = true; // 启用双缓冲 - // avoid flicker - SetStyle(ControlStyles.AllPaintingInWmPaint, true); - SetStyle(ControlStyles.OptimizedDoubleBuffer, true); - - mi_Bounds = new cBounds (this); - mi_Transform = new cTransform(this); - mi_Tooltip = new cTooltip (this); - mi_Selection = new cSelection(this); - mi_UndoBuffer = new cUndoImpl (this); - - // Load the default colors - BackColor = Color.White; - - mi_Axis[(int)eCoord.X] = new cAxis(this, eCoord.X, Color.DarkBlue); - mi_Axis[(int)eCoord.Y] = new cAxis(this, eCoord.Y, Color.DarkGreen); - mi_Axis[(int)eCoord.Z] = new cAxis(this, eCoord.Z, Color.DarkRed); - - mi_BorderPens[0] = new Pen (Color.FromArgb(255, 0xB4, 0xB4, 0xB4), 1); // normal border: bright gray - mi_BorderPens[1] = new Pen (Color.FromArgb(255, 0x33, 0x99, 0xFF), 1); // focused border: bright cyan - mi_TopLegendBrush = new SolidBrush(Color.FromArgb(255, 0xC8, 0xC8, 0x96)); // beige - - mi_Transform.SetCoefficients(mi_Mouse); - SetUserInputs(eMouseCtrl.L_Theta_R_Phi); - } - - // ================================================================================== - - /// - /// Removes all content from the control. - /// This change will become visible the next time you call Invalidate() - /// - public void Clear() - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - mi_MessageData.Clear(); - mi_UserObjects.Clear(); - mi_AxisLines .Clear(); - mi_AllObjects .Clear(); - mi_UndoBuffer .Clear(); - AxisX.Reset(); - AxisY.Reset(); - AxisZ.Reset(); - mi_Mouse.mk_OffMove = Point.Empty; - mi_Mouse.mk_OffCoord = Point.Empty; - me_Recalculate = eRecalculate.Nothing; - } - - /// - /// Adds a message to be shown, even if no 3D data is loaded. - /// Messages which are null are allowed, they will be skipped. - /// This change will become visible the next time you call Invalidate() - /// - public void AddMessageData(params cMessgData[] i_Messages) - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - foreach (cMessgData i_Mesg in i_Messages) - { - if (i_Mesg != null) - mi_MessageData.Add(i_Mesg); - } - } - - /// - /// Here you can add cSurfaceData, cScatterData, cPolygonData - /// RenderData which are null are allowed, they will be skipped. - /// This change will become visible the next time you call Invalidate() - /// - public void AddRenderData(params cRenderData[] i_Render) - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - foreach (cRenderData i_Data in i_Render) - { - if (i_Data != null) - { - i_Data.AddDrawObjects(this); - me_Recalculate |= eRecalculate.AddRemove | eRecalculate.CoordSystem | eRecalculate.Objects; - } - } - } - - /// - /// This is called from cRenderData.AddDrawObjects() - /// - private void AddDrawObject(cDrawObj i_Obj) - { - mi_UserObjects.Add(i_Obj); - mi_UndoBuffer.DrawObjectAdded(i_Obj); - } - - /// - /// Removes one or multiple 3D objects: cLine3D, cShape3D or cPolygon3D. - /// ATTENTION: It is not possible to remove a cPoint3D which is part of one or multiple Lines/Shapes/Polygons - /// This change will become visible the next time you call Invalidate() - /// - public void RemoveObjects(params cObject3D[] i_Objects) - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread - - foreach (cObject3D i_Object3D in i_Objects) - { - if (i_Object3D is cPoint3D) - throw new ArgumentException("You cannot remove a single point. Remove the 3D object that contains the point instead."); - - int s32_Found = -1; - for (int D=0; D - /// Search the object at the coordinate X, Y relative to to the upper left corner of the control. - /// b_OnlyCanSelect = true --> return only objects that can be selected by the user - /// b_OnlyCanSelect = false --> return any object at the given location - /// - public cObject3D FindObjectAt(int X, int Y, bool b_OnlyCanSelect) - { - X -= (mi_Mouse.mk_OffMove.X + mi_Mouse.mk_OffCoord.X); - Y -= (mi_Mouse.mk_OffMove.Y + mi_Mouse.mk_OffCoord.Y); - - // Search in reverse order. Last drawn objects are in foreground. - // ATTENTION: mi_UserObjects cannot be used here because it is not sorted --> background polygons would be found. - for (int i=mi_AllObjects.Count -1; i>=0; i--) - { - cDrawObj i_Draw = mi_AllObjects[i]; - - if (i_Draw.mi_Object3D == null) - continue; // a coordinate system line - - if (b_OnlyCanSelect && !i_Draw.mi_Object3D.CanSelect) - continue; // user selection disabled for this object - - // MatchesPoint2D() returns a cPoint3D, cLine3D, cShape3D or cPolygon3D. - cObject3D i_Found = i_Draw.MatchesPoint2D(X, Y); - if (i_Found != null) - return i_Found; - } - return null; - } - - // ========================================== PRIVATE ============================================ - - /// - /// This function normalizes the 3D ranges for the X,Y,Z coordinates. - /// Otherwise a 3D range of X,Y from -10 to +10 will appear much smaller than a range from -100 to +100. - /// It adapts the values so that rotation (phi) goes through the center of the X, Y pane. - /// - private void NormalizeRanges() - { - double d_RangeX = mi_Bounds.X.Range; - double d_RangeY = mi_Bounds.Y.Range; - double d_RangeZ = mi_Bounds.Z.Range; - - switch (me_Normalize) - { - case eNormalize.MaintainXY: - double d_RangeXY = (d_RangeX + d_RangeY) / 2.0; // average - d_RangeX = d_RangeXY; - d_RangeY = d_RangeXY; - break; - - case eNormalize.MaintainXYZ: - double d_RangeXYZ = (d_RangeX + d_RangeY + d_RangeZ) / 3.0; // average - d_RangeX = d_RangeXYZ; - d_RangeY = d_RangeXYZ; - d_RangeZ = d_RangeXYZ; - break; - } - - mi_Transform.md_NormalizeX = 250.0 / d_RangeX; // Ranges will never be zero. - mi_Transform.md_NormalizeY = 250.0 / d_RangeY; - mi_Transform.md_NormalizeZ = 250.0 / d_RangeZ; - - mi_Transform.mi_Center3D.X = (mi_Bounds.X.Max + mi_Bounds.X.Min) / 2.0; // average - mi_Transform.mi_Center3D.Y = (mi_Bounds.Y.Max + mi_Bounds.Y.Min) / 2.0; - mi_Transform.mi_Center3D.Z = (mi_Bounds.Z.Max + mi_Bounds.Z.Min) / 2.0; - } - - /// - /// Fills mi_AxisLines with 3 main axis and 42 raster lines - /// - private void CreateCoordinateSystem(Graphics i_Graph) - { - mi_Mouse.mk_OffCoord = new Point(0, VERT_OFFSET); - mi_AxisLines.Clear(); - - if (me_Raster == eRaster.Off) - return; - - cLine i_MainAxisX = new cLine(this, eCoord.X, eCoord.X, eMirror.Z); - mi_AxisLines.Add(i_MainAxisX); - - i_MainAxisX.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; - i_MainAxisX.mi_Points[1].mi_P3D.X = mi_Bounds.X.Max; - // ------------ - i_MainAxisX.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; // X axis at minimum Y position - i_MainAxisX.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Min; // X axis at minimum Y position - - // --------------------------------------------------- - - cLine i_MainAxisY = new cLine(this, eCoord.Y, eCoord.Y, eMirror.Z); - mi_AxisLines.Add(i_MainAxisY); - - i_MainAxisY.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; - i_MainAxisY.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Max; - // ------------ - i_MainAxisY.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; // Y axis at minimum X position - i_MainAxisY.mi_Points[1].mi_P3D.X = mi_Bounds.X.Min; // Y axis at minimum X position - // ------------ - if (mi_Bounds.Z.Min < 0.0 && mi_Bounds.Z.Max > 0.0) - i_MainAxisY.ms_Label = "0"; // label for Z value zero (red) - - // --------------------------------------------------- - - cLine i_MainAxisZ = new cLine(this, eCoord.Z, eCoord.Z, eMirror.None); - mi_AxisLines.Add(i_MainAxisZ); - - i_MainAxisZ.mi_Points[0].mi_P3D.Z = mi_Bounds.Z.Min; - i_MainAxisZ.mi_Points[1].mi_P3D.Z = mi_Bounds.Z.Max; - // ------------ - i_MainAxisZ.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; // Z axis start at minimum X position - i_MainAxisZ.mi_Points[1].mi_P3D.X = mi_Bounds.X.Min; // Z axis start at minimum X position - i_MainAxisZ.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; // Z axis start at minimum Y position - i_MainAxisZ.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Min; // Z axis start at minimum Y position - - // --------------------------------------------------- - - foreach (cLine i_Axis in mi_AxisLines) - { - i_Axis.Project3D(); - i_Axis.CalcAngle2D(); // required to calculate the quadrant - } - - // Calculate currently visible quadrant - mi_Quadrant.Calculate(mi_Mouse.md_Phi, i_MainAxisX, i_MainAxisY, i_MainAxisZ); - - // Add raster lines in 6 different directions - if (me_Raster >= eRaster.Raster) - { - for (int A=0; A<6; A++) // iterate Axes X,Y,Z twice - { - int F = A; - int S = A; - - // Combine X+Y, Y+Z, Z+X, Y+X, Z+Y, X+Z - if (A >= 3) F ++; - else S ++; - - eCoord e_First = (eCoord)(F % 3); - eCoord e_Second = (eCoord)(S % 3); - - // Define which mirror operations are allowed for this raster line - eMirror e_Mirror = eMirror.None; - if (e_Second == eCoord.X) e_Mirror = eMirror.X; - if (e_Second == eCoord.Y) e_Mirror = eMirror.Y; - if (e_Second == eCoord.Z) e_Mirror = eMirror.Z; - - cLine i_FirstLine = mi_AxisLines[(int)e_First]; // Main axis - cLine i_SecndLine = mi_AxisLines[(int)e_Second]; // Main axis - - double d_SecndStart = i_SecndLine.mi_Points[0].mi_P3D.GetValue(e_Second); - double d_SecndEnd = i_SecndLine.mi_Points[1].mi_P3D.GetValue(e_Second); - - // Distance between raster lines - double d_Interval = CalculateInterval(d_SecndEnd - d_SecndStart); - - int s32_Start = (int)(d_SecndStart / d_Interval) - 1; - int s32_End = (int)(d_SecndEnd / d_Interval) + 1; - - for (int L=s32_Start; L d_SecndEnd) - continue; - - cLine i_Raster = new cLine(this, e_First, e_Second, e_Mirror); - - i_Raster.mi_Points[0].mi_P3D = i_FirstLine.mi_Points[0].mi_P3D.Clone(); - i_Raster.mi_Points[1].mi_P3D = i_FirstLine.mi_Points[1].mi_P3D.Clone(); - - i_Raster.mi_Points[0].mi_P3D.SetValue(e_Second, d_Offset); - i_Raster.mi_Points[1].mi_P3D.SetValue(e_Second, d_Offset); - - i_Raster.ms_Label = FormatDouble(d_Offset); - - // Do not draw the raster line at value zero if it equals the main axis and the axis is not mirrored - if (L == 0 && !mi_Axis[(int)e_Second].Mirror && i_Raster.CoordEquals(mi_AxisLines[(int)e_First])) - continue; - - if ((e_First == eCoord.X && e_Second == eCoord.Z) || // Blue - (e_First == eCoord.Z && e_Second == eCoord.X)) - { - i_Raster.md_Sort = mi_Quadrant.md_SortXZ; - } - else if ((e_First == eCoord.Z && e_Second == eCoord.Y) || // Green - (e_First == eCoord.Y && e_Second == eCoord.Z)) - { - i_Raster.md_Sort = mi_Quadrant.md_SortYZ; - } - else // X + Y Red - { - i_Raster.md_Sort = mi_Quadrant.md_SortXY; - - // Special case: XY raster lines must be shifted down to negative end of Z axis - i_Raster.mi_Points[0].mi_P3D.Z = i_MainAxisZ.mi_Points[0].mi_P3D.Z; - i_Raster.mi_Points[1].mi_P3D.Z = i_MainAxisZ.mi_Points[0].mi_P3D.Z; - } - - i_Raster.Project3D(); - mi_AxisLines.Add(i_Raster); - } // for (L) - } // for (A) - } // if (Raster) - - // Remove the green and blue main axes if Z value zero is outside the visible range - if (mi_Bounds.Z.Min > 0.0 || mi_Bounds.Z.Max < 0.0) - { - mi_AxisLines.Remove(i_MainAxisX); - mi_AxisLines.Remove(i_MainAxisY); - } - - // Move the graph to the left when labels are enabled - if (me_Raster == eRaster.Labels) - { - int s32_LabelWidth = 0; - foreach (cLine i_Line in mi_AxisLines) - { - if (i_Line.me_Line == eCoord.Y && i_Line.me_Offset == eCoord.Z) - { - SizeF k_Size = i_Graph.MeasureString(i_Line.ms_Label, Font); - s32_LabelWidth = Math.Max(s32_LabelWidth, (int)k_Size.Width); - } - } - mi_Mouse.mk_OffCoord.X -= s32_LabelWidth / 2; - } - } - - /// - /// Makes a color brigther - /// - private static Color BrightenColor(Color c_Color) - { - int s32_Red = c_Color.R + (255 - c_Color.R) / 2; - int s32_Green = c_Color.G + (255 - c_Color.G) / 2; - int s32_Blue = c_Color.B + (255 - c_Color.B) / 2; - - return Color.FromArgb(255, s32_Red, s32_Green, s32_Blue); - } - - /// - /// returns intervals of 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, etc... - /// The count of intervals which fit into the range is always between 5 and 10 - /// - private static double CalculateInterval(double d_Range) - { - double d_Factor = Math.Pow(10.0, Math.Floor(Math.Log10(d_Range))); - if (d_Range / d_Factor >= 5.0) - return d_Factor; - else if (d_Range / (d_Factor / 2.0) >= 5.0) - return d_Factor / 2.0; - else - return d_Factor / 5.0; - } - - // md_Label = 123.000 --> display "123" - // md_Label = 15.700 --> display "15.7" - // md_Label = 4.260 --> display "4.26" - // md_Label = 0.834 --> display "0.834" - public static String FormatDouble(double d_Label) - { - return d_Label.ToString("0.000", CultureInfo.InvariantCulture).TrimEnd('0').TrimEnd('.'); - } - - /// - /// For debugging drawing speed. - /// ATTENTION: The first time the delays are wrong because of JIT compilation delays. - /// - private static void FormatStopwatch(String s_Measure, Stopwatch i_Watch, StringBuilder i_Debug) - { - double d_Elapsed = (double)i_Watch.ElapsedTicks / TimeSpan.TicksPerMillisecond; - i_Debug.AppendLine(s_Measure.PadRight(17) + d_Elapsed.ToString("0.000") + " ms"); - i_Watch.Restart(); - } - - /// - /// Checks if the 2D point X,Y lies on the line between i_Start and i_End within s32_MaxDist - /// All coordinates in pixels. - /// - private static bool IsPointOnLine(cPoint2D i_Start, cPoint2D i_End, int X, int Y, int s32_MaxDist) - { - double d_DeltaX = i_End.md_X - i_Start.md_X; - double d_DeltaY = i_End.md_Y - i_Start.md_Y; - - int s32_StepsX = Math.Abs((int)d_DeltaX); - int s32_StepsY = Math.Abs((int)d_DeltaY); - int s32_Steps = Math.Max(s32_StepsX, s32_StepsY); - - d_DeltaX /= s32_Steps; - d_DeltaY /= s32_Steps; - - cPoint2D i_Point = i_Start.Clone(); - for (int S=0; S<=s32_Steps; S++) - { - if (i_Point.CalcDistanceTo(X, Y) <= s32_MaxDist) - return true; - - i_Point.md_X += d_DeltaX; - i_Point.md_Y += d_DeltaY; - } - return false; - } - - // =================================== DRAWING ===================================== - - protected override void OnPaintBackground(PaintEventArgs e) - { - // Background is painted flickerless in OnPaint() - } - - /// - /// This is invoked by Invalidate() when the GUI thread becomes idle. - /// - protected override void OnPaint(PaintEventArgs e) - { + // avoid flicker + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + + mi_Bounds = new cBounds(this); + mi_Transform = new cTransform(this); + mi_Tooltip = new cTooltip(this); + mi_Selection = new cSelection(this); + mi_UndoBuffer = new cUndoImpl(this); + + // Load the default colors + BackColor = Color.White; + + mi_Axis[(int)eCoord.X] = new cAxis(this, eCoord.X, Color.DarkBlue); + mi_Axis[(int)eCoord.Y] = new cAxis(this, eCoord.Y, Color.DarkGreen); + mi_Axis[(int)eCoord.Z] = new cAxis(this, eCoord.Z, Color.DarkRed); + + mi_BorderPens[0] = new Pen(Color.FromArgb(255, 0xB4, 0xB4, 0xB4), 1); // normal border: bright gray + mi_BorderPens[1] = new Pen(Color.FromArgb(255, 0x33, 0x99, 0xFF), 1); // focused border: bright cyan + mi_TopLegendBrush = new SolidBrush(Color.FromArgb(255, 0xC8, 0xC8, 0x96)); // beige + + mi_Transform.SetCoefficients(mi_Mouse); + SetUserInputs(eMouseCtrl.L_Theta_R_Phi); + } + + // ================================================================================== + + /// + /// Removes all content from the control. This change will become visible the next time you + /// call Invalidate() + /// + public void Clear() + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + mi_MessageData.Clear(); + mi_UserObjects.Clear(); + mi_AxisLines.Clear(); + mi_AllObjects.Clear(); + mi_UndoBuffer.Clear(); + AxisX.Reset(); + AxisY.Reset(); + AxisZ.Reset(); + mi_Mouse.mk_OffMove = Point.Empty; + mi_Mouse.mk_OffCoord = Point.Empty; + me_Recalculate = eRecalculate.Nothing; + } + + /// + /// Adds a message to be shown, even if no 3D data is loaded. Messages which are null are + /// allowed, they will be skipped. This change will become visible the next time you call Invalidate() + /// + public void AddMessageData(params cMessgData[] i_Messages) + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + foreach (cMessgData i_Mesg in i_Messages) + { + if (i_Mesg != null) + mi_MessageData.Add(i_Mesg); + } + } + + /// + /// Here you can add cSurfaceData, cScatterData, cPolygonData RenderData which are null are + /// allowed, they will be skipped. This change will become visible the next time you call Invalidate() + /// + public void AddRenderData(params cRenderData[] i_Render) + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + foreach (cRenderData i_Data in i_Render) + { + if (i_Data != null) + { + i_Data.AddDrawObjects(this); + me_Recalculate |= eRecalculate.AddRemove | eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + } + + /// + /// This is called from cRenderData.AddDrawObjects() + /// + private void AddDrawObject(cDrawObj i_Obj) + { + mi_UserObjects.Add(i_Obj); + mi_UndoBuffer.DrawObjectAdded(i_Obj); + } + + /// + /// Removes one or multiple 3D objects: cLine3D, cShape3D or cPolygon3D. + /// ATTENTION: It is not possible to remove a cPoint3D which is part of one or multiple Lines/Shapes/Polygons + /// This change will become visible the next time you call Invalidate() + /// + public void RemoveObjects(params cObject3D[] i_Objects) + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread + + foreach (cObject3D i_Object3D in i_Objects) + { + if (i_Object3D is cPoint3D) + throw new ArgumentException("You cannot remove a single point. Remove the 3D object that contains the point instead."); + + int s32_Found = -1; + for (int D = 0; D < mi_UserObjects.Count; D++) + { + if (mi_UserObjects[D].mi_Object3D == i_Object3D) + { + s32_Found = D; + break; + } + } + + if (s32_Found < 0) + { + Debug.Assert(false, "There is something wrong in your code. You are removing an object that does not exist."); + } + else + { + cDrawObj i_DrawObj = mi_UserObjects[s32_Found]; + mi_UserObjects.RemoveAt(s32_Found); + mi_UndoBuffer.DrawObjectRemoved(i_DrawObj); + me_Recalculate |= eRecalculate.AddRemove | eRecalculate.CoordSystem | eRecalculate.Objects; + } + } + + mi_UndoBuffer.Store(); + } + + /// + /// Search the object at the coordinate X, Y relative to to the upper left corner of the + /// control. b_OnlyCanSelect = true --> return only objects that can be selected by the + /// user b_OnlyCanSelect = false --> return any object at the given location + /// + public cObject3D FindObjectAt(int X, int Y, bool b_OnlyCanSelect) + { + X -= (mi_Mouse.mk_OffMove.X + mi_Mouse.mk_OffCoord.X); + Y -= (mi_Mouse.mk_OffMove.Y + mi_Mouse.mk_OffCoord.Y); + + // Search in reverse order. Last drawn objects are in foreground. + // ATTENTION: mi_UserObjects cannot be used here because it is not sorted --> background + // polygons would be found. + for (int i = mi_AllObjects.Count - 1; i >= 0; i--) + { + cDrawObj i_Draw = mi_AllObjects[i]; + + if (i_Draw.mi_Object3D == null) + continue; // a coordinate system line + + if (b_OnlyCanSelect && !i_Draw.mi_Object3D.CanSelect) + continue; // user selection disabled for this object + + // MatchesPoint2D() returns a cPoint3D, cLine3D, cShape3D or cPolygon3D. + cObject3D i_Found = i_Draw.MatchesPoint2D(X, Y); + if (i_Found != null) + return i_Found; + } + return null; + } + + // ========================================== PRIVATE ============================================ + + /// + /// This function normalizes the 3D ranges for the X,Y,Z coordinates. Otherwise a 3D range + /// of X,Y from -10 to +10 will appear much smaller than a range from -100 to +100. It + /// adapts the values so that rotation (phi) goes through the center of the X, Y pane. + /// + private void NormalizeRanges() + { + double d_RangeX = mi_Bounds.X.Range; + double d_RangeY = mi_Bounds.Y.Range; + double d_RangeZ = mi_Bounds.Z.Range; + + switch (me_Normalize) + { + case eNormalize.MaintainXY: + double d_RangeXY = (d_RangeX + d_RangeY) / 2.0; // average + d_RangeX = d_RangeXY; + d_RangeY = d_RangeXY; + break; + + case eNormalize.MaintainXYZ: + double d_RangeXYZ = (d_RangeX + d_RangeY + d_RangeZ) / 3.0; // average + d_RangeX = d_RangeXYZ; + d_RangeY = d_RangeXYZ; + d_RangeZ = d_RangeXYZ; + break; + } + + mi_Transform.md_NormalizeX = 250.0 / d_RangeX; // Ranges will never be zero. + mi_Transform.md_NormalizeY = 250.0 / d_RangeY; + mi_Transform.md_NormalizeZ = 250.0 / d_RangeZ; + + mi_Transform.mi_Center3D.X = (mi_Bounds.X.Max + mi_Bounds.X.Min) / 2.0; // average + mi_Transform.mi_Center3D.Y = (mi_Bounds.Y.Max + mi_Bounds.Y.Min) / 2.0; + mi_Transform.mi_Center3D.Z = (mi_Bounds.Z.Max + mi_Bounds.Z.Min) / 2.0; + } + + /// + /// Fills mi_AxisLines with 3 main axis and 42 raster lines + /// + private void CreateCoordinateSystem(Graphics i_Graph) + { + mi_Mouse.mk_OffCoord = new Point(0, VERT_OFFSET); + mi_AxisLines.Clear(); + + if (me_Raster == eRaster.Off) + return; + + cLine i_MainAxisX = new cLine(this, eCoord.X, eCoord.X, eMirror.Z); + mi_AxisLines.Add(i_MainAxisX); + + i_MainAxisX.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; + i_MainAxisX.mi_Points[1].mi_P3D.X = mi_Bounds.X.Max; + // ------------ + i_MainAxisX.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; // X axis at minimum Y position + i_MainAxisX.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Min; // X axis at minimum Y position + + // --------------------------------------------------- + + cLine i_MainAxisY = new cLine(this, eCoord.Y, eCoord.Y, eMirror.Z); + mi_AxisLines.Add(i_MainAxisY); + + i_MainAxisY.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; + i_MainAxisY.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Max; + // ------------ + i_MainAxisY.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; // Y axis at minimum X position + i_MainAxisY.mi_Points[1].mi_P3D.X = mi_Bounds.X.Min; // Y axis at minimum X position + // ------------ + if (mi_Bounds.Z.Min < 0.0 && mi_Bounds.Z.Max > 0.0) + i_MainAxisY.ms_Label = "0"; // label for Z value zero (red) + + // --------------------------------------------------- + + cLine i_MainAxisZ = new cLine(this, eCoord.Z, eCoord.Z, eMirror.None); + mi_AxisLines.Add(i_MainAxisZ); + + i_MainAxisZ.mi_Points[0].mi_P3D.Z = mi_Bounds.Z.Min; + i_MainAxisZ.mi_Points[1].mi_P3D.Z = mi_Bounds.Z.Max; + // ------------ + i_MainAxisZ.mi_Points[0].mi_P3D.X = mi_Bounds.X.Min; // Z axis start at minimum X position + i_MainAxisZ.mi_Points[1].mi_P3D.X = mi_Bounds.X.Min; // Z axis start at minimum X position + i_MainAxisZ.mi_Points[0].mi_P3D.Y = mi_Bounds.Y.Min; // Z axis start at minimum Y position + i_MainAxisZ.mi_Points[1].mi_P3D.Y = mi_Bounds.Y.Min; // Z axis start at minimum Y position + + // --------------------------------------------------- + + foreach (cLine i_Axis in mi_AxisLines) + { + i_Axis.Project3D(); + i_Axis.CalcAngle2D(); // required to calculate the quadrant + } + + // Calculate currently visible quadrant + mi_Quadrant.Calculate(mi_Mouse.md_Phi, i_MainAxisX, i_MainAxisY, i_MainAxisZ); + + // Add raster lines in 6 different directions + if (me_Raster >= eRaster.Raster) + { + for (int A = 0; A < 6; A++) // iterate Axes X,Y,Z twice + { + int F = A; + int S = A; + + // Combine X+Y, Y+Z, Z+X, Y+X, Z+Y, X+Z + if (A >= 3) F++; + else S++; + + eCoord e_First = (eCoord)(F % 3); + eCoord e_Second = (eCoord)(S % 3); + + // Define which mirror operations are allowed for this raster line + eMirror e_Mirror = eMirror.None; + if (e_Second == eCoord.X) e_Mirror = eMirror.X; + if (e_Second == eCoord.Y) e_Mirror = eMirror.Y; + if (e_Second == eCoord.Z) e_Mirror = eMirror.Z; + + cLine i_FirstLine = mi_AxisLines[(int)e_First]; // Main axis + cLine i_SecndLine = mi_AxisLines[(int)e_Second]; // Main axis + + double d_SecndStart = i_SecndLine.mi_Points[0].mi_P3D.GetValue(e_Second); + double d_SecndEnd = i_SecndLine.mi_Points[1].mi_P3D.GetValue(e_Second); + + // Distance between raster lines + double d_Interval = CalculateInterval(d_SecndEnd - d_SecndStart); + + int s32_Start = (int)(d_SecndStart / d_Interval) - 1; + int s32_End = (int)(d_SecndEnd / d_Interval) + 1; + + for (int L = s32_Start; L < s32_End; L++) // iterate raster lines + { + double d_Offset = d_Interval * L; + + if (d_Offset < d_SecndStart || d_Offset > d_SecndEnd) + continue; + + cLine i_Raster = new cLine(this, e_First, e_Second, e_Mirror); + + i_Raster.mi_Points[0].mi_P3D = i_FirstLine.mi_Points[0].mi_P3D.Clone(); + i_Raster.mi_Points[1].mi_P3D = i_FirstLine.mi_Points[1].mi_P3D.Clone(); + + i_Raster.mi_Points[0].mi_P3D.SetValue(e_Second, d_Offset); + i_Raster.mi_Points[1].mi_P3D.SetValue(e_Second, d_Offset); + + i_Raster.ms_Label = FormatDouble(d_Offset); + + // Do not draw the raster line at value zero if it equals the main axis and + // the axis is not mirrored + if (L == 0 && !mi_Axis[(int)e_Second].Mirror && i_Raster.CoordEquals(mi_AxisLines[(int)e_First])) + continue; + + if ((e_First == eCoord.X && e_Second == eCoord.Z) || // Blue + (e_First == eCoord.Z && e_Second == eCoord.X)) + { + i_Raster.md_Sort = mi_Quadrant.md_SortXZ; + } + else if ((e_First == eCoord.Z && e_Second == eCoord.Y) || // Green + (e_First == eCoord.Y && e_Second == eCoord.Z)) + { + i_Raster.md_Sort = mi_Quadrant.md_SortYZ; + } + else // X + Y Red + { + i_Raster.md_Sort = mi_Quadrant.md_SortXY; + + // Special case: XY raster lines must be shifted down to negative end of + // Z axis + i_Raster.mi_Points[0].mi_P3D.Z = i_MainAxisZ.mi_Points[0].mi_P3D.Z; + i_Raster.mi_Points[1].mi_P3D.Z = i_MainAxisZ.mi_Points[0].mi_P3D.Z; + } + + i_Raster.Project3D(); + mi_AxisLines.Add(i_Raster); + } // for (L) + } // for (A) + } // if (Raster) + + // Remove the green and blue main axes if Z value zero is outside the visible range + if (mi_Bounds.Z.Min > 0.0 || mi_Bounds.Z.Max < 0.0) + { + mi_AxisLines.Remove(i_MainAxisX); + mi_AxisLines.Remove(i_MainAxisY); + } + + // Move the graph to the left when labels are enabled + if (me_Raster == eRaster.Labels) + { + int s32_LabelWidth = 0; + foreach (cLine i_Line in mi_AxisLines) + { + if (i_Line.me_Line == eCoord.Y && i_Line.me_Offset == eCoord.Z) + { + SizeF k_Size = i_Graph.MeasureString(i_Line.ms_Label, Font); + s32_LabelWidth = Math.Max(s32_LabelWidth, (int)k_Size.Width); + } + } + mi_Mouse.mk_OffCoord.X -= s32_LabelWidth / 2; + } + } + + /// + /// Makes a color brigther + /// + private static Color BrightenColor(Color c_Color) + { + int s32_Red = c_Color.R + (255 - c_Color.R) / 2; + int s32_Green = c_Color.G + (255 - c_Color.G) / 2; + int s32_Blue = c_Color.B + (255 - c_Color.B) / 2; + + return Color.FromArgb(255, s32_Red, s32_Green, s32_Blue); + } + + /// + /// returns intervals of 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, etc... The count of intervals + /// which fit into the range is always between 5 and 10 + /// + private static double CalculateInterval(double d_Range) + { + double d_Factor = Math.Pow(10.0, Math.Floor(Math.Log10(d_Range))); + if (d_Range / d_Factor >= 5.0) + return d_Factor; + else if (d_Range / (d_Factor / 2.0) >= 5.0) + return d_Factor / 2.0; + else + return d_Factor / 5.0; + } + + // md_Label = 123.000 --> display "123" md_Label = 15.700 --> display "15.7" md_Label = + // 4.260 --> display "4.26" md_Label = 0.834 --> display "0.834" + public static String FormatDouble(double d_Label) + { + return d_Label.ToString("0.000", CultureInfo.InvariantCulture).TrimEnd('0').TrimEnd('.'); + } + + /// + /// For debugging drawing speed. + /// ATTENTION: The first time the delays are wrong because of JIT compilation delays. + /// + private static void FormatStopwatch(String s_Measure, Stopwatch i_Watch, StringBuilder i_Debug) + { + double d_Elapsed = (double)i_Watch.ElapsedTicks / TimeSpan.TicksPerMillisecond; + i_Debug.AppendLine(s_Measure.PadRight(17) + d_Elapsed.ToString("0.000") + " ms"); + i_Watch.Restart(); + } + + /// + /// Checks if the 2D point X,Y lies on the line between i_Start and i_End within s32_MaxDist + /// All coordinates in pixels. + /// + private static bool IsPointOnLine(cPoint2D i_Start, cPoint2D i_End, int X, int Y, int s32_MaxDist) + { + double d_DeltaX = i_End.md_X - i_Start.md_X; + double d_DeltaY = i_End.md_Y - i_Start.md_Y; + + int s32_StepsX = Math.Abs((int)d_DeltaX); + int s32_StepsY = Math.Abs((int)d_DeltaY); + int s32_Steps = Math.Max(s32_StepsX, s32_StepsY); + + d_DeltaX /= s32_Steps; + d_DeltaY /= s32_Steps; + + cPoint2D i_Point = i_Start.Clone(); + for (int S = 0; S <= s32_Steps; S++) + { + if (i_Point.CalcDistanceTo(X, Y) <= s32_MaxDist) + return true; + + i_Point.md_X += d_DeltaX; + i_Point.md_Y += d_DeltaY; + } + return false; + } + + // =================================== DRAWING ===================================== + + protected override void OnPaintBackground(PaintEventArgs e) + { + // Background is painted flickerless in OnPaint() + } + + /// + /// This is invoked by Invalidate() when the GUI thread becomes idle. + /// + protected override void OnPaint(PaintEventArgs e) + { base.OnPaint(e); // Stupidly the .NET framework draws a red cross if any exception occurres in OnPaint() try - { - Render(e.Graphics); - } - catch (Exception Ex) - { - e.Graphics.ResetTransform(); - e.Graphics.Clear(Color.DarkRed); - e.Graphics.DrawString(Ex.Message + "\n" + Ex.StackTrace, new Font("Verdana", 8, FontStyle.Bold), Brushes.White, 10, 10); - return; - } + { + Render(e.Graphics); + } + catch (Exception Ex) + { + e.Graphics.ResetTransform(); + e.Graphics.Clear(Color.DarkRed); + e.Graphics.DrawString(Ex.Message + "\n" + Ex.StackTrace, new Font("Verdana", 8, FontStyle.Bold), Brushes.White, 10, 10); + return; + } - DrawBorder(e.Graphics); - } + DrawBorder(e.Graphics); + } - public Bitmap GetScreenshot() - { - Debug.Assert(!InvokeRequired); // Call only from GUI thread + public Bitmap GetScreenshot() + { + Debug.Assert(!InvokeRequired); // Call only from GUI thread - Bitmap i_Bmp = new Bitmap(ClientSize.Width, ClientSize.Height); - using (Graphics i_Graph = Graphics.FromImage(i_Bmp)) - { - Render(i_Graph); - } - return i_Bmp; - } + Bitmap i_Bmp = new Bitmap(ClientSize.Width, ClientSize.Height); + using (Graphics i_Graph = Graphics.FromImage(i_Bmp)) + { + Render(i_Graph); + } + return i_Bmp; + } - private void Render(Graphics i_Graph) - { - i_Graph.Clear(BackColor); + private void Render(Graphics i_Graph) + { + i_Graph.Clear(BackColor); - foreach (cMessgData i_Mesg in mi_MessageData) - { - i_Mesg.Draw(i_Graph, ClientRectangle); - } + foreach (cMessgData i_Mesg in mi_MessageData) + { + i_Mesg.Draw(i_Graph, ClientRectangle); + } - // If there are no 3D objects --> only show the messages. Do not show an empty coordinate system. - if (mi_UserObjects.Count == 0) - return; + // If there are no 3D objects --> only show the messages. Do not show an empty + // coordinate system. + if (mi_UserObjects.Count == 0) + return; - mi_UndoBuffer.Init(); + mi_UndoBuffer.Init(); - #if DEBUG_SPEED +#if DEBUG_SPEED StringBuilder i_Debug = new StringBuilder(); Stopwatch i_Watch = new Stopwatch(); i_Debug.Append("--------------------------\n"); i_Watch.Start(); - #endif +#endif - // Speed optimization: Add points to tooltip only if required - if ((me_Recalculate & eRecalculate.AddRemove) > 0) - { - mi_Tooltip.Clear(); - foreach (cDrawObj i_Object in mi_UserObjects) - { - foreach (cPoint i_Point in i_Object.mi_Points) - { - mi_Tooltip.AddPoint(i_Point); - } - } + // Speed optimization: Add points to tooltip only if required + if ((me_Recalculate & eRecalculate.AddRemove) > 0) + { + mi_Tooltip.Clear(); + foreach (cDrawObj i_Object in mi_UserObjects) + { + foreach (cPoint i_Point in i_Object.mi_Points) + { + mi_Tooltip.AddPoint(i_Point); + } + } - #if DEBUG_SPEED +#if DEBUG_SPEED FormatStopwatch("Add Tooltip: ", i_Watch, i_Debug); - #endif - } +#endif + } - // Speed optimization: Calculate coordinate system only if required - if ((me_Recalculate & eRecalculate.CoordSystem) > 0) - { - // Calculate Min/Max for all UserObjects, assign mi_Inst to all user objects. - mi_Bounds.Calculate(); + // Speed optimization: Calculate coordinate system only if required + if ((me_Recalculate & eRecalculate.CoordSystem) > 0) + { + // Calculate Min/Max for all UserObjects, assign mi_Inst to all user objects. + mi_Bounds.Calculate(); - // Calculate factors for transformation - NormalizeRanges(); + // Calculate factors for transformation + NormalizeRanges(); - // Fills mi_AxisLines with 3 main axis and 42 raster lines - CreateCoordinateSystem(i_Graph); + // Fills mi_AxisLines with 3 main axis and 42 raster lines + CreateCoordinateSystem(i_Graph); - #if DEBUG_SPEED +#if DEBUG_SPEED FormatStopwatch("Coord System: ", i_Watch, i_Debug); - #endif - } +#endif + } - // Speed optimization: Calculate 3D objects only if required - if ((me_Recalculate & eRecalculate.Objects) > 0) - { - foreach (cDrawObj i_Object in mi_UserObjects) - { - // This must not be called for axes - i_Object.CalcSortOrder(); + // Speed optimization: Calculate 3D objects only if required + if ((me_Recalculate & eRecalculate.Objects) > 0) + { + foreach (cDrawObj i_Object in mi_UserObjects) + { + // This must not be called for axes + i_Object.CalcSortOrder(); - // This must not be called for axes - // reload Pens, Brushes from User objects or from ColorScheme - i_Object.ProcessColors(); + // This must not be called for axes reload Pens, Brushes from User objects or + // from ColorScheme + i_Object.ProcessColors(); - // Project 3D --> 2D, calculate line width, shape radius,... - i_Object.Project3D(); - } + // Project 3D --> 2D, calculate line width, shape radius,... + i_Object.Project3D(); + } - #if DEBUG_SPEED +#if DEBUG_SPEED FormatStopwatch("Prepare Objects: ", i_Watch, i_Debug); - #endif - } +#endif + } - // Speed optimization: Merge lists only if at least one of them has changed - if ((me_Recalculate & (eRecalculate.AddRemove | eRecalculate.CoordSystem)) > 0) - { - mi_AllObjects.Clear(); - mi_AllObjects.AddRange(mi_AxisLines); - mi_AllObjects.AddRange(mi_UserObjects); + // Speed optimization: Merge lists only if at least one of them has changed + if ((me_Recalculate & (eRecalculate.AddRemove | eRecalculate.CoordSystem)) > 0) + { + mi_AllObjects.Clear(); + mi_AllObjects.AddRange(mi_AxisLines); + mi_AllObjects.AddRange(mi_UserObjects); - #if DEBUG_SPEED +#if DEBUG_SPEED FormatStopwatch("Merge Lists: ", i_Watch, i_Debug); - #endif - } +#endif + } - // Speed optimization: sort draw objects only if required - if ((me_Recalculate & (eRecalculate.AddRemove | eRecalculate.CoordSystem | eRecalculate.Objects)) > 0) - { - // Sort draw order from background to foreground - mi_AllObjects.Sort(); + // Speed optimization: sort draw objects only if required + if ((me_Recalculate & (eRecalculate.AddRemove | eRecalculate.CoordSystem | eRecalculate.Objects)) > 0) + { + // Sort draw order from background to foreground + mi_AllObjects.Sort(); - #if DEBUG_SPEED +#if DEBUG_SPEED FormatStopwatch("Sort List: ", i_Watch, i_Debug); - #endif - } +#endif + } - me_Recalculate = eRecalculate.Nothing; + me_Recalculate = eRecalculate.Nothing; - // --------------------------------------------------- + // --------------------------------------------------- - // Draw axis legends in bottom left corner - if (me_LegendPos == eLegendPos.BottomLeft) - { - int X = 4; - int Y = ClientSize.Height - Font.Height - 4; - for (int i=2; i>=0; i--) - { - if (String.IsNullOrEmpty(mi_Axis[i].LegendText)) - continue; + // Draw axis legends in bottom left corner + if (me_LegendPos == eLegendPos.BottomLeft) + { + int X = 4; + int Y = ClientSize.Height - Font.Height - 4; + for (int i = 2; i >= 0; i--) + { + if (String.IsNullOrEmpty(mi_Axis[i].LegendText)) + continue; - String s_Disp = String.Format("{0}: {1}", (eCoord)i, mi_Axis[i].LegendText); - i_Graph.DrawString(s_Disp, Font, mi_Axis[i].LegendBrush, X, Y); - Y -= Font.Height; - } - } + String s_Disp = String.Format("{0}: {1}", (eCoord)i, mi_Axis[i].LegendText); + i_Graph.DrawString(s_Disp, Font, mi_Axis[i].LegendBrush, X, Y); + Y -= Font.Height; + } + } - // Draw rotation legends at top - if (mi_TopLegendBrush != null) - { - String[] s_Legend = new String[] { "Rotation:", "Elevation:", "Distance:" }; - String[] s_Value = new String[] { String.Format("{0:+#;-#;0}°", (int)mi_Mouse.md_Phi), - String.Format("{0:+#;-#;0}°", (int)mi_Mouse.md_Theta), - String.Format("{0}", (int)mi_Mouse.md_Rho) }; + // Draw rotation legends at top + if (mi_TopLegendBrush != null) + { + String[] s_Legend = new String[] { "Rotation:", "Elevation:", "Distance:" }; + String[] s_Value = new String[] { String.Format("{0:+#;-#;0}°", (int)mi_Mouse.md_Phi), + String.Format("{0:+#;-#;0}°", (int)mi_Mouse.md_Theta), + String.Format("{0}", (int)mi_Mouse.md_Rho) }; - SizeF k_Size = i_Graph.MeasureString(s_Legend[1], Font); // measure the widest string - int X = 4; - int Y = 3; - for (int i=0; i<3; i++) - { - i_Graph.DrawString(s_Legend[i], Font, mi_TopLegendBrush, X, Y); - i_Graph.DrawString(s_Value [i], Font, mi_TopLegendBrush, X + k_Size.Width, Y); - Y += Font.Height; - } - } + SizeF k_Size = i_Graph.MeasureString(s_Legend[1], Font); // measure the widest string + int X = 4; + int Y = 3; + for (int i = 0; i < 3; i++) + { + i_Graph.DrawString(s_Legend[i], Font, mi_TopLegendBrush, X, Y); + i_Graph.DrawString(s_Value[i], Font, mi_TopLegendBrush, X + k_Size.Width, Y); + Y += Font.Height; + } + } - // --------------------------------------------------- + // --------------------------------------------------- - // Set X, Y offset which user has set by mouse dragging with SHIFT key pressed - i_Graph.TranslateTransform(mi_Mouse.mk_OffMove.X + mi_Mouse.mk_OffCoord.X, - mi_Mouse.mk_OffMove.Y + mi_Mouse.mk_OffCoord.Y); + // Set X, Y offset which user has set by mouse dragging with SHIFT key pressed + i_Graph.TranslateTransform(mi_Mouse.mk_OffMove.X + mi_Mouse.mk_OffCoord.X, + mi_Mouse.mk_OffMove.Y + mi_Mouse.mk_OffCoord.Y); - SmoothingMode e_Smooth = SmoothingMode.Invalid; + SmoothingMode e_Smooth = SmoothingMode.Invalid; - foreach (cDrawObj i_DrawObj in mi_AllObjects) - { - if (!i_DrawObj.IsValid) - continue; // avoid overflow exception or hanging + foreach (cDrawObj i_DrawObj in mi_AllObjects) + { + if (!i_DrawObj.IsValid) + continue; // avoid overflow exception or hanging - if (e_Smooth != i_DrawObj.me_SmoothMode) // avoid unneccessary calls into GDI+ (speed optimization) - { - e_Smooth = i_DrawObj.me_SmoothMode; - i_Graph.SmoothingMode = i_DrawObj.me_SmoothMode; - } + if (e_Smooth != i_DrawObj.me_SmoothMode) // avoid unneccessary calls into GDI+ (speed optimization) + { + e_Smooth = i_DrawObj.me_SmoothMode; + i_Graph.SmoothingMode = i_DrawObj.me_SmoothMode; + } - // Draw Line, Shape, Polygon - i_DrawObj.Render(i_Graph); + // Draw Line, Shape, Polygon + i_DrawObj.Render(i_Graph); - // Draw labels and legends - cLine i_Line = i_DrawObj as cLine; - if (i_Line != null && - i_Line.me_Line != eCoord.Invalid && - mi_Quadrant.mb_BottomView == false && // no label in bottom view - mi_Quadrant.ms32_Quadrant == 3) // showing labels makes sense only in quadrant 3 - { - bool b_Legend = false; + // Draw labels and legends + cLine i_Line = i_DrawObj as cLine; + if (i_Line != null && + i_Line.me_Line != eCoord.Invalid && + mi_Quadrant.mb_BottomView == false && // no label in bottom view + mi_Quadrant.ms32_Quadrant == 3) // showing labels makes sense only in quadrant 3 + { + bool b_Legend = false; - // Draw axis legends at end of of main axis - if (me_LegendPos == eLegendPos.AxisEnd && i_Line.me_Line == i_Line.me_Offset) - { - cAxis i_Axis = mi_Axis[(int)i_Line.me_Line]; - if (!String.IsNullOrEmpty(i_Axis.LegendText)) - { - StringFormat i_Align = new StringFormat(); - PointF k_Pos = i_Line.mi_Points[1].mi_P2D.Coord; - switch (i_Line.me_Line) - { - case eCoord.X: - k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); - k_Pos.Y += (float)mi_Transform.ProjectXY(5, -Font.Height / 2 - 2); - i_Align.Alignment = StringAlignment.Far; - break; - case eCoord.Y: - k_Pos.X += 5; - k_Pos.Y -= Font.Height / 2; - break; - case eCoord.Z: - k_Pos.X -= 5; - k_Pos.Y -= Font.Height + 5; - break; - } + // Draw axis legends at end of of main axis + if (me_LegendPos == eLegendPos.AxisEnd && i_Line.me_Line == i_Line.me_Offset) + { + cAxis i_Axis = mi_Axis[(int)i_Line.me_Line]; + if (!String.IsNullOrEmpty(i_Axis.LegendText)) + { + StringFormat i_Align = new StringFormat(); + PointF k_Pos = i_Line.mi_Points[1].mi_P2D.Coord; + switch (i_Line.me_Line) + { + case eCoord.X: + k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); + k_Pos.Y += (float)mi_Transform.ProjectXY(5, -Font.Height / 2 - 2); + i_Align.Alignment = StringAlignment.Far; + break; - i_Graph.DrawString(i_Axis.LegendText, Font, i_Axis.LegendBrush, k_Pos, i_Align); - b_Legend = true; // do not draw a label if a legend has already been drawn (see Demo Sphere) - } - } - - // Draw labels of raster lines - if (me_Raster == eRaster.Labels && !b_Legend && !String.IsNullOrEmpty(i_Line.ms_Label)) - { - Brush i_Brush = null; - StringFormat i_Align = new StringFormat(); - PointF k_Pos = i_Line.mi_Points[1].mi_P2D.Coord; + case eCoord.Y: + k_Pos.X += 5; + k_Pos.Y -= Font.Height / 2; + break; - if (i_Line.me_Line == eCoord.Y) - { - if (i_Line.me_Offset == eCoord.X) - { - k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); - k_Pos.Y += (float)mi_Transform.ProjectXY(-Font.Height / 2, 5); - i_Brush = AxisX.LegendBrush; - } - else // Y (Main axis) and Z (Raster) - { - k_Pos.X += 5; - k_Pos.Y -= Font.Height / 2; - i_Brush = AxisZ.LegendBrush; - } - } - else if (i_Line.me_Line == eCoord.X && i_Line.me_Offset == eCoord.Y) - { - k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); - k_Pos.Y += (float)mi_Transform.ProjectXY(5, -Font.Height / 2); - i_Align.Alignment = StringAlignment.Far; - i_Brush = AxisY.LegendBrush; - } + case eCoord.Z: + k_Pos.X -= 5; + k_Pos.Y -= Font.Height + 5; + break; + } - if (i_Brush != null) - i_Graph.DrawString(i_Line.ms_Label, Font, i_Brush, k_Pos, i_Align); - } - } // if (Line != null) - } // foreach (cDrawObj) + i_Graph.DrawString(i_Axis.LegendText, Font, i_Axis.LegendBrush, k_Pos, i_Align); + b_Legend = true; // do not draw a label if a legend has already been drawn (see Demo Sphere) + } + } - #if DEBUG_SPEED + // Draw labels of raster lines + if (me_Raster == eRaster.Labels && !b_Legend && !String.IsNullOrEmpty(i_Line.ms_Label)) + { + Brush i_Brush = null; + StringFormat i_Align = new StringFormat(); + PointF k_Pos = i_Line.mi_Points[1].mi_P2D.Coord; + + if (i_Line.me_Line == eCoord.Y) + { + if (i_Line.me_Offset == eCoord.X) + { + k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); + k_Pos.Y += (float)mi_Transform.ProjectXY(-Font.Height / 2, 5); + i_Brush = AxisX.LegendBrush; + } + else // Y (Main axis) and Z (Raster) + { + k_Pos.X += 5; + k_Pos.Y -= Font.Height / 2; + i_Brush = AxisZ.LegendBrush; + } + } + else if (i_Line.me_Line == eCoord.X && i_Line.me_Offset == eCoord.Y) + { + k_Pos.X += (float)mi_Transform.ProjectXY(5, -5); + k_Pos.Y += (float)mi_Transform.ProjectXY(5, -Font.Height / 2); + i_Align.Alignment = StringAlignment.Far; + i_Brush = AxisY.LegendBrush; + } + + if (i_Brush != null) + i_Graph.DrawString(i_Line.ms_Label, Font, i_Brush, k_Pos, i_Align); + } + } // if (Line != null) + } // foreach (cDrawObj) + +#if DEBUG_SPEED FormatStopwatch("Render Objects: ", i_Watch, i_Debug); Debug.Print(i_Debug.ToString().TrimEnd()); - #endif - } +#endif + } - // ============================================================================ + // ============================================================================ - protected override void OnCreateControl() - { - base.OnCreateControl(); + protected override void OnCreateControl() + { + base.OnCreateControl(); - // This control draws it's own border. See DrawBorder() - BorderStyle = BorderStyle.None; + // This control draws it's own border. See DrawBorder() + BorderStyle = BorderStyle.None; - // This is the size of the control defined in Visual Studio Form Designer - mi_Transform.SetInitialSize(ClientSize); - } + // This is the size of the control defined in Visual Studio Form Designer + mi_Transform.SetInitialSize(ClientSize); + } - protected override void OnSizeChanged(EventArgs e) - { - base.OnSizeChanged(e); + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); - // This may be called with an invalid size before the control is created! - mi_Transform.SetSize(ClientSize); - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - } + // This may be called with an invalid size before the control is created! + mi_Transform.SetSize(ClientSize); + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + } - /// - /// This is only called when the user moves the trackbar, not when TrackBar.Value is set programmatically. - /// - void OnTrackbarScroll(object sender, EventArgs e) - { - mi_Mouse.OnTrackBarScroll(); - mi_Transform.SetCoefficients(mi_Mouse); - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - } + /// + /// This is only called when the user moves the trackbar, not when TrackBar.Value is set programmatically. + /// + private void OnTrackbarScroll(object sender, EventArgs e) + { + mi_Mouse.OnTrackBarScroll(); + mi_Transform.SetCoefficients(mi_Mouse); + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + } - // -------------------------------------------- + // -------------------------------------------- - protected override void OnGotFocus(EventArgs e) - { - base.OnGotFocus(e); - DrawBorder(null); - } - protected override void OnLostFocus(EventArgs e) - { - base.OnLostFocus(e); - DrawBorder(null); - } - /// - /// Draw a one pixel border around the control which may change color when the control has the focus. - /// - private void DrawBorder(Graphics i_Graphics) - { - Pen i_Pen = mi_BorderPens[Focused ? 1 : 0]; - if (i_Pen != null) - { - BorderStyle = BorderStyle.None; + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + DrawBorder(null); + } - if (i_Graphics == null) - i_Graphics = Graphics.FromHwnd(Handle); + protected override void OnLostFocus(EventArgs e) + { + base.OnLostFocus(e); + DrawBorder(null); + } - i_Graphics.ResetTransform(); - Rectangle r_Rect = ClientRectangle; - i_Graphics.DrawRectangle(i_Pen, r_Rect.X, r_Rect.Y, r_Rect.Width - 1, r_Rect.Height - 1); - } - } + /// + /// Draw a one pixel border around the control which may change color when the control has + /// the focus. + /// + private void DrawBorder(Graphics i_Graphics) + { + Pen i_Pen = mi_BorderPens[Focused ? 1 : 0]; + if (i_Pen != null) + { + BorderStyle = BorderStyle.None; - // ============================== MOUSE ===================================== + if (i_Graphics == null) + i_Graphics = Graphics.FromHwnd(Handle); - protected override void OnMouseDown(MouseEventArgs e) - { - base.OnMouseDown(e); + i_Graphics.ResetTransform(); + Rectangle r_Rect = ClientRectangle; + i_Graphics.DrawRectangle(i_Pen, r_Rect.X, r_Rect.Y, r_Rect.Width - 1, r_Rect.Height - 1); + } + } - mi_Tooltip.Hide(); - mi_Mouse.mk_LastPos = e.Location; + // ============================== MOUSE ===================================== - if (mi_AllObjects.Count == 0) - return; - - int s32_UID = (int)Control.ModifierKeys | (int)e.Button; - - cUserInput i_Input; - if (mi_UserInputs.TryGetValue(s32_UID, out i_Input)) - { - switch (i_Input.Action) - { - case eMouseAction.SelectObj: - case eMouseAction.Callback: - OnSelMouseDown(e.X, e.Y, i_Input); - break; + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); - default: - mi_Mouse.me_Action = i_Input.Action; - Cursor = i_Input.Cursor; - break; - } - } - } + mi_Tooltip.Hide(); + mi_Mouse.mk_LastPos = e.Location; - protected override void OnMouseMove(MouseEventArgs e) - { - base.OnMouseMove(e); + if (mi_AllObjects.Count == 0) + return; - int s32_DeltaX = e.X - mi_Mouse.mk_LastPos.X; - int s32_DeltaY = e.Y - mi_Mouse.mk_LastPos.Y; - mi_Mouse.mk_LastPos = e.Location; + int s32_UID = (int)Control.ModifierKeys | (int)e.Button; - switch (mi_Mouse.me_Action) - { - case eMouseAction.Move: - mi_Tooltip.Hide(); - mi_Mouse.mk_OffMove.X += s32_DeltaX; - mi_Mouse.mk_OffMove.Y += s32_DeltaY; - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - break; + cUserInput i_Input; + if (mi_UserInputs.TryGetValue(s32_UID, out i_Input)) + { + switch (i_Input.Action) + { + case eMouseAction.SelectObj: + case eMouseAction.Callback: + OnSelMouseDown(e.X, e.Y, i_Input); + break; - case eMouseAction.Rho: - case eMouseAction.Theta: - case eMouseAction.Phi: - case eMouseAction.ThetaAndPhi: - mi_Tooltip.Hide(); - mi_Mouse.OnMouseMove(s32_DeltaX, s32_DeltaY); - mi_Transform.SetCoefficients(mi_Mouse); - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - break; + default: + mi_Mouse.me_Action = i_Input.Action; + Cursor = i_Input.Cursor; + break; + } + } + } - case eMouseAction.SelectObj: - case eMouseAction.Callback: - int s32_UID = (int)Control.ModifierKeys | (int)e.Button; - cUserInput i_Input; - if (mi_UserInputs.TryGetValue(s32_UID, out i_Input)) - { - if (i_Input.Action == mi_Mouse.me_Action) - { - // Mouse.Y coordinates have the zero point at top left - // Editor3D coordinates have the zero point at bottom left --> negate Y - SelectionCallback(eSelEvent.MouseDrag, i_Input.Modifiers, s32_DeltaX, -s32_DeltaY, mi_DragObject); - } - else - { - // The modifier keys have changed --> abort sending events to the callback - OnMouseExit(); - } - } - break; + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); - case eMouseAction.None: - mi_Tooltip.OnMouseMove(e); - break; - } - } + int s32_DeltaX = e.X - mi_Mouse.mk_LastPos.X; + int s32_DeltaY = e.Y - mi_Mouse.mk_LastPos.Y; + mi_Mouse.mk_LastPos = e.Location; - protected override void OnMouseUp(MouseEventArgs e) - { - base.OnMouseUp(e); - OnMouseExit(); - } - protected override void OnMouseLeave(EventArgs e) - { - base.OnMouseLeave(e); - OnMouseExit(); - } - private void OnMouseExit() - { - mi_Tooltip.Hide(); - Cursor = Cursors.Arrow; + switch (mi_Mouse.me_Action) + { + case eMouseAction.Move: + mi_Tooltip.Hide(); + mi_Mouse.mk_OffMove.X += s32_DeltaX; + mi_Mouse.mk_OffMove.Y += s32_DeltaY; + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + break; - switch (mi_Mouse.me_Action) - { - case eMouseAction.SelectObj: - case eMouseAction.Callback: - SelectionCallback(eSelEvent.MouseUp, Keys.None, 0, 0, mi_DragObject); - break; - } + case eMouseAction.Rho: + case eMouseAction.Theta: + case eMouseAction.Phi: + case eMouseAction.ThetaAndPhi: + mi_Tooltip.Hide(); + mi_Mouse.OnMouseMove(s32_DeltaX, s32_DeltaY); + mi_Transform.SetCoefficients(mi_Mouse); + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + break; - mi_DragObject = null; - mi_Mouse.me_Action = eMouseAction.None; + case eMouseAction.SelectObj: + case eMouseAction.Callback: + int s32_UID = (int)Control.ModifierKeys | (int)e.Button; + cUserInput i_Input; + if (mi_UserInputs.TryGetValue(s32_UID, out i_Input)) + { + if (i_Input.Action == mi_Mouse.me_Action) + { + // Mouse.Y coordinates have the zero point at top left Editor3D + // coordinates have the zero point at bottom left --> negate Y + SelectionCallback(eSelEvent.MouseDrag, i_Input.Modifiers, s32_DeltaX, -s32_DeltaY, mi_DragObject); + } + else + { + // The modifier keys have changed --> abort sending events to the callback + OnMouseExit(); + } + } + break; - // Store any pending user changes on mouse-up - mi_UndoBuffer.Store(); - } + case eMouseAction.None: + mi_Tooltip.OnMouseMove(e); + break; + } + } - protected override void OnMouseWheel(MouseEventArgs e) - { - base.OnMouseWheel(e); - mi_Tooltip.Hide(); + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + OnMouseExit(); + } - if (mi_Mouse.OnMouseWheel(e.Delta)) - { - mi_Transform.SetCoefficients(mi_Mouse); - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - } - } + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + OnMouseExit(); + } - /// - /// Select 3D object or call selection callback function - /// - private void OnSelMouseDown(int X, int Y, cUserInput i_Input) - { - if (!mi_Selection.Enabled) - return; + private void OnMouseExit() + { + mi_Tooltip.Hide(); + Cursor = Cursors.Arrow; - cObject3D i_Found = FindObjectAt(X, Y, true); + switch (mi_Mouse.me_Action) + { + case eMouseAction.SelectObj: + case eMouseAction.Callback: + SelectionCallback(eSelEvent.MouseUp, Keys.None, 0, 0, mi_DragObject); + break; + } - if (mi_Selection.Callback != null) - { - // Start dragging even if i_Found == null - mi_DragObject = i_Found; - mi_Mouse.me_Action = i_Input.Action; - Cursor = i_Input.Cursor; + mi_DragObject = null; + mi_Mouse.me_Action = eMouseAction.None; - SelectionCallback(eSelEvent.MouseDown, i_Input.Modifiers, 0, 0, mi_DragObject); - return; - } + // Store any pending user changes on mouse-up + mi_UndoBuffer.Store(); + } - // No callback assigned --> toggle selection of 3D object. - if (i_Found != null && i_Input.Action == eMouseAction.SelectObj) - { - // Not multiselect --> remove all current selections - if (!mi_Selection.MultiSelect) - mi_Selection.DeSelectAll(); + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + mi_Tooltip.Hide(); - i_Found.Selected = !i_Found.Selected; // toggle selection + if (mi_Mouse.OnMouseWheel(e.Delta)) + { + mi_Transform.SetCoefficients(mi_Mouse); + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + } + } - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - } - } + /// + /// Select 3D object or call selection callback function + /// + private void OnSelMouseDown(int X, int Y, cUserInput i_Input) + { + if (!mi_Selection.Enabled) + return; - // ============================== KEYBOARD ===================================== + cObject3D i_Found = FindObjectAt(X, Y, true); - protected override void OnKeyDown(KeyEventArgs e) - { - base.OnKeyDown(e); + if (mi_Selection.Callback != null) + { + // Start dragging even if i_Found == null + mi_DragObject = i_Found; + mi_Mouse.me_Action = i_Input.Action; + Cursor = i_Input.Cursor; - if (e.Control && !e.Alt && !e.Shift) - { - switch (e.KeyCode) - { - case Keys.Y: mi_UndoBuffer.Redo(); break; // CTRL + Y --> Redo - case Keys.Z: mi_UndoBuffer.Undo(); break; // CTRL + Z --> Undo - } - } - } + SelectionCallback(eSelEvent.MouseDown, i_Input.Modifiers, 0, 0, mi_DragObject); + return; + } - // ========================== SELECTION CALLBACK =============================== + // No callback assigned --> toggle selection of 3D object. + if (i_Found != null && i_Input.Action == eMouseAction.SelectObj) + { + // Not multiselect --> remove all current selections + if (!mi_Selection.MultiSelect) + mi_Selection.DeSelectAll(); - /// - /// Selection.Callback is called on the mouse events Down, Move and Up if cUserInput.Action = Callback or SelectObj - /// The callback must never throw an exception. - /// i_Object may be cPoint3D if Selection.SinglePoints = true - /// i_Object may be cShape3D, cLine3D, cPolygon3D if Selection.SinglePoints = false - /// i_Object may be null if the user has clicked a location without a 3D object. - /// In this case the callback can call Selection.GetSelectedObjects() / GetSelectedPoints() to obtain the previous selections. - /// The callback is responsible for selecting / deselecting the desired objects. - /// If the callback does not change the selection status, the 3D object will never be selected / deselected. - /// The callback is allowed to show a MessageBox to the user. - /// - private void SelectionCallback(eSelEvent e_Event, Keys e_Modifiers, int s32_DeltaX, int s32_DeltaY, cObject3D i_Object) - { - if (!mi_Selection.Enabled || mi_Selection.Callback == null) - return; - try - { - eInvalidate e_Invalidate = mi_Selection.Callback(e_Event, e_Modifiers, s32_DeltaX, s32_DeltaY, i_Object); + i_Found.Selected = !i_Found.Selected; // toggle selection - if (e_Invalidate == eInvalidate.CoordSystem) - me_Recalculate |= eRecalculate.CoordSystem; + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + } + } - if (e_Invalidate != eInvalidate.NoChange) - Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. - } - catch (Exception Ex) - { - MessageBox.Show(TopLevelControl, "Your callback function has crashed:\n\n" + Ex.Message + "\n\n" + Ex.StackTrace, - "Bug Alarm", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } + // ============================== KEYBOARD ===================================== + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (e.Control && !e.Alt && !e.Shift) + { + switch (e.KeyCode) + { + case Keys.Y: mi_UndoBuffer.Redo(); break; // CTRL + Y --> Redo + case Keys.Z: mi_UndoBuffer.Undo(); break; // CTRL + Z --> Undo + } + } + } + + // ========================== SELECTION CALLBACK =============================== + + /// + /// Selection.Callback is called on the mouse events Down, Move and Up if cUserInput.Action + /// = Callback or SelectObj The callback must never throw an exception. i_Object may be + /// cPoint3D if Selection.SinglePoints = true i_Object may be cShape3D, cLine3D, cPolygon3D + /// if Selection.SinglePoints = false i_Object may be null if the user has clicked a + /// location without a 3D object. In this case the callback can call + /// Selection.GetSelectedObjects() / GetSelectedPoints() to obtain the previous selections. + /// The callback is responsible for selecting / deselecting the desired objects. If the + /// callback does not change the selection status, the 3D object will never be selected / + /// deselected. The callback is allowed to show a MessageBox to the user. + /// + private void SelectionCallback(eSelEvent e_Event, Keys e_Modifiers, int s32_DeltaX, int s32_DeltaY, cObject3D i_Object) + { + if (!mi_Selection.Enabled || mi_Selection.Callback == null) + return; + try + { + eInvalidate e_Invalidate = mi_Selection.Callback(e_Event, e_Modifiers, s32_DeltaX, s32_DeltaY, i_Object); + + if (e_Invalidate == eInvalidate.CoordSystem) + me_Recalculate |= eRecalculate.CoordSystem; + + if (e_Invalidate != eInvalidate.NoChange) + Invalidate(); // Windows will call OnPaint() when the GUI thread becomes idle. + } + catch (Exception Ex) + { + MessageBox.Show(TopLevelControl, "Your callback function has crashed:\n\n" + Ex.Message + "\n\n" + Ex.StackTrace, + "Bug Alarm", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } } \ No newline at end of file diff --git a/HexcalMC/Base/Editor3D/FunctionCompiler.cs b/HexcalMC/Base/Editor3D/FunctionCompiler.cs index 7381fc5..0578537 100644 --- a/HexcalMC/Base/Editor3D/FunctionCompiler.cs +++ b/HexcalMC/Base/Editor3D/FunctionCompiler.cs @@ -1,115 +1,112 @@ - -/***************************************************************************** +/***************************************************************************** This class has been written by Łukasz Światkowski Cleaned up spaghetti code and improved by Elmü (elmue@gmx.de) *****************************************************************************/ +using Microsoft.CSharp; using System; using System.CodeDom.Compiler; using System.Reflection; -using System.Runtime.Serialization; using System.Text; -using Microsoft.CSharp; - using delRendererFunction = Plot3D.Editor3D.delRendererFunction; namespace Plot3D { - public delegate double delCompiledFunction(params double[] x); + public delegate double delCompiledFunction(params double[] x); - public static class FunctionCompiler - { - const String EVAL_CLASS = - "using {1};\n" - + "public class Eval\n" - + "{{\n" - + " public static double e {{ get {{ return System.Math.E; }} }}\n" - + " public static double pi {{ get {{ return System.Math.PI; }} }}\n" - // ------------------------------------------------------- - + " public static double abs (double x) {{ return System.Math.Abs(x); }}\n" - + " public static double acos (double x) {{ return System.Math.Acos(x); }}\n" - + " public static double asin (double x) {{ return System.Math.Asin(x); }}\n" - + " public static double atan (double x) {{ return System.Math.Atan(x); }}\n" - + " public static double atan2(double x, double y) {{ return System.Math.Atan2(x, y); }}\n" - + " public static double ceil (double x) {{ return System.Math.Ceiling(x); }}\n" - + " public static double cos (double x) {{ return System.Math.Cos(x); }}\n" - + " public static double cosh (double x) {{ return System.Math.Cosh(x); }}\n" - + " public static double exp (double x) {{ return System.Math.Exp(x); }}\n" - + " public static double floor(double x) {{ return System.Math.Floor(x); }}\n" - + " public static double log (double x) {{ return System.Math.Log(x); }}\n" - + " public static double log2 (double x) {{ return System.Math.Log(x, 2.0); }}\n" - + " public static double log10(double x) {{ return System.Math.Log10(x); }}\n" - + " public static double max (double x, double y) {{ return System.Math.Max(x, y); }}\n" - + " public static double min (double x, double y) {{ return System.Math.Min(x, y); }}\n" - + " public static double pow (double x, double y) {{ return System.Math.Pow(x, y); }}\n" - + " public static double round(double x) {{ return System.Math.Round(x); }}\n" - + " public static double sign (double x) {{ return System.Math.Sign(x); }}\n" - + " public static double sin (double x) {{ return System.Math.Sin(x); }}\n" - + " public static double sinh (double x) {{ return System.Math.Sinh(x); }}\n" - + " public static double sqrt (double x) {{ return System.Math.Sqrt(x); }}\n" - + " public static double tan (double x) {{ return System.Math.Tan(x); }}\n" - + " public static double tanh (double x) {{ return System.Math.Tanh(x); }}\n" - // ------------------------------------------------------- - + " public static double __eval(params double[] __X)\n" - + " {{\n" - + " double x = __X[0];\n" - + " double y = __X[1];\n" - + " return {0};\n" - + " }}\n" - + " public static {2} __get()\n" - + " {{\n" - + " return __eval;\n" - + " }}\n" - + "}}"; + public static class FunctionCompiler + { + private const String EVAL_CLASS = + "using {1};\n" + + "public class Eval\n" + + "{{\n" + + " public static double e {{ get {{ return System.Math.E; }} }}\n" + + " public static double pi {{ get {{ return System.Math.PI; }} }}\n" + // ------------------------------------------------------- + + " public static double abs (double x) {{ return System.Math.Abs(x); }}\n" + + " public static double acos (double x) {{ return System.Math.Acos(x); }}\n" + + " public static double asin (double x) {{ return System.Math.Asin(x); }}\n" + + " public static double atan (double x) {{ return System.Math.Atan(x); }}\n" + + " public static double atan2(double x, double y) {{ return System.Math.Atan2(x, y); }}\n" + + " public static double ceil (double x) {{ return System.Math.Ceiling(x); }}\n" + + " public static double cos (double x) {{ return System.Math.Cos(x); }}\n" + + " public static double cosh (double x) {{ return System.Math.Cosh(x); }}\n" + + " public static double exp (double x) {{ return System.Math.Exp(x); }}\n" + + " public static double floor(double x) {{ return System.Math.Floor(x); }}\n" + + " public static double log (double x) {{ return System.Math.Log(x); }}\n" + + " public static double log2 (double x) {{ return System.Math.Log(x, 2.0); }}\n" + + " public static double log10(double x) {{ return System.Math.Log10(x); }}\n" + + " public static double max (double x, double y) {{ return System.Math.Max(x, y); }}\n" + + " public static double min (double x, double y) {{ return System.Math.Min(x, y); }}\n" + + " public static double pow (double x, double y) {{ return System.Math.Pow(x, y); }}\n" + + " public static double round(double x) {{ return System.Math.Round(x); }}\n" + + " public static double sign (double x) {{ return System.Math.Sign(x); }}\n" + + " public static double sin (double x) {{ return System.Math.Sin(x); }}\n" + + " public static double sinh (double x) {{ return System.Math.Sinh(x); }}\n" + + " public static double sqrt (double x) {{ return System.Math.Sqrt(x); }}\n" + + " public static double tan (double x) {{ return System.Math.Tan(x); }}\n" + + " public static double tanh (double x) {{ return System.Math.Tanh(x); }}\n" + // ------------------------------------------------------- + + " public static double __eval(params double[] __X)\n" + + " {{\n" + + " double x = __X[0];\n" + + " double y = __X[1];\n" + + " return {0};\n" + + " }}\n" + + " public static {2} __get()\n" + + " {{\n" + + " return __eval;\n" + + " }}\n" + + "}}"; - public static delRendererFunction Compile(string functionBody) - { - functionBody = functionBody.Trim().ToLower(); - if (functionBody.Contains(";")) - throw new Exception("Function string cannot contain semicolon"); + public static delRendererFunction Compile(string functionBody) + { + functionBody = functionBody.Trim().ToLower(); + if (functionBody.Contains(";")) + throw new Exception("Function string cannot contain semicolon"); - string s_Class = string.Format(EVAL_CLASS, functionBody, typeof(delCompiledFunction).Namespace, typeof(delCompiledFunction).Name); + string s_Class = string.Format(EVAL_CLASS, functionBody, typeof(delCompiledFunction).Namespace, typeof(delCompiledFunction).Name); - CSharpCodeProvider i_Provider = new CSharpCodeProvider(); - CompilerParameters i_Params = new CompilerParameters(); - i_Params.CompilerOptions = "/t:library"; - i_Params.GenerateInMemory = true; - i_Params.ReferencedAssemblies.Add("mscorlib.dll"); - i_Params.ReferencedAssemblies.Add("System.dll"); - i_Params.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); + CSharpCodeProvider i_Provider = new CSharpCodeProvider(); + CompilerParameters i_Params = new CompilerParameters(); + i_Params.CompilerOptions = "/t:library"; + i_Params.GenerateInMemory = true; + i_Params.ReferencedAssemblies.Add("mscorlib.dll"); + i_Params.ReferencedAssemblies.Add("System.dll"); + i_Params.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); - CompilerResults i_Result = i_Provider.CompileAssemblyFromSource(i_Params, s_Class); - if (i_Result.Errors.HasErrors) - { - StringBuilder s_Error = new StringBuilder(); - if (i_Result.Errors.Count == 1) - s_Error.Append("Compilation error:\n"); - else - s_Error.AppendFormat("{0} Compilation errors:\n", i_Result.Errors.Count); + CompilerResults i_Result = i_Provider.CompileAssemblyFromSource(i_Params, s_Class); + if (i_Result.Errors.HasErrors) + { + StringBuilder s_Error = new StringBuilder(); + if (i_Result.Errors.Count == 1) + s_Error.Append("Compilation error:\n"); + else + s_Error.AppendFormat("{0} Compilation errors:\n", i_Result.Errors.Count); - foreach (CompilerError i_Error in i_Result.Errors) - { - s_Error.Append(i_Error.ErrorText); - s_Error.Append("\n"); - } + foreach (CompilerError i_Error in i_Result.Errors) + { + s_Error.Append(i_Error.ErrorText); + s_Error.Append("\n"); + } - s_Error.Append("\nSupported math functions are:\n" - + "e, pi, abs(), acos(), asin(), atan(), atan2(), ceil(), cos(), cosh(), " - + "exp(), floor(), log(), log2(), log10(), max(), min(), pow(), " - + "round(), sign(), sin(), sinh(), sqrt(), tan(), tanh()\n"); + s_Error.Append("\nSupported math functions are:\n" + + "e, pi, abs(), acos(), asin(), atan(), atan2(), ceil(), cos(), cosh(), " + + "exp(), floor(), log(), log2(), log10(), max(), min(), pow(), " + + "round(), sign(), sin(), sinh(), sqrt(), tan(), tanh()\n"); - throw new Exception(s_Error.ToString()); - } + throw new Exception(s_Error.ToString()); + } - MethodInfo i_Method = i_Result.CompiledAssembly.GetType("Eval").GetMethod("__get"); - delCompiledFunction f_Compiled = (delCompiledFunction)i_Method.Invoke(null, null); - delRendererFunction f_Render = delegate(double X, double Y) - { - return f_Compiled(X, Y); - }; - return f_Render; - } - } -} + MethodInfo i_Method = i_Result.CompiledAssembly.GetType("Eval").GetMethod("__get"); + delCompiledFunction f_Compiled = (delCompiledFunction)i_Method.Invoke(null, null); + delRendererFunction f_Render = delegate (double X, double Y) + { + return f_Compiled(X, Y); + }; + return f_Render; + } + } +} \ No newline at end of file diff --git a/HexcalMC/Base/Lamp.cs b/HexcalMC/Base/Lamp.cs index d06a06b..a44c58c 100644 --- a/HexcalMC/Base/Lamp.cs +++ b/HexcalMC/Base/Lamp.cs @@ -1,31 +1,24 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Drawing; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; using System.Drawing.Drawing2D; +using System.Windows.Forms; namespace HexcalMC { - public partial class Lamp : UserControl - { - public Lamp() - { - InitializeComponent(); - base.SetStyle(ControlStyles.ResizeRedraw, true); - this.DoubleBuffered = true; - } + public partial class Lamp : UserControl + { + public Lamp() + { + InitializeComponent(); + base.SetStyle(ControlStyles.ResizeRedraw, true); + this.DoubleBuffered = true; + } - private void Lamp_Load(object sender, EventArgs e) - { + private void Lamp_Load(object sender, EventArgs e) + { + } - } - - private static Color[] _colors = new Color[] + private static Color[] _colors = new Color[] { Color.Green, Color.Orange, @@ -37,147 +30,151 @@ namespace HexcalMC Color.Blue }; - private const int Ok = 0; - private const int Warn = 1; - private const int Error = 2; - private const int Debug = 4; - private const int Undef = -1; - private int _state = -1; + private const int Ok = 0; + private const int Warn = 1; + private const int Error = 2; + private const int Debug = 4; + private const int Undef = -1; + private int _state = -1; + private string _dtext = ""; + private bool _shadow = false; - private string _dtext = ""; - private bool _shadow = false; + /// + /// 设置颜色 + /// + public int State + { + get + { + return this._state; + } + set + { + this._state = value; + this.Refresh(); + } + } - /// 设置颜色 - public int State - { - get - { - return this._state; - } - set - { - this._state = value; - this.Refresh(); - } - } + /// + /// 设置文字 + /// + public string LText + { + get + { + return this._dtext; + } + set + { + this._dtext = value; + } + } - /// 设置文字 - public string LText - { - get - { - return this._dtext; - } - set - { - this._dtext = value; - } - } + /// + /// 设置阴影 + /// + public bool Shadow + { + get + { + return this._shadow; + } + set + { + this._shadow = value; + } + } - /// 设置阴影 - public bool Shadow - { - get - { - return this._shadow; - } - set - { - this._shadow = value; - } - } + protected override void OnPaint(PaintEventArgs gr) + { + base.OnPaint(gr); + int num = Math.Min(base.Width, base.Height); + bool flag = num >= 8; + if (flag) + { + this.DrawLamp(gr.Graphics, num); + } + } - protected override void OnPaint(PaintEventArgs gr) - { - base.OnPaint(gr); - int num = Math.Min(base.Width, base.Height); - bool flag = num >= 8; - if (flag) - { - this.DrawLamp(gr.Graphics, num); - } - } + private void DrawLamp(Graphics g, int rad) + { + g.SmoothingMode = SmoothingMode.AntiAlias; + RectangleF rect = new RectangleF(1f, 1f, (float)(rad - 5), (float)(rad - 5)); + float num = rect.Width / 12f; + RectangleF rect2 = new RectangleF(rect.Location, rect.Size); + rect2.Inflate(1f, 1f); + if (_shadow) + { + rect2.X += num; + rect2.Y += num; + g.FillEllipse(Brushes.Gray, rect2); + rect2.X -= num; + rect2.Y -= num; + } + g.FillEllipse(Brushes.Black, rect2); - private void DrawLamp(Graphics g, int rad) - { - g.SmoothingMode = SmoothingMode.AntiAlias; - RectangleF rect = new RectangleF(1f, 1f, (float)(rad - 5), (float)(rad - 5)); - float num = rect.Width / 12f; - RectangleF rect2 = new RectangleF(rect.Location, rect.Size); - rect2.Inflate(1f, 1f); - if (_shadow) - { - rect2.X += num; - rect2.Y += num; - g.FillEllipse(Brushes.Gray, rect2); - rect2.X -= num; - rect2.Y -= num; - } - g.FillEllipse(Brushes.Black, rect2); - - if (_state != -1) - { - using (GraphicsPath graphicsPath = new GraphicsPath()) - { - graphicsPath.AddEllipse(rect); - int x = (int)(rect.X + rect.Width / 3f); - int y = (int)(rect.Y + rect.Height / 3f); - Color color = Lamp._colors[_state]; - using (PathGradientBrush pathGradientBrush = new PathGradientBrush(graphicsPath)) - { - pathGradientBrush.CenterColor = Color.Snow; - pathGradientBrush.CenterPoint = new Point(x, y); - pathGradientBrush.SurroundColors = new Color[] + if (_state != -1) + { + using (GraphicsPath graphicsPath = new GraphicsPath()) + { + graphicsPath.AddEllipse(rect); + int x = (int)(rect.X + rect.Width / 3f); + int y = (int)(rect.Y + rect.Height / 3f); + Color color = Lamp._colors[_state]; + using (PathGradientBrush pathGradientBrush = new PathGradientBrush(graphicsPath)) + { + pathGradientBrush.CenterColor = Color.Snow; + pathGradientBrush.CenterPoint = new Point(x, y); + pathGradientBrush.SurroundColors = new Color[] { color }; - g.FillPath(pathGradientBrush, graphicsPath); - } - } - } - else - { - using (Brush brush = new SolidBrush(this.BackColor)) - { - g.FillEllipse(brush, rect); - } - } + g.FillPath(pathGradientBrush, graphicsPath); + } + } + } + else + { + using (Brush brush = new SolidBrush(this.BackColor)) + { + g.FillEllipse(brush, rect); + } + } - if (_dtext.Length > 0) - { - float emSize = rect.Height / 6f; - Font font = new Font("Microsoft Sans Serif", emSize, FontStyle.Bold, GraphicsUnit.Point, 0); - int num2 = (int)font.GetHeight(); - int y2 = (int)rect.Height / 2 - num2 / 2; - int num3 = (int)g.MeasureString(_dtext, font).Width; - int num4 = (int)(rect.Width - (float)num3) / 2; - bool flag3 = num4 < 0; - if (flag3) - { - num4 = 0; - } - g.DrawString(_dtext, font, Brushes.Black, new Point(num4, y2)); - } - } + if (_dtext.Length > 0) + { + float emSize = rect.Height / 6f; + Font font = new Font("Microsoft Sans Serif", emSize, FontStyle.Bold, GraphicsUnit.Point, 0); + int num2 = (int)font.GetHeight(); + int y2 = (int)rect.Height / 2 - num2 / 2; + int num3 = (int)g.MeasureString(_dtext, font).Width; + int num4 = (int)(rect.Width - (float)num3) / 2; + bool flag3 = num4 < 0; + if (flag3) + { + num4 = 0; + } + g.DrawString(_dtext, font, Brushes.Black, new Point(num4, y2)); + } + } + } - } + public class LampColor + { + public const int Ok = 0; + public const int Warn = 1; + public const int Error = 2; + public const int Debug = 4; + public const int Undef = -1; - public class LampColor - { - public const int Ok = 0; - public const int Warn = 1; - public const int Error = 2; - public const int Debug = 4; - public const int Undef = -1; - - public const int Green = 0; - public const int Orange = 1; - public const int Red = 2; - public const int White = 3; - public const int Silver = 4; - public const int Transparent = 5; - public const int Yellow = 6; - public const int Blue = 7; - } -} + public const int Green = 0; + public const int Orange = 1; + public const int Red = 2; + public const int White = 3; + public const int Silver = 4; + public const int Transparent = 5; + public const int Yellow = 6; + public const int Blue = 7; + } +} \ No newline at end of file diff --git a/HexcalMC/Base/Scatter/Algebra.cs b/HexcalMC/Base/Scatter/Algebra.cs index f89e459..294d507 100644 --- a/HexcalMC/Base/Scatter/Algebra.cs +++ b/HexcalMC/Base/Scatter/Algebra.cs @@ -1,105 +1,100 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Scatter { - static class Algerbra - { - public class Matrix - { - int rows; - int columns; + internal static class Algerbra + { + public class Matrix + { + private int rows; + private int columns; - private T[,] matrix; + private T[,] matrix; - public Matrix(int n, int m) - { - matrix = new T[n, m]; - rows = n; - columns = m; - } + public Matrix(int n, int m) + { + matrix = new T[n, m]; + rows = n; + columns = m; + } - public void SetValByIdx(int m, int n, T x) - { - matrix[n, m] = x; - } + public void SetValByIdx(int m, int n, T x) + { + matrix[n, m] = x; + } - public T GetValByIndex(int n, int m) - { - return matrix[n, m]; - } + public T GetValByIndex(int n, int m) + { + return matrix[n, m]; + } - public void SetMatrix(T[] arr) - { - for (int r = 0; r < rows; r++) - for (int c = 0; c < columns; c++) - matrix[r, c] = arr[r * columns + c]; - } + public void SetMatrix(T[] arr) + { + for (int r = 0; r < rows; r++) + for (int c = 0; c < columns; c++) + matrix[r, c] = arr[r * columns + c]; + } - public static Matrix operator |(Matrix m1, Matrix m2) - { - Matrix m = new Matrix(m1.rows, m1.columns + m2.columns); - for (int r = 0; r < m1.rows; r++) - { - for (int c = 0; c < m1.columns; c++) - m.matrix[r, c] = m1.matrix[r, c]; - for (int c = 0; c < m2.columns; c++) - m.matrix[r, c + m1.columns] = m2.matrix[r, c]; - } - return m; - } + public static Matrix operator |(Matrix m1, Matrix m2) + { + Matrix m = new Matrix(m1.rows, m1.columns + m2.columns); + for (int r = 0; r < m1.rows; r++) + { + for (int c = 0; c < m1.columns; c++) + m.matrix[r, c] = m1.matrix[r, c]; + for (int c = 0; c < m2.columns; c++) + m.matrix[r, c + m1.columns] = m2.matrix[r, c]; + } + return m; + } - public static Matrix operator *(Matrix m1, Matrix m2) - { - Matrix m = new Matrix(m1.rows, m2.columns); - for (int r = 0; r < m.rows; r++) - for (int c = 0; c < m.columns; c++) - { - T tmp = (dynamic)0; - for (int i = 0; i < m2.rows; i++) - tmp += (dynamic)m1.matrix[r, i] * (dynamic)m2.matrix[i, c]; - m.matrix[r, c] = tmp; - } - return m; - } + public static Matrix operator *(Matrix m1, Matrix m2) + { + Matrix m = new Matrix(m1.rows, m2.columns); + for (int r = 0; r < m.rows; r++) + for (int c = 0; c < m.columns; c++) + { + T tmp = (dynamic)0; + for (int i = 0; i < m2.rows; i++) + tmp += (dynamic)m1.matrix[r, i] * (dynamic)m2.matrix[i, c]; + m.matrix[r, c] = tmp; + } + return m; + } - public static Matrix operator ~(Matrix m) - { - Matrix tmp = new Matrix(m.columns, m.rows); - for (int r = 0; r < m.rows; r++) - for (int c = 0; c < m.columns; c++) - tmp.matrix[c, r] = m.matrix[r, c]; - return tmp; - } + public static Matrix operator ~(Matrix m) + { + Matrix tmp = new Matrix(m.columns, m.rows); + for (int r = 0; r < m.rows; r++) + for (int c = 0; c < m.columns; c++) + tmp.matrix[c, r] = m.matrix[r, c]; + return tmp; + } - public static Matrix operator -(Matrix m) - { - Matrix tmp = new Matrix(m.columns, m.rows); - for (int r = 0; r < m.rows; r++) - for (int c = 0; c < m.columns; c++) - tmp.matrix[r, c] = -(dynamic)m.matrix[r, c]; - return tmp; - } + public static Matrix operator -(Matrix m) + { + Matrix tmp = new Matrix(m.columns, m.rows); + for (int r = 0; r < m.rows; r++) + for (int c = 0; c < m.columns; c++) + tmp.matrix[r, c] = -(dynamic)m.matrix[r, c]; + return tmp; + } - public override string ToString() - { - String output = ""; - for (int r = 0; r < rows; r++) - { - output += "[\t"; - for (int c = 0; c < columns; c++) - { - output += matrix[r, c].ToString(); - if (c < columns - 1) output += ",\t"; - } - output += "]\n"; - } - return output; - } - } - - - } -} + public override string ToString() + { + String output = ""; + for (int r = 0; r < rows; r++) + { + output += "[\t"; + for (int c = 0; c < columns; c++) + { + output += matrix[r, c].ToString(); + if (c < columns - 1) output += ",\t"; + } + output += "]\n"; + } + return output; + } + } + } +} \ No newline at end of file diff --git a/HexcalMC/Base/Scatter/MouseWheelHandler.cs b/HexcalMC/Base/Scatter/MouseWheelHandler.cs index 33576bf..18a26cf 100644 --- a/HexcalMC/Base/Scatter/MouseWheelHandler.cs +++ b/HexcalMC/Base/Scatter/MouseWheelHandler.cs @@ -1,68 +1,65 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows.Forms; using System.Drawing; +using System.Windows.Forms; namespace Scatter { - public static class MouseWheelHandler - { - public static void Add(Control ctrl, Action onMouseWheel) - { - if (ctrl == null || onMouseWheel == null) - throw new ArgumentNullException(); + public static class MouseWheelHandler + { + public static void Add(Control ctrl, Action onMouseWheel) + { + if (ctrl == null || onMouseWheel == null) + throw new ArgumentNullException(); - var filter = new MouseWheelMessageFilter(ctrl, onMouseWheel); - Application.AddMessageFilter(filter); - ctrl.Disposed += (s, e) => Application.RemoveMessageFilter(filter); - } + var filter = new MouseWheelMessageFilter(ctrl, onMouseWheel); + Application.AddMessageFilter(filter); + ctrl.Disposed += (s, e) => Application.RemoveMessageFilter(filter); + } - class MouseWheelMessageFilter - : IMessageFilter - { - private readonly Control _ctrl; - private readonly Action _onMouseWheel; + private class MouseWheelMessageFilter + : IMessageFilter + { + private readonly Control _ctrl; + private readonly Action _onMouseWheel; - public MouseWheelMessageFilter(Control ctrl, Action onMouseWheel) - { - _ctrl = ctrl; - _onMouseWheel = onMouseWheel; - } + public MouseWheelMessageFilter(Control ctrl, Action onMouseWheel) + { + _ctrl = ctrl; + _onMouseWheel = onMouseWheel; + } - public bool PreFilterMessage(ref Message m) - { - var parent = _ctrl.Parent; - if (parent != null && m.Msg == 0x20a) // WM_MOUSEWHEEL, find the control at screen position m.LParam - { - var pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); + public bool PreFilterMessage(ref Message m) + { + var parent = _ctrl.Parent; + if (parent != null && m.Msg == 0x20a) // WM_MOUSEWHEEL, find the control at screen position m.LParam + { + var pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); - var clientPos = _ctrl.PointToClient(pos); + var clientPos = _ctrl.PointToClient(pos); - if (_ctrl.ClientRectangle.Contains(clientPos) - && ReferenceEquals(_ctrl, parent.GetChildAtPoint(parent.PointToClient(pos)))) - { - var wParam = m.WParam.ToInt32(); - Func getButton = - (flag, button) => ((wParam & flag) == flag) ? button : MouseButtons.None; + if (_ctrl.ClientRectangle.Contains(clientPos) + && ReferenceEquals(_ctrl, parent.GetChildAtPoint(parent.PointToClient(pos)))) + { + var wParam = m.WParam.ToInt32(); + Func getButton = + (flag, button) => ((wParam & flag) == flag) ? button : MouseButtons.None; - var buttons = getButton(wParam & 0x0001, MouseButtons.Left) - | getButton(wParam & 0x0010, MouseButtons.Middle) - | getButton(wParam & 0x0002, MouseButtons.Right) - | getButton(wParam & 0x0020, MouseButtons.XButton1) - | getButton(wParam & 0x0040, MouseButtons.XButton2) - ; // Not matching for these /*MK_SHIFT=0x0004;MK_CONTROL=0x0008*/ + var buttons = getButton(wParam & 0x0001, MouseButtons.Left) + | getButton(wParam & 0x0010, MouseButtons.Middle) + | getButton(wParam & 0x0002, MouseButtons.Right) + | getButton(wParam & 0x0020, MouseButtons.XButton1) + | getButton(wParam & 0x0040, MouseButtons.XButton2) + ; // Not matching for these /*MK_SHIFT=0x0004;MK_CONTROL=0x0008*/ - var delta = wParam >> 16; - var e = new MouseEventArgs(buttons, 0, clientPos.X, clientPos.Y, delta); - _onMouseWheel(e); + var delta = wParam >> 16; + var e = new MouseEventArgs(buttons, 0, clientPos.X, clientPos.Y, delta); + _onMouseWheel(e); - return true; - } - } - return false; - } - } - } -} + return true; + } + } + return false; + } + } + } +} \ No newline at end of file diff --git a/HexcalMC/Base/Scatter/Projection.cs b/HexcalMC/Base/Scatter/Projection.cs index 3e0b166..ff49e9d 100644 --- a/HexcalMC/Base/Scatter/Projection.cs +++ b/HexcalMC/Base/Scatter/Projection.cs @@ -1,67 +1,64 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Drawing; -using System.Diagnostics; namespace Scatter { - static class Projection - { - static public PointF Project(double[] x, double s_x, double s_y, double f, double[] d_w, double azimuth, double elevation) - { - Algerbra.Matrix Mext = GetMext(azimuth, elevation, d_w); - Algerbra.Matrix Mint = GetMint(s_x, s_y, f); - Algerbra.Matrix X_h = new Algerbra.Matrix(4, 1); - X_h.SetMatrix(new double[] { x[0], x[1], x[2], 1.0}); - //Debug.Print((Mint * Mext).ToString()); - Algerbra.Matrix P = Mint * Mext * X_h; - return new PointF((float)(P.GetValByIndex(0, 0) / P.GetValByIndex(2, 0)), (float)(P.GetValByIndex(1, 0) / P.GetValByIndex(2, 0))); - } + internal static class Projection + { + public static PointF Project(double[] x, double s_x, double s_y, double f, double[] d_w, double azimuth, double elevation) + { + Algerbra.Matrix Mext = GetMext(azimuth, elevation, d_w); + Algerbra.Matrix Mint = GetMint(s_x, s_y, f); + Algerbra.Matrix X_h = new Algerbra.Matrix(4, 1); + X_h.SetMatrix(new double[] { x[0], x[1], x[2], 1.0 }); + //Debug.Print((Mint * Mext).ToString()); + Algerbra.Matrix P = Mint * Mext * X_h; + return new PointF((float)(P.GetValByIndex(0, 0) / P.GetValByIndex(2, 0)), (float)(P.GetValByIndex(1, 0) / P.GetValByIndex(2, 0))); + } - static public PointF[] ProjectVector(List x, double s_x, double s_y, double f, double[] d_w, double azimuth, double elevation) - { - Algerbra.Matrix Mext = GetMext(azimuth, elevation, d_w); - Algerbra.Matrix Mint = GetMint(s_x, s_y, f); - Algerbra.Matrix X_h = new Algerbra.Matrix(4, 1); + public static PointF[] ProjectVector(List x, double s_x, double s_y, double f, double[] d_w, double azimuth, double elevation) + { + Algerbra.Matrix Mext = GetMext(azimuth, elevation, d_w); + Algerbra.Matrix Mint = GetMint(s_x, s_y, f); + Algerbra.Matrix X_h = new Algerbra.Matrix(4, 1); - PointF[] Pvec = new PointF[x.Count]; - for (int i = 0; i < x.Count; i++) - { - X_h.SetMatrix(new double[] { x[i][0], x[i][1], x[i][2], 1.0 }); - Algerbra.Matrix P = Mint * Mext * X_h; - Pvec[i] = new PointF((float)(P.GetValByIndex(0, 0) / P.GetValByIndex(2, 0)), (float)(P.GetValByIndex(1, 0) / P.GetValByIndex(2, 0))); - } - return Pvec; - } + PointF[] Pvec = new PointF[x.Count]; + for (int i = 0; i < x.Count; i++) + { + X_h.SetMatrix(new double[] { x[i][0], x[i][1], x[i][2], 1.0 }); + Algerbra.Matrix P = Mint * Mext * X_h; + Pvec[i] = new PointF((float)(P.GetValByIndex(0, 0) / P.GetValByIndex(2, 0)), (float)(P.GetValByIndex(1, 0) / P.GetValByIndex(2, 0))); + } + return Pvec; + } - static Algerbra.Matrix GetMint(double s_x, double s_y, double f) - { - Algerbra.Matrix Mint = new Algerbra.Matrix(3, 3); - double o_x = s_x / 2; - double o_y = s_y / 2; - double a = 1; - Mint.SetMatrix(new double[] { f, 0, o_x, 0, f * a, o_y, 0, 0, 1 }); - return Mint; - } + private static Algerbra.Matrix GetMint(double s_x, double s_y, double f) + { + Algerbra.Matrix Mint = new Algerbra.Matrix(3, 3); + double o_x = s_x / 2; + double o_y = s_y / 2; + double a = 1; + Mint.SetMatrix(new double[] { f, 0, o_x, 0, f * a, o_y, 0, 0, 1 }); + return Mint; + } - static Algerbra.Matrix GetMext(double azimuth, double elevation, double[] d_w) - { - Algerbra.Matrix R = RotationMatrix(azimuth, elevation); - Algerbra.Matrix dw = new Algerbra.Matrix(3, 1); - dw.SetMatrix(d_w); - Algerbra.Matrix Mext = R | (-R * dw); - return Mext; - } + private static Algerbra.Matrix GetMext(double azimuth, double elevation, double[] d_w) + { + Algerbra.Matrix R = RotationMatrix(azimuth, elevation); + Algerbra.Matrix dw = new Algerbra.Matrix(3, 1); + dw.SetMatrix(d_w); + Algerbra.Matrix Mext = R | (-R * dw); + return Mext; + } - static Algerbra.Matrix RotationMatrix(double azimuth, double elevation) - { - Algerbra.Matrix R = new Algerbra.Matrix(3, 3); - R.SetMatrix(new double[] { Math.Cos(azimuth), 0, -Math.Sin(azimuth), - Math.Sin(azimuth)*Math.Sin(elevation), Math.Cos(elevation), Math.Cos(azimuth)*Math.Sin(elevation), - Math.Cos(elevation)*Math.Sin(azimuth), -Math.Sin(elevation), Math.Cos(azimuth)*Math.Cos(elevation) }); - return R; - } - } -} + private static Algerbra.Matrix RotationMatrix(double azimuth, double elevation) + { + Algerbra.Matrix R = new Algerbra.Matrix(3, 3); + R.SetMatrix(new double[] { Math.Cos(azimuth), 0, -Math.Sin(azimuth), + Math.Sin(azimuth)*Math.Sin(elevation), Math.Cos(elevation), Math.Cos(azimuth)*Math.Sin(elevation), + Math.Cos(elevation)*Math.Sin(azimuth), -Math.Sin(elevation), Math.Cos(azimuth)*Math.Cos(elevation) }); + return R; + } + } +} \ No newline at end of file diff --git a/HexcalMC/Base/Scatter/ScatterPlot.cs b/HexcalMC/Base/Scatter/ScatterPlot.cs index b018067..477aa98 100644 --- a/HexcalMC/Base/Scatter/ScatterPlot.cs +++ b/HexcalMC/Base/Scatter/ScatterPlot.cs @@ -1,179 +1,171 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Drawing; -using System.Data; -using System.Linq; -using System.Text; using System.Windows.Forms; -using System.Diagnostics; namespace Scatter { - public partial class ScatterPlot : UserControl - { - List> Points = new List>(); - List ProjPoints = new List(); - private double f = 1000; - private double d = 5; - private double[] d_w = new double[3]; - private double last_azimuth, azimuth = 0, last_elevation, elevation = 0; - private bool leftMousePressed = false; - private PointF ptMouseClick; + public partial class ScatterPlot : UserControl + { + private List> Points = new List>(); + private List ProjPoints = new List(); + private double f = 1000; + private double d = 5; + private double[] d_w = new double[3]; + private double last_azimuth, azimuth = 0, last_elevation, elevation = 0; + private bool leftMousePressed = false; + private PointF ptMouseClick; - public double Distance - { - get { return d; } - set { d = (value >= 0.1) ? d = value : d; UpdateProjection(); } - } + public double Distance + { + get { return d; } + set { d = (value >= 0.1) ? d = value : d; UpdateProjection(); } + } - public double F - { - get { return f; } - set { f = value; UpdateProjection(); } - } + public double F + { + get { return f; } + set { f = value; UpdateProjection(); } + } - public double[] CameraPos - { - get { return d_w;} - set { d_w = value; UpdateProjection(); } - } + public double[] CameraPos + { + get { return d_w; } + set { d_w = value; UpdateProjection(); } + } - public double Azimuth - { - get { return azimuth; } - set { azimuth = value; UpdateProjection(); } - } - - public double Elevation - { - get { return elevation; } - set { elevation = value; UpdateProjection(); } - } + public double Azimuth + { + get { return azimuth; } + set { azimuth = value; UpdateProjection(); } + } - public ScatterPlot() - { - InitializeComponent(); - MouseWheelHandler.Add(this, MyOnMouseWheel); - } + public double Elevation + { + get { return elevation; } + set { elevation = value; UpdateProjection(); } + } - protected override CreateParams CreateParams - { - get - { - var cp = base.CreateParams; - cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED - return cp; - } - } + public ScatterPlot() + { + InitializeComponent(); + MouseWheelHandler.Add(this, MyOnMouseWheel); + } - Color[] colorIdx = new Color[] { Color.Blue, Color.Red, Color.Green, Color.Orange, Color.Fuchsia, Color.Black }; + protected override CreateParams CreateParams + { + get + { + var cp = base.CreateParams; + cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED + return cp; + } + } - protected override void OnPaint(PaintEventArgs e) - { - base.OnPaint(e); + private Color[] colorIdx = new Color[] { Color.Blue, Color.Red, Color.Green, Color.Orange, Color.Fuchsia, Color.Black }; - Graphics g = this.CreateGraphics(); - g.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height)); - if (ProjPoints != null) - { - for (int i = 0; i < ProjPoints.Count; i++) - { - foreach (PointF p in ProjPoints[i]) - { - g.FillEllipse(new SolidBrush(colorIdx[i % colorIdx.Length]), new RectangleF(p.X, p.Y, 4, 4)); - } - } - } - } + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); - public void AddPoint(double x, double y, double z, int series) - { - if (Points.Count - 1 < series) - { - Points.Add(new List()); - } + Graphics g = this.CreateGraphics(); + g.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height)); + if (ProjPoints != null) + { + for (int i = 0; i < ProjPoints.Count; i++) + { + foreach (PointF p in ProjPoints[i]) + { + g.FillEllipse(new SolidBrush(colorIdx[i % colorIdx.Length]), new RectangleF(p.X, p.Y, 4, 4)); + } + } + } + } - Points[series].Add(new double[] { x, y, z }); + public void AddPoint(double x, double y, double z, int series) + { + if (Points.Count - 1 < series) + { + Points.Add(new List()); + } - foreach (List ser in Points) - { - if (ProjPoints.Count - 1 < series) - ProjPoints.Add(Projection.ProjectVector(ser, this.Width, this.Height, f, d_w, azimuth, elevation)); - else - ProjPoints[series] = Projection.ProjectVector(ser, this.Width, this.Height, f, d_w, azimuth, elevation); - } - this.Invalidate(); - } + Points[series].Add(new double[] { x, y, z }); - public void AddPoints(List points) - { - List _tmp = new List(points); - Points.Add(_tmp); - ProjPoints.Add(Projection.ProjectVector(Points[Points.Count-1], this.Width, this.Height, f, d_w, azimuth, elevation)); - UpdateProjection(); - } + foreach (List ser in Points) + { + if (ProjPoints.Count - 1 < series) + ProjPoints.Add(Projection.ProjectVector(ser, this.Width, this.Height, f, d_w, azimuth, elevation)); + else + ProjPoints[series] = Projection.ProjectVector(ser, this.Width, this.Height, f, d_w, azimuth, elevation); + } + this.Invalidate(); + } - public void Clear() - { - ProjPoints.Clear(); - Points.Clear(); - Azimuth = 0; - Elevation = 0; - } + public void AddPoints(List points) + { + List _tmp = new List(points); + Points.Add(_tmp); + ProjPoints.Add(Projection.ProjectVector(Points[Points.Count - 1], this.Width, this.Height, f, d_w, azimuth, elevation)); + UpdateProjection(); + } - private void ScatterPlot_MouseMove(object sender, MouseEventArgs e) - { - if (leftMousePressed) - { - azimuth = last_azimuth - (ptMouseClick.X - e.X) / 100; - elevation = last_elevation + (ptMouseClick.Y - e.Y) / 100; - UpdateProjection(); - } - } + public void Clear() + { + ProjPoints.Clear(); + Points.Clear(); + Azimuth = 0; + Elevation = 0; + } - private void ScatterPlot_SizeChanged(object sender, EventArgs e) - { - if (ProjPoints != null) - UpdateProjection(); - } + private void ScatterPlot_MouseMove(object sender, MouseEventArgs e) + { + if (leftMousePressed) + { + azimuth = last_azimuth - (ptMouseClick.X - e.X) / 100; + elevation = last_elevation + (ptMouseClick.Y - e.Y) / 100; + UpdateProjection(); + } + } - private void ScatterPlot_MouseDown(object sender, MouseEventArgs e) - { - if (e.Button == System.Windows.Forms.MouseButtons.Left) - { - leftMousePressed = true; - ptMouseClick = new PointF(e.X, e.Y); - last_azimuth = azimuth; - last_elevation = elevation; - } - } + private void ScatterPlot_SizeChanged(object sender, EventArgs e) + { + if (ProjPoints != null) + UpdateProjection(); + } - private void ScatterPlot_MouseUp(object sender, MouseEventArgs e) - { - if (e.Button == System.Windows.Forms.MouseButtons.Left) - leftMousePressed = false; - } + private void ScatterPlot_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + { + leftMousePressed = true; + ptMouseClick = new PointF(e.X, e.Y); + last_azimuth = azimuth; + last_elevation = elevation; + } + } - private void MyOnMouseWheel(MouseEventArgs e) - { - Distance += -e.Delta / 500D; - } + private void ScatterPlot_MouseUp(object sender, MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + leftMousePressed = false; + } - private void UpdateProjection() - { - if (ProjPoints == null) - return; - double x = d * Math.Cos(elevation) * Math.Cos(azimuth); - double y = d * Math.Cos(elevation) * Math.Sin(azimuth); - double z = d * Math.Sin(elevation); - d_w = new double[3] { -y, z, -x }; - for (int i = 0; i < ProjPoints.Count; i++) - ProjPoints[i] = Projection.ProjectVector(Points[i], this.Width, this.Height, f, d_w, azimuth, elevation); - this.Invalidate(); - } + private void MyOnMouseWheel(MouseEventArgs e) + { + Distance += -e.Delta / 500D; + } - } - - -} + private void UpdateProjection() + { + if (ProjPoints == null) + return; + double x = d * Math.Cos(elevation) * Math.Cos(azimuth); + double y = d * Math.Cos(elevation) * Math.Sin(azimuth); + double z = d * Math.Sin(elevation); + d_w = new double[3] { -y, z, -x }; + for (int i = 0; i < ProjPoints.Count; i++) + ProjPoints[i] = Projection.ProjectVector(Points[i], this.Width, this.Height, f, d_w, azimuth, elevation); + this.Invalidate(); + } + } +} \ No newline at end of file diff --git a/HexcalMC/Base/SharpGLViewportControl.cs b/HexcalMC/Base/SharpGLViewportControl.cs index 5c5031c..7fe83aa 100644 --- a/HexcalMC/Base/SharpGLViewportControl.cs +++ b/HexcalMC/Base/SharpGLViewportControl.cs @@ -1,9 +1,9 @@ -using System; +using SharpGL; +using SharpGL.SceneGraph; +using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; -using SharpGL; -using SharpGL.SceneGraph; using Telerik.WinControls.UI; namespace HexcalMC.Base @@ -22,7 +22,6 @@ namespace HexcalMC.Base Dock = DockStyle.Fill }; - Controls.Add(openGLControl); openGLControl.OpenGLInitialized += OpenGLControl_OpenGLInitialized; @@ -31,7 +30,6 @@ namespace HexcalMC.Base pointCloud = new List(); } - public void SetPointCloud(List points) { pointCloud = points; diff --git a/HexcalMC/Form/AboutBox.cs b/HexcalMC/Form/AboutBox.cs index fce52ca..14cd9f0 100644 --- a/HexcalMC/Form/AboutBox.cs +++ b/HexcalMC/Form/AboutBox.cs @@ -11,10 +11,10 @@ namespace HexcalMC.Form { InitializeComponent(); - // Initialize the AboutBox to display the product information from the assembly information. - // Change assembly information settings for your application through either: - // - Project->Properties->Application->Assembly Information - // - AssemblyInfo.cs + // Initialize the AboutBox to display the product information from the assembly + // information. Change assembly information settings for your application through either: + // - Project->Properties->Application->Assembly Information + // - AssemblyInfo.cs Text = string.Format("关于 {0}", AssemblyTitle); radLabelProductName.Text = AssemblyProduct; radLabelVersion.Text = string.Format("Version {0}", AssemblyVersion); @@ -31,7 +31,6 @@ namespace HexcalMC.Form Close(); } - #region Assembly Attribute Accessors public string AssemblyTitle @@ -51,7 +50,8 @@ namespace HexcalMC.Form return titleAttribute.Title; } - // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name + // If there was no Title attribute, or if the Title attribute was the empty string, + // return the .exe name return Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); } } @@ -118,6 +118,6 @@ namespace HexcalMC.Form } } - #endregion + #endregion Assembly Attribute Accessors } } \ No newline at end of file diff --git a/HexcalMC/Hexcal/TcpIpServer.cs b/HexcalMC/Hexcal/TcpIpServer.cs index 1119f56..3a4a3f0 100644 --- a/HexcalMC/Hexcal/TcpIpServer.cs +++ b/HexcalMC/Hexcal/TcpIpServer.cs @@ -1,4 +1,5 @@ -using System; +using HexcalMC.Base; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -6,7 +7,6 @@ using System.Net.Sockets; using System.Text; using System.Threading; using System.Windows.Forms; -using HexcalMC.Base; using Timer = System.Windows.Forms.Timer; namespace HexcalMC.Hexcal @@ -36,7 +36,7 @@ namespace HexcalMC.Hexcal private Thread _mWatchThread; // 负责监听客户端连接请求的线程; /// - /// 使用模式,默认=1,接收任意数据显示;2=前两个字节为数据长度 + /// 使用模式,默认=1,接收任意数据显示;2=前两个字节为数据长度 /// public int UseMode = 1; @@ -207,8 +207,8 @@ namespace HexcalMC.Hexcal if (length > 0) { string strData = Encoding.Default.GetString(arrMsgRec); // 将接受到的字节数据转化成字符串; - //strData = strData.Substring(0, length); - //RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData.Replace("\0", "<0>")); + //strData = strData.Substring(0, length); + //RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData.Replace("\0", "<0>")); RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData.Replace("\0", ".")); } @@ -226,6 +226,7 @@ namespace HexcalMC.Hexcal } break; + case 3: if (length > 0) { @@ -315,7 +316,6 @@ namespace HexcalMC.Hexcal } } - public void SendMessageToAllClients(byte[] arrMsg) { foreach (Socket soc in _dictSocket.Values) @@ -388,7 +388,7 @@ namespace HexcalMC.Hexcal for (int i = _dictSocket.Values.ToArray().Length - 1; i >= 0; i--) { if (_dictSocket.Values.ToArray()[i] - .Poll(100, SelectMode.SelectRead)) //10毫秒,检查套接字状态, SelectMode 参数指定要监视的套接字的类别。 + .Poll(100, SelectMode.SelectRead)) //10毫秒,检查套接字状态, SelectMode 参数指定要监视的套接字的类别。 { // DictSocket.Remove(DictSocket.Keys.ToArray()[i]); diff --git a/HexcalMC/HexcalMC.csproj b/HexcalMC/HexcalMC.csproj index dfff1eb..e64ec9a 100644 --- a/HexcalMC/HexcalMC.csproj +++ b/HexcalMC/HexcalMC.csproj @@ -86,16 +86,22 @@ Hexagon.ico - 6EDD851028CAF15F53977FDAABCEB5A0859CFE89 + 496B16FFAF2CF6830CF56CFF2C6223A82DC3B1DF - HexcalMC_TemporaryKey.pfx + HexcalMC_1_TemporaryKey.pfx true - true + false + + + + + + true @@ -197,11 +203,11 @@ AboutBox.cs - + Form - - MainFrom.cs + + MainForm.cs Form @@ -236,8 +242,8 @@ AboutBox.cs - - MainFrom.cs + + MainForm.cs Designer MainFrom1.Designer.cs @@ -267,7 +273,6 @@ - SettingsSingleFileGenerator diff --git a/HexcalMC/MainFrom.Designer.cs b/HexcalMC/MainForm.Designer.cs similarity index 97% rename from HexcalMC/MainFrom.Designer.cs rename to HexcalMC/MainForm.Designer.cs index 19eb2b3..6ab117a 100644 --- a/HexcalMC/MainFrom.Designer.cs +++ b/HexcalMC/MainForm.Designer.cs @@ -1,6 +1,6 @@ namespace HexcalMC { - partial class MainFrom + partial class MainForm { /// /// Required designer variable. @@ -29,14 +29,12 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainFrom)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.radLabelElement2 = new Telerik.WinControls.UI.RadLabelElement(); this.radTextBoxElement2 = new Telerik.WinControls.UI.RadTextBoxElement(); this.radButtonElement5 = new Telerik.WinControls.UI.RadButtonElement(); this.radTextBoxElement1 = new Telerik.WinControls.UI.RadTextBoxElement(); this.ribbonTab1 = new Telerik.WinControls.UI.RibbonTab(); - this.radRibbonBarGroup1 = new Telerik.WinControls.UI.RadRibbonBarGroup(); - this.btn_motion = new Telerik.WinControls.UI.RadButtonElement(); this.radRibbonBarGroup4 = new Telerik.WinControls.UI.RadRibbonBarGroup(); this.radRibbonBarButtonGroup4 = new Telerik.WinControls.UI.RadRibbonBarButtonGroup(); this.radLabelElement5 = new Telerik.WinControls.UI.RadLabelElement(); @@ -62,6 +60,8 @@ this.rtb_stop = new Telerik.WinControls.UI.RadButtonElement(); this.radRibbonBarGroup9 = new Telerik.WinControls.UI.RadRibbonBarGroup(); this.rtb_etalon = new Telerik.WinControls.UI.RadButtonElement(); + this.radRibbonBarGroup1 = new Telerik.WinControls.UI.RadRibbonBarGroup(); + this.btn_motion = new Telerik.WinControls.UI.RadButtonElement(); this.radRibbonBarGroup10 = new Telerik.WinControls.UI.RadRibbonBarGroup(); this.rtb_monitor = new Telerik.WinControls.UI.RadButtonElement(); this.ribbonTab2 = new Telerik.WinControls.UI.RibbonTab(); @@ -217,13 +217,13 @@ this.ribbonTab1.DisabledTextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault; this.ribbonTab1.IsSelected = true; this.ribbonTab1.Items.AddRange(new Telerik.WinControls.RadItem[] { - this.radRibbonBarGroup1, this.radRibbonBarGroup4, this.radRibbonBarGroup5, this.radRibbonBarGroup6, this.radRibbonBarGroup8, this.radRibbonBarGroup7, this.radRibbonBarGroup9, + this.radRibbonBarGroup1, this.radRibbonBarGroup10}); this.ribbonTab1.Name = "ribbonTab1"; this.ribbonTab1.Text = "常用"; @@ -231,27 +231,6 @@ this.ribbonTab1.UseCompatibleTextRendering = false; this.ribbonTab1.UseMnemonic = false; // - // radRibbonBarGroup1 - // - this.radRibbonBarGroup1.Alignment = System.Drawing.ContentAlignment.TopLeft; - this.radRibbonBarGroup1.AutoSize = false; - this.radRibbonBarGroup1.Bounds = new System.Drawing.Rectangle(0, 0, 77, 100); - this.radRibbonBarGroup1.Items.AddRange(new Telerik.WinControls.RadItem[] { - this.btn_motion}); - this.radRibbonBarGroup1.Margin = new System.Windows.Forms.Padding(0); - this.radRibbonBarGroup1.MaxSize = new System.Drawing.Size(110, 100); - this.radRibbonBarGroup1.MinSize = new System.Drawing.Size(110, 100); - this.radRibbonBarGroup1.Name = "radRibbonBarGroup1"; - this.radRibbonBarGroup1.Text = "运动控制"; - this.radRibbonBarGroup1.UseCompatibleTextRendering = false; - // - // btn_motion - // - this.btn_motion.Image = global::HexcalMC.Properties.Resources.mothion_64; - this.btn_motion.Name = "btn_motion"; - this.btn_motion.Text = ""; - this.btn_motion.Click += new System.EventHandler(this.btn_motion_Click); - // // radRibbonBarGroup4 // this.radRibbonBarGroup4.Items.AddRange(new Telerik.WinControls.RadItem[] { @@ -445,6 +424,7 @@ this.rtb_etalon}); this.radRibbonBarGroup9.Name = "radRibbonBarGroup9"; this.radRibbonBarGroup9.Text = "etalon校准"; + this.radRibbonBarGroup9.Visibility = Telerik.WinControls.ElementVisibility.Hidden; // // rtb_etalon // @@ -453,18 +433,42 @@ this.rtb_etalon.Text = ""; this.rtb_etalon.Click += new System.EventHandler(this.rtb_etalon_Click); // + // radRibbonBarGroup1 + // + this.radRibbonBarGroup1.Alignment = System.Drawing.ContentAlignment.TopLeft; + this.radRibbonBarGroup1.AutoSize = false; + this.radRibbonBarGroup1.Bounds = new System.Drawing.Rectangle(0, 0, 77, 100); + this.radRibbonBarGroup1.Items.AddRange(new Telerik.WinControls.RadItem[] { + this.btn_motion}); + this.radRibbonBarGroup1.Margin = new System.Windows.Forms.Padding(0); + this.radRibbonBarGroup1.MaxSize = new System.Drawing.Size(110, 100); + this.radRibbonBarGroup1.MinSize = new System.Drawing.Size(110, 100); + this.radRibbonBarGroup1.Name = "radRibbonBarGroup1"; + this.radRibbonBarGroup1.Text = "运动控制"; + this.radRibbonBarGroup1.UseCompatibleTextRendering = false; + this.radRibbonBarGroup1.Visibility = Telerik.WinControls.ElementVisibility.Hidden; + // + // btn_motion + // + this.btn_motion.Image = global::HexcalMC.Properties.Resources.mothion_64; + this.btn_motion.Name = "btn_motion"; + this.btn_motion.Text = ""; + this.btn_motion.Click += new System.EventHandler(this.btn_motion_Click); + // // radRibbonBarGroup10 // this.radRibbonBarGroup10.Items.AddRange(new Telerik.WinControls.RadItem[] { this.rtb_monitor}); this.radRibbonBarGroup10.Name = "radRibbonBarGroup10"; this.radRibbonBarGroup10.Text = "速度监控"; + this.radRibbonBarGroup10.Visibility = Telerik.WinControls.ElementVisibility.Hidden; // // rtb_monitor // this.rtb_monitor.AutoSize = false; this.rtb_monitor.Bounds = new System.Drawing.Rectangle(0, 0, 66, 75); this.rtb_monitor.Image = global::HexcalMC.Properties.Resources.speed; + this.rtb_monitor.ImageAlignment = System.Drawing.ContentAlignment.MiddleCenter; this.rtb_monitor.Name = "rtb_monitor"; this.rtb_monitor.Text = ""; this.rtb_monitor.Click += new System.EventHandler(this.rtb_monitor_Click); @@ -537,6 +541,12 @@ this.radMenuItem2}); this.radRibbonBar1.TabIndex = 0; this.radRibbonBar1.Text = "运动补偿中间件"; + ((Telerik.WinControls.UI.RadRibbonBarElement)(this.radRibbonBar1.GetChildAt(0))).Text = "运动补偿中间件"; + ((Telerik.WinControls.UI.RadQuickAccessToolBar)(this.radRibbonBar1.GetChildAt(0).GetChildAt(2))).Visibility = Telerik.WinControls.ElementVisibility.Hidden; + ((Telerik.WinControls.UI.RadRibbonBarCaption)(this.radRibbonBar1.GetChildAt(0).GetChildAt(3))).Visibility = Telerik.WinControls.ElementVisibility.Visible; + ((Telerik.WinControls.UI.RadPageViewContentAreaElement)(this.radRibbonBar1.GetChildAt(0).GetChildAt(4).GetChildAt(1))).Visibility = Telerik.WinControls.ElementVisibility.Visible; + ((Telerik.WinControls.UI.RadApplicationMenuButtonElement)(this.radRibbonBar1.GetChildAt(0).GetChildAt(5))).Image = global::HexcalMC.Properties.Resources.Hexagon; + ((Telerik.WinControls.UI.RadApplicationMenuButtonElement)(this.radRibbonBar1.GetChildAt(0).GetChildAt(5))).Visibility = Telerik.WinControls.ElementVisibility.Visible; // // radRibbonBarBackstageView1 // @@ -791,7 +801,7 @@ this.groupBox7.Size = new System.Drawing.Size(731, 154); this.groupBox7.TabIndex = 4; this.groupBox7.TabStop = false; - this.groupBox7.Text = "Hexcal接口"; + this.groupBox7.Text = "1. Hexcal接口"; // // groupBox1 // @@ -801,7 +811,7 @@ this.groupBox1.Controls.Add(this.label3); this.groupBox1.Location = new System.Drawing.Point(14, 19); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(701, 114); + this.groupBox1.Size = new System.Drawing.Size(263, 114); this.groupBox1.TabIndex = 0; this.groupBox1.TabStop = false; this.groupBox1.Text = "通讯状态"; @@ -820,7 +830,7 @@ // // Btn_StopServer // - this.Btn_StopServer.Location = new System.Drawing.Point(113, 79); + this.Btn_StopServer.Location = new System.Drawing.Point(111, 63); this.Btn_StopServer.Name = "Btn_StopServer"; this.Btn_StopServer.Size = new System.Drawing.Size(81, 30); this.Btn_StopServer.TabIndex = 26; @@ -830,7 +840,7 @@ // // Btn_StartServer // - this.Btn_StartServer.Location = new System.Drawing.Point(18, 79); + this.Btn_StartServer.Location = new System.Drawing.Point(16, 63); this.Btn_StartServer.Name = "Btn_StartServer"; this.Btn_StartServer.Size = new System.Drawing.Size(81, 30); this.Btn_StartServer.TabIndex = 25; @@ -858,7 +868,7 @@ this.groupBox5.Size = new System.Drawing.Size(731, 154); this.groupBox5.TabIndex = 3; this.groupBox5.TabStop = false; - this.groupBox5.Text = "运动平台"; + this.groupBox5.Text = "2. 运动平台"; // // grpSafety // @@ -1302,7 +1312,7 @@ this.txtIP.Name = "txtIP"; this.txtIP.Size = new System.Drawing.Size(91, 21); this.txtIP.TabIndex = 22; - this.txtIP.Text = "100.0.0.100"; + this.txtIP.Text = "10.0.0.100"; // // btn_ACSStop // @@ -1372,7 +1382,7 @@ this.timer_RefreshUI.Interval = 1000; this.timer_RefreshUI.Tick += new System.EventHandler(this.Timer_RefreshUI_Tick); // - // MainFrom + // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; @@ -1383,7 +1393,7 @@ this.Controls.Add(this.radRibbonBarBackstageView1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = null; - this.Name = "MainFrom"; + this.Name = "MainForm"; // // // diff --git a/HexcalMC/MainForm.cs b/HexcalMC/MainForm.cs new file mode 100644 index 0000000..7272acb --- /dev/null +++ b/HexcalMC/MainForm.cs @@ -0,0 +1,1637 @@ +using ACS.SPiiPlusNET; +using HexcalMC.Base; +using HexcalMC.Form; +using HexcalMC.Hexcal; +using HexcalMC.Properties; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; +using Telerik.WinControls.UI; +using static HexcalMC.EtalonForm; + +namespace HexcalMC +{ + //定一个 回家状态枚举,包括 从未回家,正在回家,已经回家 + public enum HomeStates + { + None, //默认状态 + NotHome, //未回家 + Homing, //回家中 + Homed //回家完成 + , + } + + //定义 运动状态枚举,包括 正在运动,运动到位,Jog运动 + public enum MotionStates + { + None, //默认状态 + Moving, //运动中 + InPos, //运动到位 + Jogging //jog中 + , + } + + public partial class MainForm : RadRibbonForm + { + private readonly List _pointCloud = new List(); //运动中点集合 + + private bool _mBHexcalConnected; + + private TcpIpServer _mTcpIpServer; //创建tcpserver,用于接收hexcal传来的指令,并解析传递平台 + + private int m_nTotalAxis; //定义总轴数 + private Axis[] m_arrAxisList = null; + + //定一个运动到位次数的变量和三个方法,开始统计运动到位次数,停止统计运动到位次数,获取运动到位次数 + + #region 运动到位次数 + + // 记录运动到位次数的变量 + private int m_nInPosCount = 0; + + private bool isCounting = false; + private bool isInPose = false; //运动到位状态 + + // 开始统计运动到位次数 + public void StartCounting() + { + isCounting = true; + m_nInPosCount = 0; // 重置计数器 + } + + // 停止统计运动到位次数 + public void StopCounting() + { + isCounting = false; + m_nInPosCount = 0; // 重置计数器 + } + + // 获取运动到位次数 + public int GetInPosCount() + { + return m_nInPosCount; + } + + public bool GetIsMoving() + { + return isInPose; + } + + #endregion 运动到位次数 + + public MainForm() + { + InitializeComponent(); + + // 处理未被捕获的线程异常 + Application.ThreadException += Application_ThreadException; + + // 处理未被捕获的非UI线程异常 + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + } + + private void MainFrom_Load(object sender, EventArgs e) + { + FormBorderStyle = FormBorderStyle.FixedSingle; // 设置窗体边框样式为固定大小 + MaximizeBox = false; // 禁用窗体的最大化按钮 + DebugDfn.textBox_Msg = TextBoxMsg; + + //加载配置文件 + LoadConfig(); + _acs = new Api(); //初始化 ACS运动控制类 + + //启动界面刷新 + timer_RefreshUI.Start(); + + //Point3D point3D = new Point3D(800, 980, -290); + //IsWithinLimit(point3D); + } + + private void MainFrom_Shown(object sender, EventArgs e) //窗体显示准备好接受用户输入时发生 + { + ////启动服务端,用于接收hexcal传来的指令 + //StartServer(); + + //if (_enableAcs) + //{ + // Btn_ACSStart_Click(null, null); //模拟连接运动平台 + //} + } + + private void MainFrom_FormClosed(object sender, FormClosedEventArgs e) + { + MyBase.TraceWriteLine("关闭程序"); + DebugDfn._strEndTime = DateTime.Now.ToString("yyyy.MM.dd HH-mm-ss"); + timer_RefreshUI.Stop(); + + Btn_StopServer_Click(null, null); + Btn_ACSStop_Click(null, null); //关闭ACS + + string copyFileName = + DebugDfn.StrDebugSavePath + + "\\Debug(" + + DebugDfn._strStartTime + + " To " + + DebugDfn._strEndTime + + ")" + + ".txt"; + if (!File.Exists(DebugDfn.StrDebugSavePath)) + { + //创建文件夹 DebugDfn.StrDebugSavePath + Directory.CreateDirectory(DebugDfn.StrDebugSavePath); + } + + File.Copy(DebugDfn.StrDebugFile, copyFileName); + + if (Errors.ErrorWrite != null) + Errors.ErrorWrite.Close(); + if (Errors.OtherWrite != null) + Errors.OtherWrite.Close(); + if (Errors.StatusWrite != null) + Errors.StatusWrite.Close(); + } + + private void LoadConfig() //加载配置文件 + { + //判断配置文件是否存在 + if (!File.Exists(StrConfigFile)) + { + MessageBox.Show( + "配置文件不存在,请检查配置文件", + "异常", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + return; + } + + MotionSpeedxy = FileIni.ReadDouble(StrConfigFile, "MOTOR", "MOTION_SPEEDXY"); //运动定位速度 + MotionSpeedz = FileIni.ReadDouble(StrConfigFile, "MOTOR", "MOTION_SPEEDZ"); + + //正限位 + XMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "X_MAXSTROKESW"); + YMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Y_MAXSTROKESW"); + ZMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Z_MAXSTROKESW"); + + //负限位 + XMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "X_MINSTROKESW"); + YMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Y_MINSTROKESW"); + ZMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Z_MINSTROKESW"); + + port = FileIni.ReadInt(StrConfigFile, "MOTOR", "Port"); + DebugDfn.AddLogText($"当前监听端口配置为: {port}"); + DebugDfn.AddLogText($"当前xy运动速度配置为: {MotionSpeedxy}"); + DebugDfn.AddLogText($"当前z运动速度配置为: {MotionSpeedz}"); + } + + private void Plot2D(List pointCloud) + { + // 清空画布 + formsPlot1.Plot.Clear(); + + //pointCloud 是否为空 + if (pointCloud.Count <= 0) + { + return; + } + + List dataX = new List(); + List dataY = new List(); + + foreach (Point3D point3D in pointCloud) + { + dataX.Add(point3D.X); + dataY.Add(point3D.Y); + } + + formsPlot1.Plot.AddScatter(dataX.ToArray(), dataY.ToArray()); + formsPlot1.Refresh(); + } + + #region 运动平台变量区 + + public Api _acs; + + private const int MaxUiLimitCnt = 24; + private int _mNTotalAxis; + + //private int _mNTotalBuffer = 0; + //private Axis[] _mArrAxisList = null; + public bool _mAcsConnected; //ACS通讯状态 + + // For update values + private MotorStates _mNMotorState; //运动状态 + + private ProgramStates _mNProgramState; //程序状态 + private object _mObjReadVar; + private Array _mArrReadVector; + + private double _mLfRPos, + _mLfFPos, + _mLfPe, + _mLfFvel; //参考位置,反馈位置 位置误差 反馈速度 double类型 + + private int _mNValues, + _mNOutputState; + + private Label[] _mLblLeftLimit; //左限位 + private Label[] _mLblRightLimit; //右限位 + private Label[] _mlblMoving; //运动中 + private Label[] _mlblAcc; //加速中 + private Label[] _mlblInPos; //轴就位 + private Label[] _mlblEnable; //使能 + private bool[] axisEnabled = new bool[MaxUiLimitCnt]; //轴使能状态 + public bool totalAxisEnabled = false; + + private HomeStates _homeStates; //回家状态 + private MotionStates _currentMotionState; //当前运动状态 + private MotionStates _currentMotorStateLast; + private readonly int _motionTimeout = 50000; //定义运动超时时间 + + //定义启用的轴,后面运动时会使用 + public static Axis[] UseAxis = + { + Axis.ACSC_AXIS_1, + Axis.ACSC_AXIS_0, + Axis.ACSC_AXIS_4, + Axis.ACSC_NONE, + }; + + //定义 XYZ三个轴的左右行程范围 + public string StrConfigFile = Application.StartupPath + "\\File\\config.ini"; + + public static double MotionSpeedxy = 60; + public static double MotionSpeedz = 30; + public static double XMaxstrokesw = 730; //正限位 + public static double YMaxstrokesw = 1000; + public static double ZMaxstrokesw = 5; + + public static double XMinstrokesw = -30; //负限位 + public static double YMinstrokesw = -10; + public static double ZMinstrokesw = -280; + public static int port = 1234; //默认监听端口 + + //定义一个3D点,存储当前平台实时位置 + public Point3D _mPoint3D; + + #endregion 运动平台变量区 + + #region hexcal软件交互 + + private void StartServer() + { + // 对_mTcpIpServer增加判断是否已经启动且存在设备连接 + if (_mTcpIpServer != null && _mTcpIpServer.ConnectStatus) + { + //弹窗提醒已经启动 + MyBase.TraceWriteLine("TCP服务端已经启动,请勿重复启动"); + MessageBox.Show( + "TCP服务端已经启动,请勿重复启动", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + + //启动服务器,并获取数据,解析 + _mTcpIpServer = new TcpIpServer(IPAddress.Any.ToString(), Convert.ToString(port)); + _mTcpIpServer.UseMode = 1; //设置通讯返回数据流格式 + try + { + //启动监听 + if (_mTcpIpServer.StartListen()) + { + //绑定两个事件 OnRaisedStatus 和OnRaisedMessage + _mTcpIpServer.OnRaisedMessage += ReceiveMessage; //接收消息回调 + _mTcpIpServer.OnRaisedStatus += ReceiveStatus; //连接状态 + _mTcpIpServer.DataReceived += ReceiveByte; + } + else + { + MessageBox.Show( + "TCP服务端启动失败,请检查网络连接,重新打开软件", + "异常", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + catch (Exception ex) + { + DebugDfn.AddLogText("启动TCP服务端异常" + ex); + } + } + + private void ReceiveByte(object sender, byte[] e) + { + DebugDfn.AddLogText("接收到" + BitConverter.ToString(e)); + } + + private void ReceiveMessage(string clientIp, string msg) //接收的内容 + { + //打印ClientIP 和 Msg + DebugDfn.AddLogText("接收到" + clientIp + ": " + msg); + + //根据源地址的不同,执行不同处理 + string sourceIp = clientIp.Split(':')[0]; + switch (sourceIp) + { + case "100.0.0.1": + ParseHexcalMsg(msg); + break; + + case "100.0.0.2": + ParseHexcalMsg(msg); + break; + + default: + DebugDfn.AddLogText("未知来源,没有应答"); + break; + } + } + + public static string ConstructString(string variableName, double[] values) + { + string result = variableName + " "; + for (int i = 0; i < values.Length; i++) + { + result += values[i].ToString("F6"); + if (i < values.Length - 1) + { + result += ", "; + } + } + + return result; + } + + public static string ConstructPosString(Point3D point) + { + double[] values = { point.X, point.Y, point.Z, 0.0, 0.0, 0.0, 0.0 }; + return ConstructString("POS", values); + } + + public static Point3D ParsePoint3DFromCommand(string input) + { + string[] parts = input.Split(' ')[1].Split(','); + if (parts.Length >= 3) + { + double x = double.Parse(parts[0]); + double y = double.Parse(parts[1]); + double z = double.Parse(parts[2]); + return new Point3D(x, y, z); + } + + throw new ArgumentException("输入字符串格式不正确。"); + } + + private void CheckPlatformStatus() + { + //检查平台状态,如果运动中,返回BUSY,否则返回READY + if ( + _currentMotionState == MotionStates.None + || _currentMotionState == MotionStates.InPos + ) //默认或到位 + { + SendMsgToHexcal("READY"); + } + else + { + SendMsgToHexcal("BUSY"); + } + } + + private void ParseHexcalMsg(string msg) //编写一个Hexcal协议解析函数 + { + //DebugDfn.AddLogText("正在解析 " + msg); + + //去除Msg中\r\n + msg = msg.Replace("\r\n", ""); + + //判断是否含有故障ERROR字样 + if (msg.Contains("ERROR")) + { + //弹窗提醒 + MessageBox.Show("CMM错误", msg, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (msg.Contains("\x02") || msg.Contains("\u0002")) + { + //DebugDfn.AddLogText("接收到STX,开始解析"); + CheckPlatformStatus(); + } + else if (msg.Contains("\x03") || msg.Contains("\u0003")) + { + CheckPlatformStatus(); + } + //else if (msg.Contains("^B")) //查询状态, READY或BUSY + //{ + // checkPlatformStatus(); + //} + else if (msg.Contains("CMMTYP")) //测量机类型 + { + SendMsgToHexcal("CMMTYP MA 19617, FDC V15.00, 10 8 3 , 0"); + } + else if (msg.Contains("VERSION")) //版本号 + { + SendMsgToHexcal("00-000-000-00000 FDC V51.04.0000 DATE: 12/21/22 TIME: 12:50:55"); + } + else if (msg.Contains("SHOW MAXSTROKESW")) //最大行程,根据实际情况填写 + { + //MAXSTROKESW 233.200000,346.500000,15.100000,0.000000,0.000000,0.000000,0.000000 + + double[] values = { XMaxstrokesw, YMaxstrokesw, ZMaxstrokesw, 0.0, 0.0, 0.0, 0.0 }; + string resultString = ConstructString("MAXSTROKESW", values); + SendMsgToHexcal(resultString); + } + else if (msg.Contains("SHOW MINSTROKESW")) //最小行程,根据实际情况填写 + { + //MINSTROKESW -68.800000,-55.500000,-286.900000,0.000000,0.000000,0.000000,0.000000 + + double[] values = { XMinstrokesw, YMinstrokesw, ZMinstrokesw, 0.0, 0.0, 0.0, 0.0 }; + string resultString = ConstructString("MINSTROKESW", values); + SendMsgToHexcal(resultString); + } + else if (msg.Contains("SHOW MAXVEL")) //最大速度 + { + SendMsgToHexcal( + "MAXVEL 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" + ); + } + else if (msg.Contains("SHOW MAXACC")) //最大加速度 + { + SendMsgToHexcal( + "MAXACC 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" + ); + } + else if (msg.Contains("SHOW SENSWKP")) + { + SendMsgToHexcal("X_ SENSWKP 4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); + } + else if (msg.Contains("SHOW X_SENSAXIS")) + { + SendMsgToHexcal("X_SENSAXIS 6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); + } + else if (msg.Contains("SHOW Y_SENSAXIS")) //查询Y轴 + { + SendMsgToHexcal("Y_SENSAXIS 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); + } + else if (msg.Contains("SHOW Z_SENSAXIS")) //查询Z轴 + { + SendMsgToHexcal("Z_SENSAXIS 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); + } + else if (msg.Contains("SHOW TEMPCOMPTYPE")) //温度补偿,温度补偿 >1 表示支持温度补偿,此处不支持 + { + SendMsgToHexcal("TEMPCOMPTYPE 0"); + } + else if (msg.Contains("READTP")) + { + SendMsgToHexcal("READTP 0.000000"); + } + else if (msg.Contains("SHOW ESTOP")) //查询急停状态,根据真是情况调整 + { + SendMsgToHexcal("ESTOP FALSE"); + } + else if (msg.Contains("CMHWST")) + { + SendMsgToHexcal("CMHWST 8257,0,1792,0"); + } + else if (msg.Contains("SHOW MOVPAR")) //查询速度 + { + SendMsgToHexcal( + "MOVPAR 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.00000 0,0.000000" + ); + } + else if (msg.Contains("SHOW MAXVEL")) //查询最大速度 + { + SendMsgToHexcal( + "MAXVEL 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" + ); + } + else if (msg.Contains("SHOW ACCEL")) //查询加速度 + { + SendMsgToHexcal( + "ACCEL 1000.000000,1000.000000,1000.000000,0.000000,0.000000,0.000000,0.000000,0.000000" + ); + } + else if (msg.Contains("MOVPAR")) //设置速度 xyz 轴的速度 + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("ACCEL")) //设置加速度 + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("PRBPIN")) //设置侧头偏置 + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("ENABLE TEMP")) //设置温度补偿 + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("WKPPAR")) + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("SCLTMP")) + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("DISABLE GEO")) + { + SendMsgToHexcal("%"); + } + else if (msg.Contains("AUTZER")) //回家指令 + { + SendMsgToHexcal("%"); //收到并执行,同时状态改为忙碌 + + //执行回家 + IsHomed(); + } + else if (msg.Contains("MOVABS")) //移动指令,解析移动位置 + { + //收到指令 ,形如 MOVABS 0.015000,127.172997,-114.897003,0.000000\r\n + SendMsgToHexcal("%"); + + Point3D point = ParsePoint3DFromCommand(msg); + SetPositionXyz(point); //开始移动 + + _pointCloud.Add(point); //添加到点集合 + } + else if (msg.Contains("GETPOS")) //获取位置 + { + //POS 167.553898,-55.400421,-208.548678,0.000000,0.000000,0.000000,0.000000 + Point3D point3D = GetPositionXyz(); //获取当前位置 + string resultString = ConstructPosString(point3D); + SendMsgToHexcal(resultString); + } + else + { + DebugDfn.AddLogText("未知命令,没有应答"); + } + } + + private void ReceiveStatus(TcpIpServer.EnumTcpIpServer iType, string msg) + { + //记录到日志 + DebugDfn.AddLogText(iType + " : " + msg); + + //根据连接状态,更新界面 + switch (iType) + { + case TcpIpServer.EnumTcpIpServer.ClientConnect: + _mBHexcalConnected = true; + break; + + default: + _mBHexcalConnected = false; + break; + } + } + + private void SendMsgToHexcal(string msg) + { + if (_mTcpIpServer == null) + return; + + //发送数据 + DebugDfn.AddLogText("回复 " + msg); + _mTcpIpServer.SendMessageToAllClients(msg += "\r\n"); //回复内容末尾加上\r\n,协议要求 + } + + private void Btn_StartServer_Click(object sender, EventArgs e) + { + Btn_StartServer.Enabled = false; + Btn_StopServer.Enabled = true; + StartServer(); + DebugDfn.AddLogText("TCP服务端启动成功 "); + } + + private void Btn_StopServer_Click(object sender, EventArgs e) + { + //关闭服务端 + if (_mTcpIpServer != null) + { + _mTcpIpServer.StopListen(); + } + + Btn_StopServer.Enabled = false; + Btn_StartServer.Enabled = true; + _mBHexcalConnected = false; + DebugDfn.AddLogText("TCP服务端已关闭"); + } + + #endregion hexcal软件交互 + + #region ACS平台相关 + + #region 异常抓取 + + //实现函数 + + private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + // 防止程序终止 + MessageBox.Show( + "发生了未处理的异常:" + e.Exception.Message, + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + + private static void CurrentDomain_UnhandledException( + object sender, + UnhandledExceptionEventArgs e + ) + { + if (e.ExceptionObject is Exception ex) + { + HandleException(ex); + } + } + + private static void HandleException(Exception ex) + { + MessageBox.Show( + $"发生了未处理的异常:{ex.Message}", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + + //订阅报错 + private void EnableFaultEvent() + { + _acs.EnableEvent(Interrupts.ACSC_INTR_COMM_CHANNEL_CLOSED); + _acs.EnableEvent(Interrupts.ACSC_INTR_EMERGENCY); + _acs.EnableEvent(Interrupts.ACSC_INTR_SYSTEM_ERROR); + _acs.EnableEvent(Interrupts.ACSC_INTR_ETHERCAT_ERROR); + _acs.EnableEvent(Interrupts.ACSC_INTR_MOTOR_FAILURE); + _acs.EnableEvent(Interrupts.ACSC_INTR_MOTION_FAILURE); + _acs.EnableEvent(Interrupts.ACSC_INTR_PHYSICAL_MOTION_END); + _acs.COMMCHANNELCLOSED += _acs_COMMCHANNELCLOSED; + _acs.MOTORFAILURE += _acs_MOTORFAILURE; + _acs.MOTIONFAILURE += _acs_MOTIONFAILURE; + _acs.SYSTEMERROR += _acs_SYSTEMERROR; + _acs.ETHERCATERROR += _acs_ETHERCATERROR; + _acs.EMERGENCY += _acs_EMERGENCY; + _acs.PHYSICALMOTIONEND += _acs_PHYSICAL_MOTION_END; + } + + private void DisableFaultEvent() + { + _acs.COMMCHANNELCLOSED -= _acs_COMMCHANNELCLOSED; + _acs.MOTORFAILURE -= _acs_MOTORFAILURE; + _acs.MOTIONFAILURE -= _acs_MOTIONFAILURE; + _acs.SYSTEMERROR -= _acs_SYSTEMERROR; + _acs.ETHERCATERROR -= _acs_ETHERCATERROR; + _acs.EMERGENCY -= _acs_EMERGENCY; + _acs.PHYSICALMOTIONEND -= _acs_PHYSICAL_MOTION_END; + + _acs.DisableEvent(Interrupts.ACSC_INTR_COMM_CHANNEL_CLOSED); + _acs.DisableEvent(Interrupts.ACSC_INTR_EMERGENCY); + _acs.DisableEvent(Interrupts.ACSC_INTR_SYSTEM_ERROR); + _acs.DisableEvent(Interrupts.ACSC_INTR_ETHERCAT_ERROR); + _acs.DisableEvent(Interrupts.ACSC_INTR_MOTOR_FAILURE); + _acs.DisableEvent(Interrupts.ACSC_INTR_MOTION_FAILURE); + _acs.DisableEvent(Interrupts.ACSC_INTR_PHYSICAL_MOTION_END); + } + + //关联函数 + private void _acs_EMERGENCY(ulong param) + { + DebugDfn.AddLogText($"[EStopError] Error Message:{_acs.GetErrorString((int)param)}"); + } + + private void _acs_ETHERCATERROR(ulong param) + { + DebugDfn.AddLogText( + $"[EtherCatError] Error Message:{_acs.GetErrorString((int)param)}" + ); + } + + private void _acs_SYSTEMERROR(ulong param) + { + DebugDfn.AddLogText($"[SystemError] Error Message:{_acs.GetErrorString((int)param)}"); + } + + private void _acs_MOTIONFAILURE(AxisMasks axis) + { + for (int i = 0; i < _acs.GetAxesCount(); i++) + { + if (((int)axis & (int)Math.Pow(2, i)) == Math.Pow(2, i)) + { + if (_acs.GetMotionError((Axis)i) != 0) + { + //Motor无法自动捕获,需要在motion报错中获取 + int errorcode = _acs.GetMotionError((Axis)i); + + DebugDfn.AddLogText( + $"[MotionError] Axis:{i} Error Code:{errorcode} Error Message: {_acs.GetErrorString(errorcode)}" + ); + + int errorcodes = _acs.GetMotorError((Axis)i); + + DebugDfn.AddLogText( + $"[MotorError] Axis:{i} Error Code:{errorcodes} Error Message:{_acs.GetErrorString(errorcodes)}" + ); + } + } + } + } + + private void _acs_MOTORFAILURE(AxisMasks axis) + { + for (int i = 0; i < _acs.GetAxesCount(); i++) + { + if (((int)axis & (int)Math.Pow(2, i)) == Math.Pow(2, i)) + { + int errorcode = _acs.GetMotorError((Axis)i); + + DebugDfn.AddLogText( + $"[MotorError] Axis:{i} Error Code:{errorcode} Error Message:{_acs.GetErrorString(errorcode)}" + ); + } + } + } + + private void _acs_COMMCHANNELCLOSED(ulong param) + { + DebugDfn.AddLogText($"[CommError] Error Message:{_acs.GetErrorString((int)param)}"); + } + + private void _acs_PHYSICAL_MOTION_END(AxisMasks axis) + { + int bit = 0x01; + int axisNo = 0; + + for (int i = 0; i < 64; i++) + { + if ((int)axis == bit) + { + axisNo = i; + break; + } + + bit = bit << 1; + } + + //DebugDfn.AddLogText(string.Format(" - Axis {0}, Stoppped", axisNo)); + } + + #endregion 异常抓取 + + private void BtnEnable_Click(object sender, EventArgs e) //使能所有轴 + { + if (_mAcsConnected) + { + //!!!! Important !! Must insert '-1' at the last + _acs.EnableM(UseAxis); + } + else + { + //弹窗提醒尚未连接 + MessageBox.Show("未连接到运动平台,请先点击连接"); + } + } + + private void BtnDisable_Click(object sender, EventArgs e) //轴取消 + { + // Disable all of axes + _acs.DisableAll(); + } + + private bool IsMotionInPose() + { + bool x_inpose = false, + y_inpose = false, + z_inpose = false; + + _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_1); + if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) + { + x_inpose = true; + } + + _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_0); + if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) + { + y_inpose = true; + } + + _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_4); + if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) + { + z_inpose = true; + } + + if (x_inpose && y_inpose && z_inpose) + { + return true; + } + + return false; + } + + private void TmrMonitor_Tick(object sender, EventArgs e) //用于刷新状态 + { + if (_mAcsConnected) + { + try + { + _mPoint3D = GetPositionXyz(); //取平台当前位置 + + #region 更新限位及运动状态 + + //左右限位刷新 + _mObjReadVar = _acs.ReadVariableAsVector( + "FAULT", + ProgramBuffer.ACSC_NONE, + 0, + _mNTotalAxis - 1 + ); + if (_mObjReadVar != null) + { + _mArrReadVector = _mObjReadVar as Array; + if (_mArrReadVector != null) + { + UpdateLimitState(0, (int)_mArrReadVector.GetValue(1)); //获取X轴 + UpdateLimitState(1, (int)_mArrReadVector.GetValue(0)); + UpdateLimitState(2, (int)_mArrReadVector.GetValue(4)); + } + } + + UpdateSingleAxisStatus(); //刷新运动状态 + + #endregion 更新限位及运动状态 + + #region 到位判断 + + if (IsMotionInPose()) + { + _currentMotionState = MotionStates.InPos; + //DebugDfn.AddLogText("运动到位"); + } + else + { + _currentMotionState = MotionStates.Moving; + //DebugDfn.AddLogText("运动中"); + isInPose = false; + } + + //增加判断 运动中到 运动到位,主动发送READY + if ( + _currentMotionState == MotionStates.InPos + && _currentMotionState != _currentMotorStateLast + ) + { + DebugDfn.AddLogText("运动到位"); + isInPose = true; + + if (isCounting) + { + m_nInPosCount++; + DebugDfn.AddLogText("到位次数" + m_nInPosCount); + } + } + + _currentMotorStateLast = _currentMotionState; + + #endregion 到位判断 + } + catch (Exception ex) + { + DebugDfn.AddLogText("ACS平台刷新异常" + ex); + MessageBox.Show( + ex.Message, + "ERROR", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + Btn_ACSStop_Click(null, null); + } + } + } + + private void Btn_ACSStart_Click(object sender, EventArgs e) //连接 + { + btn_ACSStart.Enabled = false; + btn_ACSStop.Enabled = true; + + try + { + if (rdoTCP.Checked) + { + // TCP/IP (Ethernet) TCP/IP (Ethernet) + _acs.OpenCommEthernetTCP( + txtIP.Text, // IP Address (Default : 10.0.0.100) + Convert.ToInt32(txtPort.Text.Trim()) + ); // TCP/IP Port nubmer (default : 701) + } + else if (rdoSimu.Checked) + { + // Simmulation mode + _acs.OpenCommSimulator(); + } + + _mAcsConnected = true; + + //运动相关初始化操作 + InitMotion(); + + // 启动定时器 + tmrMonitor.Interval = 50; + tmrMonitor.Start(); + } + catch (COMException comex) + { + MessageBox.Show( + "Connection fail", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + Debug.WriteLine("Connection fail" + comex.Message); + + _mAcsConnected = false; + } + catch (Exception ex) + { + DebugDfn.AddLogText("ACS平台连接异常" + ex); + MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void Btn_ACSStop_Click(object sender, EventArgs e) //断开连接 + { + if (_mAcsConnected) + { + //DisableFaultEvent(); //取消注册事件 + _acs.CloseComm(); + } + + tmrMonitor.Stop(); + _mAcsConnected = false; + btn_ACSStart.Enabled = true; + btn_ACSStop.Enabled = false; + } + + private void UpdateLimitState(int axisNo, int fault) //刷新限位 + { + if (axisNo < MaxUiLimitCnt) + { + if ((fault & (int)SafetyControlMasks.ACSC_SAFETY_LL) != 0) + _mLblLeftLimit[axisNo].Image = Resources.Error; + else + _mLblLeftLimit[axisNo].Image = Resources.Off; + if ((fault & (int)SafetyControlMasks.ACSC_SAFETY_RL) != 0) + _mLblRightLimit[axisNo].Image = Resources.Error; + else + _mLblRightLimit[axisNo].Image = Resources.Off; + } + } + + public int TranslateAxisNumber(Axis originalAxisNumber) + { + int newAxisNumber = -1; + + switch (originalAxisNumber) + { + case Axis.ACSC_AXIS_1: //X轴 + newAxisNumber = 0; + break; + + case Axis.ACSC_AXIS_0: + newAxisNumber = 1; //Y轴 + break; + + case Axis.ACSC_AXIS_4: + newAxisNumber = 2; + break; + } + + return newAxisNumber; + } + + private void UpdateSingleAxisStatus() + { + Axis axis = 0; + int _axisNo = 0; + for (int i = 0; i < UseAxis.Length; i++) + { + axis = UseAxis[i]; + + _axisNo = TranslateAxisNumber(UseAxis[i]); + + // Get Motor State ACSPL+ Variable : MST (integer) + _mNMotorState = _acs.GetMotorState(axis); + + if (_axisNo == -1) + { + return; + } + + // 运动中 + if ((_mNMotorState & MotorStates.ACSC_MST_MOVE) != 0) + { + _mlblMoving[_axisNo].Image = Resources.On; + } + else + { + _mlblMoving[_axisNo].Image = Resources.Off; + } + + // 就位 + if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) + { + _mlblInPos[_axisNo].Image = Resources.On; + } + else + { + _mlblInPos[_axisNo].Image = Resources.Off; + } + + // 加速 + if ((_mNMotorState & MotorStates.ACSC_MST_ACC) != 0) + { + _mlblAcc[_axisNo].Image = Resources.On; + } + else + { + _mlblAcc[_axisNo].Image = Resources.Off; + } + + // 使能 + if ((_mNMotorState & MotorStates.ACSC_MST_ENABLE) != 0) + { + _mlblEnable[_axisNo].Image = Resources.On; + axisEnabled[_axisNo] = true; //轴使能 + } + else + { + _mlblEnable[_axisNo].Image = Resources.Off; + axisEnabled[_axisNo] = false; + } + } + + totalAxisEnabled = CalculateTotalEnabled(axisEnabled, 0, 1, 8); + DebugDfn.AddLogText($"总的使能状态为:{(totalAxisEnabled ? "使能" : "未使能")}"); + } + + private void IsHomed() //读取回家状态,当未回家时执行回家指令 + { + // 1、连接状态检查,如果未连接,提示 + if (!_mAcsConnected) + { + DebugDfn.AddLogText("[IsHomed] ACS平台未连接,请先点击连接"); + MessageBox.Show( + "ACS平台未连接,请先点击连接", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + + // 2、回家状态检查是否已经回家 + if (_mAcsConnected && _homeStates == HomeStates.Homed) + { + //弹窗提示 + MessageBox.Show( + "轴已经回家", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + + // 3、读取回家状态 + if (_acs != null && _mAcsConnected) + { + var yawHome = _acs.ReadVariable("YAW_HOME_DONE"); + + if (Convert.ToBoolean(yawHome)) + { + //弹窗提示 + MessageBox.Show( + "轴已经回家", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + _homeStates = HomeStates.Homed; + return; + } + // 4、执行回家指令,弹窗等待用户确认 + + DialogResult result = MessageBox.Show( + "轴未回家,即将执行回家指令", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + + if (result == DialogResult.OK) + { + // 在这里执行接下来的操作,例如回家指令 + + _acs.RunBuffer(ProgramBuffer.ACSC_BUFFER_6, null); //执行回家指令,这里的buffer6是回家指令的buffer + _homeStates = HomeStates.Homing; + _currentMotionState = MotionStates.Moving; + DebugDfn.AddLogText("回家运动中"); + + //等待回家完成 + for (int i = 0; i < UseAxis.Length; i++) + { + _acs.WaitMotionEnd(UseAxis[i], _motionTimeout); //等待回家完成 + } + + _homeStates = HomeStates.Homed; + _currentMotionState = MotionStates.InPos; + DebugDfn.AddLogText("回家完成"); + } + else + { + //弹窗提醒 + MessageBox.Show( + "点击了取消,未进行任何动作", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + } + } + + private void InitMotion() //定义 运动状态初始化函数,包括内部变量,轴启用,回家判断等 + { + string strTemp; + //运动相关变量初始化 + _homeStates = HomeStates.None; + _currentMotionState = MotionStates.None; + _currentMotionState = MotionStates.None; + + if (!_mAcsConnected) + { + MessageBox.Show("未连接运动平台,请先连接运动平台"); + return; + } + + //轴启用,加电 + _acs.EnableM(UseAxis); + for (int i = 0; i < UseAxis.Length; i++) + { + _acs.WaitMotorEnabled(UseAxis[i], 1, _motionTimeout); //等待电机使能 + } + + DebugDfn.AddLogText("电机已启用"); + + //回家 + IsHomed(); + + //设置定位速度 + SetSpeedXyz(MotionSpeedxy, MotionSpeedz); + + //获取轴数量 + strTemp = _acs.Transaction("?SYSINFO(13)"); + _mNTotalAxis = Convert.ToInt32(strTemp.Trim()); + _mLblLeftLimit = new Label[MaxUiLimitCnt]; //左限位 + _mLblLeftLimit[0] = lblLL0; + _mLblLeftLimit[1] = lblLL1; + _mLblLeftLimit[2] = lblLL2; + + _mLblRightLimit = new Label[MaxUiLimitCnt]; //右限位 + _mLblRightLimit[0] = lblRL0; + _mLblRightLimit[1] = lblRL1; + _mLblRightLimit[2] = lblRL2; + + _mlblMoving = new Label[MaxUiLimitCnt]; //运动中 + _mlblMoving[0] = lblMoving0; + _mlblMoving[1] = lblMoving1; + _mlblMoving[2] = lblMoving2; + + _mlblAcc = new Label[MaxUiLimitCnt]; // 加速中 + _mlblAcc[0] = lblAcc0; + _mlblAcc[1] = lblAcc1; + _mlblAcc[2] = lblAcc2; + + _mlblInPos = new Label[MaxUiLimitCnt]; //就位 + _mlblInPos[0] = lblInPos0; + _mlblInPos[1] = lblInPos1; + _mlblInPos[2] = lblInPos2; + + _mlblEnable = new Label[MaxUiLimitCnt]; //轴使能 + _mlblEnable[0] = lblEnable0; + _mlblEnable[1] = lblEnable1; + _mlblEnable[2] = lblEnable2; + + //EnableFaultEvent(); //订阅错误事件 + } + + public static bool IsWithinLimit(Point3D point) //判断点是否在行程范围内 + { + if ( + point.X >= XMinstrokesw + && point.X <= XMaxstrokesw + && point.Y >= YMinstrokesw + && point.Y <= YMaxstrokesw + && point.Z >= ZMinstrokesw + && point.Z <= ZMaxstrokesw + ) + { + return true; + } + + DebugDfn.AddLogText(point.X.ToString() + " " + point.Y.ToString() + " " + point.Z.ToString()); + //打印 XMinstrokesw 等值 + DebugDfn.AddLogText("XMinstrokesw超限: " + XMinstrokesw); + DebugDfn.AddLogText("XMaxstrokesw超限: " + XMaxstrokesw); + DebugDfn.AddLogText("YMinstrokesw超限: " + YMinstrokesw); + DebugDfn.AddLogText("YMaxstrokesw超限: " + YMaxstrokesw); + DebugDfn.AddLogText("ZMinstrokesw超限: " + ZMinstrokesw); + DebugDfn.AddLogText("ZMaxstrokesw超限: " + ZMaxstrokesw); + return false; + } + + public static bool IsWithinLimit(Point point) //判断点是否在行程范围内 + { + if ( + point.X >= XMinstrokesw + && point.X <= XMaxstrokesw + && point.Y >= YMinstrokesw + && point.Y <= YMaxstrokesw + && point.Z >= ZMinstrokesw + && point.Z <= ZMaxstrokesw + ) + { + return true; + } + + return false; + } + + public void SetPositionXyz(Point3D point3D) //运动到指定位置 + { + if (!_mAcsConnected) + { + DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); + MessageBox.Show( + "ACS平台未连接,请先点击连接", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + + if (_currentMotionState != MotionStates.Moving) + { + _currentMotionState = MotionStates.Moving; //设置当前运动状态 + //判断 point3D是否合法 + if (point3D != null) + { + if (IsWithinLimit(point3D)) //判断点是否在行程范围内 + { + double[] pointsArray = { point3D.X, point3D.Y, point3D.Z }; + //判断各轴使能状态,如果未使能,则使能 + + if (!totalAxisEnabled) + { + _acs.EnableM(UseAxis); + for (int i = 0; i < UseAxis.Length; i++) + { + _acs.WaitMotorEnabled(UseAxis[i], 1, _motionTimeout); //等待电机使能 + } + + //DebugDfn.AddLogText("电机已启用"); + } + + //执行运动指令 + _acs.ToPointM(MotionFlags.ACSC_NONE, UseAxis, pointsArray); //多轴运动到指定位置 + } + else + { + DebugDfn.AddLogText("目标位置超出行程范围,请重新设置"); + //MessageBox.Show( + // "目标位置超出行程范围,请重新设置", + // "异常", + // MessageBoxButtons.OK, + // MessageBoxIcon.Error + //); + } + } + else + { + DebugDfn.AddLogText("目标位置为空,请重新设置"); + MessageBox.Show( + "目标位置为空,请重新设置", + "异常", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + } + + private Point3D GetPositionXyz(int positionMode = 1) //获取当前位置 + { + double xPosition = 0, + yPosition = 0, + zPosition = 0; + Point3D point3D = new Point3D(xPosition, yPosition, zPosition); + if (!_mAcsConnected) + { + DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); + MessageBox.Show( + "ACS平台未连接,请先点击连接", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return point3D; + } + + //获取当前位置, 两种 GetRPosition,GetFPosition + if (positionMode == 1) + { + //获取反馈位置 Feedback position (Encoder value) ACSPL+ Variable : FPO (real) + xPosition = _acs.GetFPosition(UseAxis[0]); + yPosition = _acs.GetFPosition(UseAxis[1]); + zPosition = _acs.GetFPosition(UseAxis[2]); + //DebugDfn.AddLogText("反馈位置: " + xPosition + " " + yPosition + " " + zPosition); + } + else + { + //获取参考位置 ACSPL+ Variable : RPOS (real) + xPosition = _acs.GetRPosition(UseAxis[0]); + yPosition = _acs.GetRPosition(UseAxis[1]); + zPosition = _acs.GetRPosition(UseAxis[2]); + DebugDfn.AddLogText("参考位置: " + xPosition + " " + yPosition + " " + zPosition); + } + + //构造point3D格式 + point3D = new Point3D(xPosition, yPosition, zPosition); + + return point3D; + } + + private void SetSpeedXyz(double speedxy, double speedz) //获取运动参数 + { + //获取实际速度 + double feedbackVelocity = (double) + _acs.ReadVariable("FVEL", ProgramBuffer.ACSC_NONE, 0, 0); + DebugDfn.AddLogText("实际速度: " + feedbackVelocity); + + //设置Y轴 速度参数 + _acs.SetVelocity(Axis.ACSC_AXIS_0, speedxy); + _acs.SetAcceleration(Axis.ACSC_AXIS_0, speedxy * 10); + _acs.SetDeceleration(Axis.ACSC_AXIS_0, speedxy * 10); + _acs.SetKillDeceleration(Axis.ACSC_AXIS_0, speedxy * 100); + _acs.SetJerk(Axis.ACSC_AXIS_0, speedxy * 100); + + //设置X轴速度参数 + _acs.SetVelocity(Axis.ACSC_AXIS_1, speedxy); + _acs.SetAcceleration(Axis.ACSC_AXIS_1, speedxy * 10); + _acs.SetDeceleration(Axis.ACSC_AXIS_1, speedxy * 10); + _acs.SetKillDeceleration(Axis.ACSC_AXIS_1, speedxy * 100); + _acs.SetJerk(Axis.ACSC_AXIS_1, speedxy * 100); + + //设置Z轴速度参数 + + _acs.SetVelocity(Axis.ACSC_AXIS_4, speedz); + _acs.SetAcceleration(Axis.ACSC_AXIS_4, speedz * 10); + _acs.SetDeceleration(Axis.ACSC_AXIS_4, speedz * 10); + _acs.SetKillDeceleration(Axis.ACSC_AXIS_4, speedz * 100); + _acs.SetJerk(Axis.ACSC_AXIS_4, speedz * 100); + + DebugDfn.AddLogText($"速度设置完成 "); + } + + private void rtb_quick_loc_Click(object sender, EventArgs e) //快速定位 + { + // 获取文本框的值 + double x = double.Parse(rtb_SetX.Text); + double y = double.Parse(rtb_Sety.Text); + double z = double.Parse(rtb_SetZ.Text); + + // 构造 Point3D 对象 + Point3D point = new Point3D(x, y, z); + + SetPositionXyz(point); + } + + private static bool CalculateTotalEnabled(bool[] axisEnabled, params int[] axisIndices) //判断轴使能状态 + { + bool totalEnabled = true; + foreach (int index in axisIndices) + { + bool isEnabled = axisEnabled[index]; + if (!isEnabled) + { + totalEnabled = false; + break; + } + } + return totalEnabled; + } + + private void rtb_stop_Click(object sender, EventArgs e) + { + try + { + Axis[] m_arrAxisList = new Axis[] + { + Axis.ACSC_AXIS_0, + Axis.ACSC_AXIS_1, + Axis.ACSC_AXIS_4, + Axis.ACSC_NONE, + }; + + if (m_arrAxisList != null) + _acs.HaltM(m_arrAxisList); + + DebugDfn.AddLogText("立即停止 已发送命令"); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + Debug.WriteLine(ex.Message); + } + } + + private void rtb_home_Click(object sender, EventArgs e) + { + //弹窗提醒用户,是否执行回家指令 + DialogResult result = MessageBox.Show( + "是否执行回家指令", + "提示", + MessageBoxButtons.OKCancel, + MessageBoxIcon.Information + ); + + if (result == DialogResult.Cancel) + { + return; + } + + DebugDfn.AddLogText("回家指令已发送"); + if (!_mAcsConnected) + { + DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); + MessageBox.Show( + "ACS平台未连接,请先点击连接", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + + if (_currentMotionState != MotionStates.Moving) + { + _currentMotionState = MotionStates.Moving; //设置当前运动状态 + + _acs.RunBuffer(ProgramBuffer.ACSC_BUFFER_6, null); //执行回家指令,这里的buffer6是回家指令的buffer + _homeStates = HomeStates.Homing; + _currentMotionState = MotionStates.Moving; + DebugDfn.AddLogText("回家运动中"); + + //等待回家完成 + for (int i = 0; i < UseAxis.Length; i++) + { + _acs.WaitMotionEnd(UseAxis[i], _motionTimeout); //等待回家完成 + } + + _homeStates = HomeStates.Homed; + _currentMotionState = MotionStates.InPos; + DebugDfn.AddLogText("回家完成"); + } + } + + private void rtb_etalon_Click(object sender, EventArgs e) //etalon校准 + { + DebugDfn.AddLogText("Etalon校准"); + //判断通讯对象是否存在 + if (_acs == null || !_acs.IsConnected) + { + DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); + + // 在合适的位置调用 MessageBox.Show() 方法 + MessageBox.Show( + "未建立与运动平台通讯,请在主界面先建立通讯", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + + return; + } + else + { + EtalonForm etalonForm = new EtalonForm(this); + etalonForm.Show(); + } + } + + private void rtb_monitor_Click(object sender, EventArgs e) + { + DebugDfn.AddLogText("监控界面"); + //判断通讯对象是否存在 + if (_acs == null || !_acs.IsConnected) + { + DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); + + // 在合适的位置调用 MessageBox.Show() 方法 + MessageBox.Show( + "未建立与运动平台通讯,请在主界面先建立通讯", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + + return; + } + else + { + MonitorForm monitorForm = new MonitorForm(this); + monitorForm.Show(); + } + } + + #endregion ACS平台相关 + + #region 菜单栏 + + private void btn_motion_Click(object sender, EventArgs e) + { + //判断通讯对象是否存在 + if (_acs == null || !_acs.IsConnected) + { + DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); + + // 在合适的位置调用 MessageBox.Show() 方法 + MessageBox.Show( + "未建立与运动平台通讯,请在主界面先建立通讯", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + + return; + } + else + { + Motion motion = new Motion(this); + motion.Show(); + } + } + + private void Rtb_about_Click(object sender, EventArgs e) //关于界面 + { + AboutBox mAboutBox = new AboutBox(); + mAboutBox.StartPosition = FormStartPosition.CenterScreen; + mAboutBox.Show(); + } + + private void rtb_demo_Click(object sender, EventArgs e) + { + DemoShow demoShow = new DemoShow(_acs); + demoShow.Show(); + demoShow.BringToFront(); + } + + private void Timer_RefreshUI_Tick(object sender, EventArgs e) //UI刷新 + { + //状态灯刷新 + lamp_acs.State = _mAcsConnected ? LampColor.Green : LampColor.Silver; + lamp_hexcal.State = _mBHexcalConnected ? LampColor.Green : LampColor.Silver; + + //时间栏 + //获取当前时间,构造形如 精确到秒,例如 2023-10-08 16:01:23 + rle_timer.Text = "当前时间: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + + Plot2D(_pointCloud); //绘图 + + //更新位置 + if (_mPoint3D != null) + { + rtb_xPos.Text = _mPoint3D.X.ToString("F3"); + rtb_yPos.Text = _mPoint3D.Y.ToString("F3"); + rtb_zPos.Text = _mPoint3D.Z.ToString("F3"); + } + } + + #endregion 菜单栏 + } +} \ No newline at end of file diff --git a/HexcalMC/MainFrom.resx b/HexcalMC/MainForm.resx similarity index 99% rename from HexcalMC/MainFrom.resx rename to HexcalMC/MainForm.resx index 9c064aa..55aa34e 100644 --- a/HexcalMC/MainFrom.resx +++ b/HexcalMC/MainForm.resx @@ -632,13 +632,13 @@ - 328, 17 + 164, 17 17, 17 - 25 + 118 diff --git a/HexcalMC/MainFrom.cs b/HexcalMC/MainFrom.cs deleted file mode 100644 index 28bc916..0000000 --- a/HexcalMC/MainFrom.cs +++ /dev/null @@ -1,1636 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Runtime.InteropServices; -using System.Threading; -using System.Windows.Forms; -using ACS.SPiiPlusNET; -using HexcalMC.Base; -using HexcalMC.Form; -using HexcalMC.Hexcal; -using HexcalMC.Properties; -using Telerik.WinControls.UI; -using static HexcalMC.EtalonForm; - -namespace HexcalMC -{ - //定一个 回家状态枚举,包括 从未回家,正在回家,已经回家 - public enum HomeStates - { - None, //默认状态 - NotHome, //未回家 - Homing, //回家中 - Homed //回家完成 - , - } - - //定义 运动状态枚举,包括 正在运动,运动到位,Jog运动 - public enum MotionStates - { - None, //默认状态 - Moving, //运动中 - InPos, //运动到位 - Jogging //jog中 - , - } - - public partial class MainFrom : RadRibbonForm - { - private readonly List _pointCloud = new List(); //运动中点集合 - - private bool _mBHexcalConnected; - - private TcpIpServer _mTcpIpServer; //创建tcpserver,用于接收hexcal传来的指令,并解析传递平台 - - private int m_nTotalAxis; //定义总轴数 - private Axis[] m_arrAxisList = null; - - //定一个运动到位次数的变量和三个方法,开始统计运动到位次数,停止统计运动到位次数,获取运动到位次数 - - #region 运动到位次数 - - - // 记录运动到位次数的变量 - private int m_nInPosCount = 0; - private bool isCounting = false; - private bool isInPose = false; //运动到位状态 - - // 开始统计运动到位次数 - public void StartCounting() - { - isCounting = true; - m_nInPosCount = 0; // 重置计数器 - } - - // 停止统计运动到位次数 - public void StopCounting() - { - isCounting = false; - m_nInPosCount = 0; // 重置计数器 - } - - // 获取运动到位次数 - public int GetInPosCount() - { - return m_nInPosCount; - } - - public bool GetIsMoving() - { - return isInPose; - } - #endregion - - public MainFrom() - { - InitializeComponent(); - - // 处理未被捕获的线程异常 - Application.ThreadException += Application_ThreadException; - - // 处理未被捕获的非UI线程异常 - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - } - - private void MainFrom_Load(object sender, EventArgs e) - { - FormBorderStyle = FormBorderStyle.FixedSingle; // 设置窗体边框样式为固定大小 - MaximizeBox = false; // 禁用窗体的最大化按钮 - DebugDfn.textBox_Msg = TextBoxMsg; - - //加载配置文件 - LoadConfig(); - _acs = new Api(); //初始化 ACS运动控制类 - - //启动界面刷新 - timer_RefreshUI.Start(); - - Point3D point3D = new Point3D(800, 980, -290); - IsWithinLimit(point3D); - - } - - private void MainFrom_Shown(object sender, EventArgs e) //窗体显示准备好接受用户输入时发生 - { - ////启动服务端,用于接收hexcal传来的指令 - //StartServer(); - - //if (_enableAcs) - //{ - // Btn_ACSStart_Click(null, null); //模拟连接运动平台 - //} - } - - private void MainFrom_FormClosed(object sender, FormClosedEventArgs e) - { - MyBase.TraceWriteLine("关闭程序"); - DebugDfn._strEndTime = DateTime.Now.ToString("yyyy.MM.dd HH-mm-ss"); - timer_RefreshUI.Stop(); - - Btn_StopServer_Click(null, null); - Btn_ACSStop_Click(null, null); //关闭ACS - - string copyFileName = - DebugDfn.StrDebugSavePath - + "\\Debug(" - + DebugDfn._strStartTime - + " To " - + DebugDfn._strEndTime - + ")" - + ".txt"; - if (!File.Exists(DebugDfn.StrDebugSavePath)) - { - //创建文件夹 DebugDfn.StrDebugSavePath - Directory.CreateDirectory(DebugDfn.StrDebugSavePath); - } - - File.Copy(DebugDfn.StrDebugFile, copyFileName); - - if (Errors.ErrorWrite != null) - Errors.ErrorWrite.Close(); - if (Errors.OtherWrite != null) - Errors.OtherWrite.Close(); - if (Errors.StatusWrite != null) - Errors.StatusWrite.Close(); - } - - private void LoadConfig() //加载配置文件 - { - //判断配置文件是否存在 - if (!File.Exists(StrConfigFile)) - { - MessageBox.Show( - "配置文件不存在,请检查配置文件", - "异常", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - return; - } - - MotionSpeedxy = FileIni.ReadDouble(StrConfigFile, "MOTOR", "MOTION_SPEEDXY"); //运动定位速度 - MotionSpeedz = FileIni.ReadDouble(StrConfigFile, "MOTOR", "MOTION_SPEEDZ"); - - //正限位 - XMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "X_MAXSTROKESW"); - YMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Y_MAXSTROKESW"); - ZMaxstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Z_MAXSTROKESW"); - - //负限位 - XMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "X_MINSTROKESW"); - YMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Y_MINSTROKESW"); - ZMinstrokesw = FileIni.ReadDouble(StrConfigFile, "MOTOR", "Z_MINSTROKESW"); - - port = FileIni.ReadInt(StrConfigFile, "MOTOR", "Port"); - DebugDfn.AddLogText($"当前监听端口配置为: {port}"); - DebugDfn.AddLogText($"当前xy运动速度配置为: {MotionSpeedxy}"); - DebugDfn.AddLogText($"当前z运动速度配置为: {MotionSpeedz}"); - } - - private void Plot2D(List pointCloud) - { - // 清空画布 - formsPlot1.Plot.Clear(); - - //pointCloud 是否为空 - if (pointCloud.Count <= 0) - { - return; - } - - List dataX = new List(); - List dataY = new List(); - - foreach (Point3D point3D in pointCloud) - { - dataX.Add(point3D.X); - dataY.Add(point3D.Y); - } - - formsPlot1.Plot.AddScatter(dataX.ToArray(), dataY.ToArray()); - formsPlot1.Refresh(); - } - - #region 运动平台变量区 - - public Api _acs; - - private const int MaxUiLimitCnt = 24; - private int _mNTotalAxis; - - //private int _mNTotalBuffer = 0; - //private Axis[] _mArrAxisList = null; - public bool _mAcsConnected; //ACS通讯状态 - - // For update values - private MotorStates _mNMotorState; //运动状态 - - private ProgramStates _mNProgramState; //程序状态 - private object _mObjReadVar; - private Array _mArrReadVector; - private double _mLfRPos, - _mLfFPos, - _mLfPe, - _mLfFvel; //参考位置,反馈位置 位置误差 反馈速度 double类型 - private int _mNValues, - _mNOutputState; - - private Label[] _mLblLeftLimit; //左限位 - private Label[] _mLblRightLimit; //右限位 - private Label[] _mlblMoving; //运动中 - private Label[] _mlblAcc; //加速中 - private Label[] _mlblInPos; //轴就位 - private Label[] _mlblEnable; //使能 - private bool[] axisEnabled = new bool[MaxUiLimitCnt]; //轴使能状态 - public bool totalAxisEnabled = false; - - private HomeStates _homeStates; //回家状态 - private MotionStates _currentMotionState; //当前运动状态 - private MotionStates _currentMotorStateLast; - private readonly int _motionTimeout = 50000; //定义运动超时时间 - - //定义启用的轴,后面运动时会使用 - public static Axis[] UseAxis = - { - Axis.ACSC_AXIS_1, - Axis.ACSC_AXIS_0, - Axis.ACSC_AXIS_8, - Axis.ACSC_NONE, - }; - - //定义 XYZ三个轴的左右行程范围 - public string StrConfigFile = Application.StartupPath + "\\File\\config.ini"; - - public static double MotionSpeedxy = 60; - public static double MotionSpeedz = 30; - public static double XMaxstrokesw = 730; //正限位 - public static double YMaxstrokesw = 1000; - public static double ZMaxstrokesw = 5; - - public static double XMinstrokesw = -30; //负限位 - public static double YMinstrokesw = -10; - public static double ZMinstrokesw = -280; - public static int port = 1234; //默认监听端口 - - //定义一个3D点,存储当前平台实时位置 - public Point3D _mPoint3D; - - #endregion 运动平台变量区 - - #region hexcal软件交互 - - private void StartServer() - { - // 对_mTcpIpServer增加判断是否已经启动且存在设备连接 - if (_mTcpIpServer != null && _mTcpIpServer.ConnectStatus) - { - //弹窗提醒已经启动 - MyBase.TraceWriteLine("TCP服务端已经启动,请勿重复启动"); - MessageBox.Show( - "TCP服务端已经启动,请勿重复启动", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - - //启动服务器,并获取数据,解析 - _mTcpIpServer = new TcpIpServer(IPAddress.Any.ToString(), Convert.ToString(port)); - _mTcpIpServer.UseMode = 1; //设置通讯返回数据流格式 - try - { - //启动监听 - if (_mTcpIpServer.StartListen()) - { - //绑定两个事件 OnRaisedStatus 和OnRaisedMessage - _mTcpIpServer.OnRaisedMessage += ReceiveMessage; //接收消息回调 - _mTcpIpServer.OnRaisedStatus += ReceiveStatus; //连接状态 - _mTcpIpServer.DataReceived += ReceiveByte; - } - else - { - MessageBox.Show( - "TCP服务端启动失败,请检查网络连接,重新打开软件", - "异常", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } - } - catch (Exception ex) - { - DebugDfn.AddLogText("启动TCP服务端异常" + ex); - } - } - - private void ReceiveByte(object sender, byte[] e) - { - DebugDfn.AddLogText("接收到" + BitConverter.ToString(e)); - } - - private void ReceiveMessage(string clientIp, string msg) //接收的内容 - { - //打印ClientIP 和 Msg - DebugDfn.AddLogText("接收到" + clientIp + ": " + msg); - - //根据源地址的不同,执行不同处理 - string sourceIp = clientIp.Split(':')[0]; - switch (sourceIp) - { - case "100.0.0.1": - ParseHexcalMsg(msg); - break; - - case "100.0.0.2": - ParseHexcalMsg(msg); - break; - - default: - DebugDfn.AddLogText("未知来源,没有应答"); - break; - } - } - - public static string ConstructString(string variableName, double[] values) - { - string result = variableName + " "; - for (int i = 0; i < values.Length; i++) - { - result += values[i].ToString("F6"); - if (i < values.Length - 1) - { - result += ", "; - } - } - - return result; - } - - public static string ConstructPosString(Point3D point) - { - double[] values = { point.X, point.Y, point.Z, 0.0, 0.0, 0.0, 0.0 }; - return ConstructString("POS", values); - } - - public static Point3D ParsePoint3DFromCommand(string input) - { - string[] parts = input.Split(' ')[1].Split(','); - if (parts.Length >= 3) - { - double x = double.Parse(parts[0]); - double y = double.Parse(parts[1]); - double z = double.Parse(parts[2]); - return new Point3D(x, y, z); - } - - throw new ArgumentException("输入字符串格式不正确。"); - } - - private void CheckPlatformStatus() - { - //检查平台状态,如果运动中,返回BUSY,否则返回READY - if ( - _currentMotionState == MotionStates.None - || _currentMotionState == MotionStates.InPos - ) //默认或到位 - { - SendMsgToHexcal("READY"); - } - else - { - SendMsgToHexcal("BUSY"); - } - } - - private void ParseHexcalMsg(string msg) //编写一个Hexcal协议解析函数 - { - //DebugDfn.AddLogText("正在解析 " + msg); - - //去除Msg中\r\n - msg = msg.Replace("\r\n", ""); - - //判断是否含有故障ERROR字样 - if (msg.Contains("ERROR")) - { - //弹窗提醒 - MessageBox.Show("CMM错误", msg, MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - if (msg.Contains("\x02") || msg.Contains("\u0002")) - { - //DebugDfn.AddLogText("接收到STX,开始解析"); - CheckPlatformStatus(); - } - else if (msg.Contains("\x03") || msg.Contains("\u0003")) - { - CheckPlatformStatus(); - } - //else if (msg.Contains("^B")) //查询状态, READY或BUSY - //{ - // checkPlatformStatus(); - //} - else if (msg.Contains("CMMTYP")) //测量机类型 - { - SendMsgToHexcal("CMMTYP MA 19617, FDC V15.00, 10 8 3 , 0"); - } - else if (msg.Contains("VERSION")) //版本号 - { - SendMsgToHexcal("00-000-000-00000 FDC V51.04.0000 DATE: 12/21/22 TIME: 12:50:55"); - } - else if (msg.Contains("SHOW MAXSTROKESW")) //最大行程,根据实际情况填写 - { - //MAXSTROKESW 233.200000,346.500000,15.100000,0.000000,0.000000,0.000000,0.000000 - - double[] values = { XMaxstrokesw, YMaxstrokesw, ZMaxstrokesw, 0.0, 0.0, 0.0, 0.0 }; - string resultString = ConstructString("MAXSTROKESW", values); - SendMsgToHexcal(resultString); - } - else if (msg.Contains("SHOW MINSTROKESW")) //最小行程,根据实际情况填写 - { - //MINSTROKESW -68.800000,-55.500000,-286.900000,0.000000,0.000000,0.000000,0.000000 - - double[] values = { XMinstrokesw, YMinstrokesw, ZMinstrokesw, 0.0, 0.0, 0.0, 0.0 }; - string resultString = ConstructString("MINSTROKESW", values); - SendMsgToHexcal(resultString); - } - else if (msg.Contains("SHOW MAXVEL")) //最大速度 - { - SendMsgToHexcal( - "MAXVEL 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" - ); - } - else if (msg.Contains("SHOW MAXACC")) //最大加速度 - { - SendMsgToHexcal( - "MAXACC 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" - ); - } - else if (msg.Contains("SHOW SENSWKP")) - { - SendMsgToHexcal("X_ SENSWKP 4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); - } - else if (msg.Contains("SHOW X_SENSAXIS")) - { - SendMsgToHexcal("X_SENSAXIS 6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); - } - else if (msg.Contains("SHOW Y_SENSAXIS")) //查询Y轴 - { - SendMsgToHexcal("Y_SENSAXIS 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); - } - else if (msg.Contains("SHOW Z_SENSAXIS")) //查询Z轴 - { - SendMsgToHexcal("Z_SENSAXIS 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); - } - else if (msg.Contains("SHOW TEMPCOMPTYPE")) //温度补偿,温度补偿 >1 表示支持温度补偿,此处不支持 - { - SendMsgToHexcal("TEMPCOMPTYPE 0"); - } - else if (msg.Contains("READTP")) - { - SendMsgToHexcal("READTP 0.000000"); - } - else if (msg.Contains("SHOW ESTOP")) //查询急停状态,根据真是情况调整 - { - SendMsgToHexcal("ESTOP FALSE"); - } - else if (msg.Contains("CMHWST")) - { - SendMsgToHexcal("CMHWST 8257,0,1792,0"); - } - else if (msg.Contains("SHOW MOVPAR")) //查询速度 - { - SendMsgToHexcal( - "MOVPAR 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.00000 0,0.000000" - ); - } - else if (msg.Contains("SHOW MAXVEL")) //查询最大速度 - { - SendMsgToHexcal( - "MAXVEL 300.000000,300.000000,300.000000,0.000000,0.000000,0.000000,0.000000,0.000000" - ); - } - else if (msg.Contains("SHOW ACCEL")) //查询加速度 - { - SendMsgToHexcal( - "ACCEL 1000.000000,1000.000000,1000.000000,0.000000,0.000000,0.000000,0.000000,0.000000" - ); - } - else if (msg.Contains("MOVPAR")) //设置速度 xyz 轴的速度 - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("ACCEL")) //设置加速度 - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("PRBPIN")) //设置侧头偏置 - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("ENABLE TEMP")) //设置温度补偿 - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("WKPPAR")) - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("SCLTMP")) - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("DISABLE GEO")) - { - SendMsgToHexcal("%"); - } - else if (msg.Contains("AUTZER")) //回家指令 - { - SendMsgToHexcal("%"); //收到并执行,同时状态改为忙碌 - - //执行回家 - IsHomed(); - } - else if (msg.Contains("MOVABS")) //移动指令,解析移动位置 - { - //收到指令 ,形如 MOVABS 0.015000,127.172997,-114.897003,0.000000\r\n - SendMsgToHexcal("%"); - - Point3D point = ParsePoint3DFromCommand(msg); - SetPositionXyz(point); //开始移动 - - _pointCloud.Add(point); //添加到点集合 - } - else if (msg.Contains("GETPOS")) //获取位置 - { - //POS 167.553898,-55.400421,-208.548678,0.000000,0.000000,0.000000,0.000000 - Point3D point3D = GetPositionXyz(); //获取当前位置 - string resultString = ConstructPosString(point3D); - SendMsgToHexcal(resultString); - } - else - { - DebugDfn.AddLogText("未知命令,没有应答"); - } - } - - private void ReceiveStatus(TcpIpServer.EnumTcpIpServer iType, string msg) - { - //记录到日志 - DebugDfn.AddLogText(iType + " : " + msg); - - //根据连接状态,更新界面 - switch (iType) - { - case TcpIpServer.EnumTcpIpServer.ClientConnect: - _mBHexcalConnected = true; - break; - - default: - _mBHexcalConnected = false; - break; - } - } - - private void SendMsgToHexcal(string msg) - { - if (_mTcpIpServer == null) - return; - - //发送数据 - DebugDfn.AddLogText("回复 " + msg); - _mTcpIpServer.SendMessageToAllClients(msg += "\r\n"); //回复内容末尾加上\r\n,协议要求 - } - - private void Btn_StartServer_Click(object sender, EventArgs e) - { - Btn_StartServer.Enabled = false; - Btn_StopServer.Enabled = true; - StartServer(); - DebugDfn.AddLogText("TCP服务端启动成功 "); - } - - private void Btn_StopServer_Click(object sender, EventArgs e) - { - //关闭服务端 - if (_mTcpIpServer != null) - { - _mTcpIpServer.StopListen(); - } - - Btn_StopServer.Enabled = false; - Btn_StartServer.Enabled = true; - _mBHexcalConnected = false; - DebugDfn.AddLogText("TCP服务端已关闭"); - } - - #endregion hexcal软件交互 - - #region ACS平台相关 - - #region 异常抓取 - - //实现函数 - - private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) - { - // 防止程序终止 - MessageBox.Show( - "发生了未处理的异常:" + e.Exception.Message, - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - - private static void CurrentDomain_UnhandledException( - object sender, - UnhandledExceptionEventArgs e - ) - { - if (e.ExceptionObject is Exception ex) - { - HandleException(ex); - } - } - - private static void HandleException(Exception ex) - { - MessageBox.Show( - $"发生了未处理的异常:{ex.Message}", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - - //订阅报错 - private void EnableFaultEvent() - { - _acs.EnableEvent(Interrupts.ACSC_INTR_COMM_CHANNEL_CLOSED); - _acs.EnableEvent(Interrupts.ACSC_INTR_EMERGENCY); - _acs.EnableEvent(Interrupts.ACSC_INTR_SYSTEM_ERROR); - _acs.EnableEvent(Interrupts.ACSC_INTR_ETHERCAT_ERROR); - _acs.EnableEvent(Interrupts.ACSC_INTR_MOTOR_FAILURE); - _acs.EnableEvent(Interrupts.ACSC_INTR_MOTION_FAILURE); - _acs.EnableEvent(Interrupts.ACSC_INTR_PHYSICAL_MOTION_END); - _acs.COMMCHANNELCLOSED += _acs_COMMCHANNELCLOSED; - _acs.MOTORFAILURE += _acs_MOTORFAILURE; - _acs.MOTIONFAILURE += _acs_MOTIONFAILURE; - _acs.SYSTEMERROR += _acs_SYSTEMERROR; - _acs.ETHERCATERROR += _acs_ETHERCATERROR; - _acs.EMERGENCY += _acs_EMERGENCY; - _acs.PHYSICALMOTIONEND += _acs_PHYSICAL_MOTION_END; - } - - private void DisableFaultEvent() - { - _acs.COMMCHANNELCLOSED -= _acs_COMMCHANNELCLOSED; - _acs.MOTORFAILURE -= _acs_MOTORFAILURE; - _acs.MOTIONFAILURE -= _acs_MOTIONFAILURE; - _acs.SYSTEMERROR -= _acs_SYSTEMERROR; - _acs.ETHERCATERROR -= _acs_ETHERCATERROR; - _acs.EMERGENCY -= _acs_EMERGENCY; - _acs.PHYSICALMOTIONEND -= _acs_PHYSICAL_MOTION_END; - - _acs.DisableEvent(Interrupts.ACSC_INTR_COMM_CHANNEL_CLOSED); - _acs.DisableEvent(Interrupts.ACSC_INTR_EMERGENCY); - _acs.DisableEvent(Interrupts.ACSC_INTR_SYSTEM_ERROR); - _acs.DisableEvent(Interrupts.ACSC_INTR_ETHERCAT_ERROR); - _acs.DisableEvent(Interrupts.ACSC_INTR_MOTOR_FAILURE); - _acs.DisableEvent(Interrupts.ACSC_INTR_MOTION_FAILURE); - _acs.DisableEvent(Interrupts.ACSC_INTR_PHYSICAL_MOTION_END); - } - - //关联函数 - private void _acs_EMERGENCY(ulong param) - { - DebugDfn.AddLogText($"[EStopError] Error Message:{_acs.GetErrorString((int)param)}"); - } - - private void _acs_ETHERCATERROR(ulong param) - { - DebugDfn.AddLogText( - $"[EtherCatError] Error Message:{_acs.GetErrorString((int)param)}" - ); - } - - private void _acs_SYSTEMERROR(ulong param) - { - DebugDfn.AddLogText($"[SystemError] Error Message:{_acs.GetErrorString((int)param)}"); - } - - private void _acs_MOTIONFAILURE(AxisMasks axis) - { - for (int i = 0; i < _acs.GetAxesCount(); i++) - { - if (((int)axis & (int)Math.Pow(2, i)) == Math.Pow(2, i)) - { - if (_acs.GetMotionError((Axis)i) != 0) - { - //Motor无法自动捕获,需要在motion报错中获取 - int errorcode = _acs.GetMotionError((Axis)i); - - DebugDfn.AddLogText( - $"[MotionError] Axis:{i} Error Code:{errorcode} Error Message: {_acs.GetErrorString(errorcode)}" - ); - - int errorcodes = _acs.GetMotorError((Axis)i); - - DebugDfn.AddLogText( - $"[MotorError] Axis:{i} Error Code:{errorcodes} Error Message:{_acs.GetErrorString(errorcodes)}" - ); - } - } - } - } - - private void _acs_MOTORFAILURE(AxisMasks axis) - { - for (int i = 0; i < _acs.GetAxesCount(); i++) - { - if (((int)axis & (int)Math.Pow(2, i)) == Math.Pow(2, i)) - { - int errorcode = _acs.GetMotorError((Axis)i); - - DebugDfn.AddLogText( - $"[MotorError] Axis:{i} Error Code:{errorcode} Error Message:{_acs.GetErrorString(errorcode)}" - ); - } - } - } - - private void _acs_COMMCHANNELCLOSED(ulong param) - { - DebugDfn.AddLogText($"[CommError] Error Message:{_acs.GetErrorString((int)param)}"); - } - - private void _acs_PHYSICAL_MOTION_END(AxisMasks axis) - { - int bit = 0x01; - int axisNo = 0; - - for (int i = 0; i < 64; i++) - { - if ((int)axis == bit) - { - axisNo = i; - break; - } - - bit = bit << 1; - } - - //DebugDfn.AddLogText(string.Format(" - Axis {0}, Stoppped", axisNo)); - } - - #endregion 异常抓取 - - private void BtnEnable_Click(object sender, EventArgs e) //使能所有轴 - { - if (_mAcsConnected) - { - //!!!! Important !! Must insert '-1' at the last - _acs.EnableM(UseAxis); - } - else - { - //弹窗提醒尚未连接 - MessageBox.Show("未连接到运动平台,请先点击连接"); - } - } - - private void BtnDisable_Click(object sender, EventArgs e) //轴取消 - { - // Disable all of axes - _acs.DisableAll(); - } - - private bool IsMotionInPose() - { - bool x_inpose = false, - y_inpose = false, - z_inpose = false; - - _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_1); - if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) - { - x_inpose = true; - } - - _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_0); - if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) - { - y_inpose = true; - } - - _mNMotorState = _acs.GetMotorState(Axis.ACSC_AXIS_8); - if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) - { - z_inpose = true; - } - - if (x_inpose && y_inpose && z_inpose) - { - return true; - } - - return false; - } - - private void TmrMonitor_Tick(object sender, EventArgs e) //用于刷新状态 - { - if (_mAcsConnected) - { - try - { - _mPoint3D = GetPositionXyz(); //取平台当前位置 - #region 更新限位及运动状态 - - //左右限位刷新 - _mObjReadVar = _acs.ReadVariableAsVector( - "FAULT", - ProgramBuffer.ACSC_NONE, - 0, - _mNTotalAxis - 1 - ); - if (_mObjReadVar != null) - { - _mArrReadVector = _mObjReadVar as Array; - if (_mArrReadVector != null) - { - UpdateLimitState(0, (int)_mArrReadVector.GetValue(1)); //获取X轴 - UpdateLimitState(1, (int)_mArrReadVector.GetValue(0)); - UpdateLimitState(2, (int)_mArrReadVector.GetValue(8)); - } - } - - UpdateSingleAxisStatus(); //刷新运动状态 - #endregion 更新限位及运动状态 - - #region 到位判断 - - if (IsMotionInPose()) - { - _currentMotionState = MotionStates.InPos; - //DebugDfn.AddLogText("运动到位"); - } - else - { - _currentMotionState = MotionStates.Moving; - //DebugDfn.AddLogText("运动中"); - isInPose = false; - } - - //增加判断 运动中到 运动到位,主动发送READY - if ( - _currentMotionState == MotionStates.InPos - && _currentMotionState != _currentMotorStateLast - ) - { - DebugDfn.AddLogText("运动到位"); - isInPose = true; - - if (isCounting) - { - m_nInPosCount++; - DebugDfn.AddLogText("到位次数" + m_nInPosCount); - } - } - - _currentMotorStateLast = _currentMotionState; - - #endregion 到位判断 - } - catch (Exception ex) - { - DebugDfn.AddLogText("ACS平台刷新异常" + ex); - MessageBox.Show( - ex.Message, - "ERROR", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - Btn_ACSStop_Click(null, null); - } - } - } - - private void Btn_ACSStart_Click(object sender, EventArgs e) //连接 - { - btn_ACSStart.Enabled = false; - btn_ACSStop.Enabled = true; - - try - { - if (rdoTCP.Checked) - { - // TCP/IP (Ethernet) - // TCP/IP (Ethernet) - _acs.OpenCommEthernetTCP( - txtIP.Text, // IP Address (Default : 10.0.0.100) - Convert.ToInt32(txtPort.Text.Trim()) - ); // TCP/IP Port nubmer (default : 701) - } - else if (rdoSimu.Checked) - { - // Simmulation mode - _acs.OpenCommSimulator(); - } - - _mAcsConnected = true; - - //运动相关初始化操作 - InitMotion(); - - // 启动定时器 - tmrMonitor.Interval = 50; - tmrMonitor.Start(); - } - catch (COMException comex) - { - MessageBox.Show( - "Connection fail", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - Debug.WriteLine("Connection fail" + comex.Message); - - _mAcsConnected = false; - } - catch (Exception ex) - { - DebugDfn.AddLogText("ACS平台连接异常" + ex); - MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void Btn_ACSStop_Click(object sender, EventArgs e) //断开连接 - { - if (_mAcsConnected) - { - //DisableFaultEvent(); //取消注册事件 - _acs.CloseComm(); - } - - tmrMonitor.Stop(); - _mAcsConnected = false; - btn_ACSStart.Enabled = true; - btn_ACSStop.Enabled = false; - } - - private void UpdateLimitState(int axisNo, int fault) //刷新限位 - { - if (axisNo < MaxUiLimitCnt) - { - if ((fault & (int)SafetyControlMasks.ACSC_SAFETY_LL) != 0) - _mLblLeftLimit[axisNo].Image = Resources.Error; - else - _mLblLeftLimit[axisNo].Image = Resources.Off; - if ((fault & (int)SafetyControlMasks.ACSC_SAFETY_RL) != 0) - _mLblRightLimit[axisNo].Image = Resources.Error; - else - _mLblRightLimit[axisNo].Image = Resources.Off; - } - } - - public int TranslateAxisNumber(Axis originalAxisNumber) - { - int newAxisNumber = -1; - - switch (originalAxisNumber) - { - case Axis.ACSC_AXIS_1: //X轴 - newAxisNumber = 0; - break; - - case Axis.ACSC_AXIS_0: - newAxisNumber = 1; //Y轴 - break; - - case Axis.ACSC_AXIS_8: - newAxisNumber = 2; - break; - } - - return newAxisNumber; - } - - private void UpdateSingleAxisStatus() - { - Axis axis = 0; - int _axisNo = 0; - for (int i = 0; i < UseAxis.Length; i++) - { - axis = UseAxis[i]; - - _axisNo = TranslateAxisNumber(UseAxis[i]); - - // Get Motor State ACSPL+ Variable : MST (integer) - _mNMotorState = _acs.GetMotorState(axis); - - if (_axisNo == -1) - { - return; - } - - // 运动中 - if ((_mNMotorState & MotorStates.ACSC_MST_MOVE) != 0) - { - _mlblMoving[_axisNo].Image = Resources.On; - } - else - { - _mlblMoving[_axisNo].Image = Resources.Off; - } - - // 就位 - if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) - { - _mlblInPos[_axisNo].Image = Resources.On; - } - else - { - _mlblInPos[_axisNo].Image = Resources.Off; - } - - // 加速 - if ((_mNMotorState & MotorStates.ACSC_MST_ACC) != 0) - { - _mlblAcc[_axisNo].Image = Resources.On; - } - else - { - _mlblAcc[_axisNo].Image = Resources.Off; - } - - // 使能 - if ((_mNMotorState & MotorStates.ACSC_MST_ENABLE) != 0) - { - _mlblEnable[_axisNo].Image = Resources.On; - axisEnabled[_axisNo] = true; //轴使能 - } - else - { - _mlblEnable[_axisNo].Image = Resources.Off; - axisEnabled[_axisNo] = false; - } - } - - totalAxisEnabled = CalculateTotalEnabled(axisEnabled, 0, 1, 8); - DebugDfn.AddLogText($"总的使能状态为:{(totalAxisEnabled ? "使能" : "未使能")}"); - } - - private void IsHomed() //读取回家状态,当未回家时执行回家指令 - { - // 1、连接状态检查,如果未连接,提示 - if (!_mAcsConnected) - { - DebugDfn.AddLogText("[IsHomed] ACS平台未连接,请先点击连接"); - MessageBox.Show( - "ACS平台未连接,请先点击连接", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - - // 2、回家状态检查是否已经回家 - if (_mAcsConnected && _homeStates == HomeStates.Homed) - { - //弹窗提示 - MessageBox.Show( - "轴已经回家", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - - // 3、读取回家状态 - if (_acs != null && _mAcsConnected) - { - var yawHome = _acs.ReadVariable("YAW_HOME_DONE"); - - if (Convert.ToBoolean(yawHome)) - { - //弹窗提示 - MessageBox.Show( - "轴已经回家", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - _homeStates = HomeStates.Homed; - return; - } - // 4、执行回家指令,弹窗等待用户确认 - - DialogResult result = MessageBox.Show( - "轴未回家,即将执行回家指令", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - - if (result == DialogResult.OK) - { - // 在这里执行接下来的操作,例如回家指令 - - _acs.RunBuffer(ProgramBuffer.ACSC_BUFFER_6, null); //执行回家指令,这里的buffer6是回家指令的buffer - _homeStates = HomeStates.Homing; - _currentMotionState = MotionStates.Moving; - DebugDfn.AddLogText("回家运动中"); - - //等待回家完成 - for (int i = 0; i < UseAxis.Length; i++) - { - _acs.WaitMotionEnd(UseAxis[i], _motionTimeout); //等待回家完成 - } - - _homeStates = HomeStates.Homed; - _currentMotionState = MotionStates.InPos; - DebugDfn.AddLogText("回家完成"); - } - else - { - //弹窗提醒 - MessageBox.Show( - "点击了取消,未进行任何动作", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - } - } - - private void InitMotion() //定义 运动状态初始化函数,包括内部变量,轴启用,回家判断等 - { - string strTemp; - //运动相关变量初始化 - _homeStates = HomeStates.None; - _currentMotionState = MotionStates.None; - _currentMotionState = MotionStates.None; - - if (!_mAcsConnected) - { - MessageBox.Show("未连接运动平台,请先连接运动平台"); - return; - } - - //轴启用,加电 - _acs.EnableM(UseAxis); - for (int i = 0; i < UseAxis.Length; i++) - { - _acs.WaitMotorEnabled(UseAxis[i], 1, _motionTimeout); //等待电机使能 - } - - DebugDfn.AddLogText("电机已启用"); - - //回家 - IsHomed(); - - //设置定位速度 - SetSpeedXyz(MotionSpeedxy, MotionSpeedz); - - //获取轴数量 - strTemp = _acs.Transaction("?SYSINFO(13)"); - _mNTotalAxis = Convert.ToInt32(strTemp.Trim()); - _mLblLeftLimit = new Label[MaxUiLimitCnt]; //左限位 - _mLblLeftLimit[0] = lblLL0; - _mLblLeftLimit[1] = lblLL1; - _mLblLeftLimit[2] = lblLL2; - - _mLblRightLimit = new Label[MaxUiLimitCnt]; //右限位 - _mLblRightLimit[0] = lblRL0; - _mLblRightLimit[1] = lblRL1; - _mLblRightLimit[2] = lblRL2; - - _mlblMoving = new Label[MaxUiLimitCnt]; //运动中 - _mlblMoving[0] = lblMoving0; - _mlblMoving[1] = lblMoving1; - _mlblMoving[2] = lblMoving2; - - _mlblAcc = new Label[MaxUiLimitCnt]; // 加速中 - _mlblAcc[0] = lblAcc0; - _mlblAcc[1] = lblAcc1; - _mlblAcc[2] = lblAcc2; - - _mlblInPos = new Label[MaxUiLimitCnt]; //就位 - _mlblInPos[0] = lblInPos0; - _mlblInPos[1] = lblInPos1; - _mlblInPos[2] = lblInPos2; - - _mlblEnable = new Label[MaxUiLimitCnt]; //轴使能 - _mlblEnable[0] = lblEnable0; - _mlblEnable[1] = lblEnable1; - _mlblEnable[2] = lblEnable2; - - //EnableFaultEvent(); //订阅错误事件 - } - - public static bool IsWithinLimit(Point3D point) //判断点是否在行程范围内 - { - if ( - point.X >= XMinstrokesw - && point.X <= XMaxstrokesw - && point.Y >= YMinstrokesw - && point.Y <= YMaxstrokesw - && point.Z >= ZMinstrokesw - && point.Z <= ZMaxstrokesw - ) - { - - return true; - } - - DebugDfn.AddLogText(point.X.ToString() + " " + point.Y.ToString() + " " + point.Z.ToString()); - //打印 XMinstrokesw 等值 - DebugDfn.AddLogText("XMinstrokesw超限: " + XMinstrokesw); - DebugDfn.AddLogText("XMaxstrokesw超限: " + XMaxstrokesw); - DebugDfn.AddLogText("YMinstrokesw超限: " + YMinstrokesw); - DebugDfn.AddLogText("YMaxstrokesw超限: " + YMaxstrokesw); - DebugDfn.AddLogText("ZMinstrokesw超限: " + ZMinstrokesw); - DebugDfn.AddLogText("ZMaxstrokesw超限: " + ZMaxstrokesw); - return false; - } - - public static bool IsWithinLimit(Point point) //判断点是否在行程范围内 - { - if ( - point.X >= XMinstrokesw - && point.X <= XMaxstrokesw - && point.Y >= YMinstrokesw - && point.Y <= YMaxstrokesw - && point.Z >= ZMinstrokesw - && point.Z <= ZMaxstrokesw - ) - { - return true; - } - - return false; - } - - public void SetPositionXyz(Point3D point3D) //运动到指定位置 - { - if (!_mAcsConnected) - { - DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); - MessageBox.Show( - "ACS平台未连接,请先点击连接", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - - if (_currentMotionState != MotionStates.Moving) - { - _currentMotionState = MotionStates.Moving; //设置当前运动状态 - //判断 point3D是否合法 - if (point3D != null) - { - if (IsWithinLimit(point3D)) //判断点是否在行程范围内 - { - double[] pointsArray = { point3D.X, point3D.Y, point3D.Z }; - //判断各轴使能状态,如果未使能,则使能 - - if (!totalAxisEnabled) - { - _acs.EnableM(UseAxis); - for (int i = 0; i < UseAxis.Length; i++) - { - _acs.WaitMotorEnabled(UseAxis[i], 1, _motionTimeout); //等待电机使能 - } - - //DebugDfn.AddLogText("电机已启用"); - } - - //执行运动指令 - _acs.ToPointM(MotionFlags.ACSC_NONE, UseAxis, pointsArray); //多轴运动到指定位置 - } - else - { - DebugDfn.AddLogText("目标位置超出行程范围,请重新设置"); - //MessageBox.Show( - // "目标位置超出行程范围,请重新设置", - // "异常", - // MessageBoxButtons.OK, - // MessageBoxIcon.Error - //); - } - } - else - { - DebugDfn.AddLogText("目标位置为空,请重新设置"); - MessageBox.Show( - "目标位置为空,请重新设置", - "异常", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } - } - } - - private Point3D GetPositionXyz(int positionMode = 1) //获取当前位置 - { - double xPosition = 0, - yPosition = 0, - zPosition = 0; - Point3D point3D = new Point3D(xPosition, yPosition, zPosition); - if (!_mAcsConnected) - { - DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); - MessageBox.Show( - "ACS平台未连接,请先点击连接", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return point3D; - } - - //获取当前位置, 两种 GetRPosition,GetFPosition - if (positionMode == 1) - { - //获取反馈位置 Feedback position (Encoder value) ACSPL+ Variable : FPO (real) - xPosition = _acs.GetFPosition(UseAxis[0]); - yPosition = _acs.GetFPosition(UseAxis[1]); - zPosition = _acs.GetFPosition(UseAxis[2]); - //DebugDfn.AddLogText("反馈位置: " + xPosition + " " + yPosition + " " + zPosition); - } - else - { - //获取参考位置 ACSPL+ Variable : RPOS (real) - xPosition = _acs.GetRPosition(UseAxis[0]); - yPosition = _acs.GetRPosition(UseAxis[1]); - zPosition = _acs.GetRPosition(UseAxis[2]); - DebugDfn.AddLogText("参考位置: " + xPosition + " " + yPosition + " " + zPosition); - } - - //构造point3D格式 - point3D = new Point3D(xPosition, yPosition, zPosition); - - return point3D; - } - - private void SetSpeedXyz(double speedxy, double speedz) //获取运动参数 - { - //获取实际速度 - double feedbackVelocity = (double) - _acs.ReadVariable("FVEL", ProgramBuffer.ACSC_NONE, 0, 0); - DebugDfn.AddLogText("实际速度: " + feedbackVelocity); - - //设置Y轴 速度参数 - _acs.SetVelocity(Axis.ACSC_AXIS_0, speedxy); - _acs.SetAcceleration(Axis.ACSC_AXIS_0, speedxy * 10); - _acs.SetDeceleration(Axis.ACSC_AXIS_0, speedxy * 10); - _acs.SetKillDeceleration(Axis.ACSC_AXIS_0, speedxy * 100); - _acs.SetJerk(Axis.ACSC_AXIS_0, speedxy * 100); - - //设置X轴速度参数 - _acs.SetVelocity(Axis.ACSC_AXIS_1, speedxy); - _acs.SetAcceleration(Axis.ACSC_AXIS_1, speedxy * 10); - _acs.SetDeceleration(Axis.ACSC_AXIS_1, speedxy * 10); - _acs.SetKillDeceleration(Axis.ACSC_AXIS_1, speedxy * 100); - _acs.SetJerk(Axis.ACSC_AXIS_1, speedxy * 100); - - //设置Z轴速度参数 - - _acs.SetVelocity(Axis.ACSC_AXIS_8, speedz); - _acs.SetAcceleration(Axis.ACSC_AXIS_8, speedz * 10); - _acs.SetDeceleration(Axis.ACSC_AXIS_8, speedz * 10); - _acs.SetKillDeceleration(Axis.ACSC_AXIS_8, speedz * 100); - _acs.SetJerk(Axis.ACSC_AXIS_8, speedz * 100); - - DebugDfn.AddLogText($"速度设置完成 "); - } - - private void rtb_quick_loc_Click(object sender, EventArgs e) //快速定位 - { - // 获取文本框的值 - double x = double.Parse(rtb_SetX.Text); - double y = double.Parse(rtb_Sety.Text); - double z = double.Parse(rtb_SetZ.Text); - - // 构造 Point3D 对象 - Point3D point = new Point3D(x, y, z); - - SetPositionXyz(point); - } - - private static bool CalculateTotalEnabled(bool[] axisEnabled, params int[] axisIndices) //判断轴使能状态 - { - bool totalEnabled = true; - foreach (int index in axisIndices) - { - bool isEnabled = axisEnabled[index]; - if (!isEnabled) - { - totalEnabled = false; - break; - } - } - return totalEnabled; - } - - private void rtb_stop_Click(object sender, EventArgs e) - { - try - { - Axis[] m_arrAxisList = new Axis[] - { - Axis.ACSC_AXIS_0, - Axis.ACSC_AXIS_1, - Axis.ACSC_AXIS_8, - Axis.ACSC_NONE, - }; - - if (m_arrAxisList != null) - _acs.HaltM(m_arrAxisList); - - DebugDfn.AddLogText("立即停止 已发送命令"); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - Debug.WriteLine(ex.Message); - } - } - - private void rtb_home_Click(object sender, EventArgs e) - { - //弹窗提醒用户,是否执行回家指令 - DialogResult result = MessageBox.Show( - "是否执行回家指令", - "提示", - MessageBoxButtons.OKCancel, - MessageBoxIcon.Information - ); - - if (result == DialogResult.Cancel) - { - return; - } - - DebugDfn.AddLogText("回家指令已发送"); - if (!_mAcsConnected) - { - DebugDfn.AddLogText("ACS平台未连接,请先点击连接"); - MessageBox.Show( - "ACS平台未连接,请先点击连接", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - - if (_currentMotionState != MotionStates.Moving) - { - _currentMotionState = MotionStates.Moving; //设置当前运动状态 - - _acs.RunBuffer(ProgramBuffer.ACSC_BUFFER_6, null); //执行回家指令,这里的buffer6是回家指令的buffer - _homeStates = HomeStates.Homing; - _currentMotionState = MotionStates.Moving; - DebugDfn.AddLogText("回家运动中"); - - //等待回家完成 - for (int i = 0; i < UseAxis.Length; i++) - { - _acs.WaitMotionEnd(UseAxis[i], _motionTimeout); //等待回家完成 - } - - _homeStates = HomeStates.Homed; - _currentMotionState = MotionStates.InPos; - DebugDfn.AddLogText("回家完成"); - } - } - - private void rtb_etalon_Click(object sender, EventArgs e) //etalon校准 - { - DebugDfn.AddLogText("Etalon校准"); - //判断通讯对象是否存在 - if (_acs == null || !_acs.IsConnected) - { - DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); - - // 在合适的位置调用 MessageBox.Show() 方法 - MessageBox.Show( - "未建立与运动平台通讯,请在主界面先建立通讯", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - - return; - } - else - { - EtalonForm etalonForm = new EtalonForm(this); - etalonForm.Show(); - } - } - - private void rtb_monitor_Click(object sender, EventArgs e) - { - DebugDfn.AddLogText("监控界面"); - //判断通讯对象是否存在 - if (_acs == null || !_acs.IsConnected) - { - DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); - - // 在合适的位置调用 MessageBox.Show() 方法 - MessageBox.Show( - "未建立与运动平台通讯,请在主界面先建立通讯", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - - return; - } - else - { - MonitorForm monitorForm = new MonitorForm(this); - monitorForm.Show(); - } - - } - - #endregion ACS平台相关 - - #region 菜单栏 - - private void btn_motion_Click(object sender, EventArgs e) - { - //判断通讯对象是否存在 - if (_acs == null || !_acs.IsConnected) - { - DebugDfn.AddLogText("未建立与运动平台通讯,请在主界面先建立通讯"); - - // 在合适的位置调用 MessageBox.Show() 方法 - MessageBox.Show( - "未建立与运动平台通讯,请在主界面先建立通讯", - "提示", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - - return; - } - else - { - Motion motion = new Motion(this); - motion.Show(); - } - } - - private void Rtb_about_Click(object sender, EventArgs e) //关于界面 - { - AboutBox mAboutBox = new AboutBox(); - mAboutBox.StartPosition = FormStartPosition.CenterScreen; - mAboutBox.Show(); - } - - private void rtb_demo_Click(object sender, EventArgs e) - { - DemoShow demoShow = new DemoShow(_acs); - demoShow.Show(); - demoShow.BringToFront(); - } - - private void Timer_RefreshUI_Tick(object sender, EventArgs e) //UI刷新 - { - //状态灯刷新 - lamp_acs.State = _mAcsConnected ? LampColor.Green : LampColor.Silver; - lamp_hexcal.State = _mBHexcalConnected ? LampColor.Green : LampColor.Silver; - - //时间栏 - //获取当前时间,构造形如 精确到秒,例如 2023-10-08 16:01:23 - rle_timer.Text = "当前时间: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - - Plot2D(_pointCloud); //绘图 - - //更新位置 - if (_mPoint3D != null) - { - rtb_xPos.Text = _mPoint3D.X.ToString("F3"); - rtb_yPos.Text = _mPoint3D.Y.ToString("F3"); - rtb_zPos.Text = _mPoint3D.Z.ToString("F3"); - } - } - - #endregion 菜单栏 - } -} diff --git a/HexcalMC/Motion/DemoShow.cs b/HexcalMC/Motion/DemoShow.cs index 87d5dcd..a994570 100644 --- a/HexcalMC/Motion/DemoShow.cs +++ b/HexcalMC/Motion/DemoShow.cs @@ -1,7 +1,7 @@ -using System; +using ACS.SPiiPlusNET; +using System; using System.Drawing; using System.Windows.Forms; -using ACS.SPiiPlusNET; using Telerik.WinControls.UI; namespace HexcalMC @@ -55,11 +55,10 @@ namespace HexcalMC //判断是否在演示中 if (Status == DemoStatus.InProgress) { - MessageBox.Show("演示正在进行中,请先停止演示","提示", MessageBoxButtons.OK, + MessageBox.Show("演示正在进行中,请先停止演示", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } - //运行演示 try { @@ -121,14 +120,17 @@ namespace HexcalMC lable_showstatus.ForeColor = Color.Black; lable_showstatus.Text = "未演示"; break; + case DemoStatus.InProgress: lable_showstatus.ForeColor = Color.Blue; lable_showstatus.Text = "演示中"; break; + case DemoStatus.Completed: lable_showstatus.ForeColor = Color.Green; lable_showstatus.Text = "演示完成"; break; + case DemoStatus.Error: lable_showstatus.ForeColor = Color.Red; lable_showstatus.Text = "演示出错"; diff --git a/HexcalMC/Motion/EtalonForm.cs b/HexcalMC/Motion/EtalonForm.cs index fd71a27..53e0482 100644 --- a/HexcalMC/Motion/EtalonForm.cs +++ b/HexcalMC/Motion/EtalonForm.cs @@ -1,806 +1,792 @@ -using System; +using ACS.SPiiPlusNET; +using HexcalMC.Base; +using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; -using System.Timers; using System.Windows.Forms; -using System.Windows.Media; -using ACS.SPiiPlusNET; -using HexcalMC.Base; -using Plot3D; -using ScottPlot; -using ScottPlot.Plottable; -using Telerik.WinControls; using Telerik.WinControls.UI; -using static Plot3D.Editor3D; -using static Telerik.WinControls.UI.ValueMapper; using cColorScheme = Plot3D.Editor3D.cColorScheme; using cLine3D = Plot3D.Editor3D.cLine3D; using cLineData = Plot3D.Editor3D.cLineData; using cMessgData = Plot3D.Editor3D.cMessgData; + // classes using cObject3D = Plot3D.Editor3D.cObject3D; using cPoint3D = Plot3D.Editor3D.cPoint3D; -using cPolygon3D = Plot3D.Editor3D.cPolygon3D; -using cPolygonData = Plot3D.Editor3D.cPolygonData; using cScatterData = Plot3D.Editor3D.cScatterData; using cShape3D = Plot3D.Editor3D.cShape3D; -using cSurfaceData = Plot3D.Editor3D.cSurfaceData; -using cUserInput = Plot3D.Editor3D.cUserInput; + // callback function -using delRendererFunction = Plot3D.Editor3D.delRendererFunction; using eColorScheme = Plot3D.Editor3D.eColorScheme; using eInvalidate = Plot3D.Editor3D.eInvalidate; -using eLegendPos = Plot3D.Editor3D.eLegendPos; using eMouseCtrl = Plot3D.Editor3D.eMouseCtrl; using eNormalize = Plot3D.Editor3D.eNormalize; -using eObjType = Plot3D.Editor3D.eObjType; -using ePolygonMode = Plot3D.Editor3D.ePolygonMode; + // enums using eRaster = Plot3D.Editor3D.eRaster; using eScatterShape = Plot3D.Editor3D.eScatterShape; using eSelEvent = Plot3D.Editor3D.eSelEvent; using eSelType = Plot3D.Editor3D.eSelType; -using eTooltip = Plot3D.Editor3D.eTooltip; namespace HexcalMC { - internal enum eDemo - { - Math_Callback, - Math_Formula, - Surface_Fill, - Surface_Grid, - Surface_Fill_Missing, - Surface_Grid_Missing, - Nested_Graphs, - Scatter_Plot, - Connected_Lines, - Scatter_Shapes, - Pyramid, - Sphere_Fill_Closed, - Sphere_Fill_Open, - Sphere_Grid, - Valentine, - Animation, - } - - public partial class EtalonForm : System.Windows.Forms.Form - { - private eDemo me_Demo; - private eColorScheme me_ColorScheme; - private System.Windows.Forms.Timer mi_StatusTimer = new System.Windows.Forms.Timer(); - private cMessgData mi_MesgTop = new cMessgData("", -7, 7, System.Drawing.Color.Blue); // For special hint - private cMessgData mi_MesgBottom = new cMessgData("", -7, -7, System.Drawing.Color.Gray); // For selection mode - - // Only for demo "Animation" - private System.Windows.Forms.Timer mi_AnimationTimer = new System.Windows.Forms.Timer(); - - private cScatterData mi_SinusData; - private cPoint3D[] mi_Pyramid; - private int ms32_AnimationAngle; - - // etalon解析变量 - private List etalon_points = new List(); - - private List filteredPoints = new List(); //过滤后的点 - private List dwellTimes = new List(); - - private static System.Windows.Forms.Timer refresh_time = new System.Windows.Forms.Timer(); - private static int currentIndex = 0; - - private readonly MainFrom mainFrom; - private readonly Api _acs; //ACS控制器 - private double dwellTime = 2000; //停顿时间 - - public Axis[] axes = - { - Axis.ACSC_AXIS_0, - Axis.ACSC_AXIS_1, - Axis.ACSC_AXIS_8, - Axis.ACSC_NONE, - }; //使能的轴 - - public class Point - { - public double X { get; } - public double Y { get; } - public double Z { get; } - - public Point(double x, double y, double z) - { - X = x; - Y = y; - Z = z; - } - - // 从 Point 转换 - public static Point3D FromPoint(Point point) - { - return new Point3D(point.X, point.Y, point.Z); - } - } - - public EtalonForm(MainFrom _mainFrom) - { - InitializeComponent(); - mainFrom = _mainFrom; - this._acs = _mainFrom._acs; - } - - private void EtalonForm_Load(object sender, EventArgs e) - { - InitScatterPlot(); //清空绘图 - - DebugDfn.textBox_Msg = this.text_etalon_info; - } - - #region 绘图区功能 - private void InitScatterPlot() //绘图区初始化 - { - comboColors.Sorted = false; - foreach (eColorScheme e_Scheme in Enum.GetValues(typeof(eColorScheme))) - { - comboColors.Items.Add(e_Scheme.ToString().Replace('_', ' ')); - } - comboColors.SelectedIndex = (int)eColorScheme.Monochrome; //默认色卡 - - comboRaster.Sorted = false; - foreach (eRaster e_Raster in Enum.GetValues(typeof(eRaster))) - { - comboRaster.Items.Add(e_Raster); - } - comboRaster.SelectedIndex = (int)eRaster.Labels; //坐标系栅格样式 - - //设置默认 - comboMouse.SelectedIndex = 0; - - // 设置定时器 - refresh_time.Interval = 100; - refresh_time.Tick += new EventHandler(OnAnimationTimer); - refresh_time.Start(); - } - - private void DemoScatterPlot(bool b_Lines) - { - comboColors.Enabled = true; // Some of the demos will disable this combobox - me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; - - checkMirrorX.Checked = editor3D.AxisX.Mirror; - checkMirrorY.Checked = editor3D.AxisY.Mirror; - - // 3 pixels for line width and for circle radius - const int SIZE = 3; - - cColorScheme i_Scheme = new cColorScheme(me_ColorScheme); - cScatterData i_ShapeData = new cScatterData(i_Scheme); - cLineData i_LineData = new cLineData(i_Scheme); - List i_Points = new List(); - - for (double P = -22.0; P < 22.0; P += 0.1) - { - double d_X = Math.Sin(P) * P; - double d_Y = Math.Cos(P) * P; - double d_Z = P; - if (d_Z > 0.0) - d_Z /= 3.0; - - cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); - if (b_Lines) - { - i_Points.Add(i_Point); - } - else // Shapes - { - // You can store the returned shape in a variable and later modify it's properties - cShape3D i_Shape = i_ShapeData.AddShape( - i_Point, - eScatterShape.Circle, - SIZE, - null - ); - } - } - - // You can store the returned lines in a variable and later modify their properties - cLine3D[] i_Lines = i_LineData.AddConnectedLines(i_Points, SIZE, null); - - // Depending on your use case you can also specify MaintainXY or MaintainXYZ here - editor3D.Clear(); - editor3D.Normalize = eNormalize.Separate; - editor3D.AddRenderData(i_ShapeData, i_LineData); - - editor3D.Selection.HighlightColor = System.Drawing.Color.FromArgb(90, 90, 90); - editor3D.Selection.Callback = OnSelectEvent; - editor3D.Selection.MultiSelect = true; - editor3D.Selection.Enabled = true; - editor3D.UndoBuffer.Enabled = true; - editor3D.Invalidate(); - } - - private eInvalidate OnSelectEvent( - eSelEvent e_Event, - Keys e_Modifiers, - int s32_DeltaX, - int s32_DeltaY, - cObject3D i_Object - ) // - { - eInvalidate e_Invalidate = eInvalidate.NoChange; - - bool b_CTRL = (e_Modifiers & Keys.Control) > 0; - - // The left mouse button went down with ALT key down and CTRL key up - if (e_Event == eSelEvent.MouseDown && !b_CTRL && i_Object != null) - { - i_Object.Selected = !i_Object.Selected; - - // After changing the selection status the object must be redrawn. - e_Invalidate = eInvalidate.Invalidate; - } - else if (e_Event == eSelEvent.MouseDrag && b_CTRL) - { - // The user is dragging the mouse with ALT + CTRL keys down. Convert the mouse - // movement in the 2D space into a movement in the 3D space. - cPoint3D i_Project = editor3D.ReverseProject(s32_DeltaX, s32_DeltaY); - - // GetSelectedPoints() returns only unique points. - cPoint3D[] i_Selected = editor3D.Selection.GetSelectedPoints(eSelType.All); - foreach (cPoint3D i_Point in i_Selected) - { - switch (me_Demo) - { - case eDemo.Pyramid: - case eDemo.Scatter_Shapes: - case eDemo.Scatter_Plot: - case eDemo.Connected_Lines: - // The pyramid line end points / scatter shapes can be moved freely in - // the 3D space - i_Point.Move(i_Project.X, i_Project.Y, i_Project.Z); - break; - - case eDemo.Surface_Fill: - case eDemo.Surface_Grid: - case eDemo.Surface_Fill_Missing: - case eDemo.Surface_Grid_Missing: - // The points in the Surface grid have a fixed X,Y position, only Z can - // be modified. - i_Point.Move(0, 0, i_Project.Z); - break; - - default: - Debug.Assert(false); - break; - } - } - - // Set flag to recalculate the coordinate system, then Invalidate() - e_Invalidate = eInvalidate.CoordSystem; - } - - mi_StatusTimer.Stop(); - - mi_StatusTimer.Start(); - return e_Invalidate; - } - - private void comboRaster_SelectedIndexChanged(object sender, EventArgs e) //坐标栅格样式 - { - editor3D.Raster = (eRaster)comboRaster.SelectedIndex; - editor3D.Invalidate(); - } - - private void checkMirrorY_CheckedChanged(object sender, EventArgs e) //Y轴镜像 - { - editor3D.AxisY.Mirror = checkMirrorY.Checked; - editor3D.Invalidate(); - } - - private void checkMirrorX_CheckedChanged(object sender, EventArgs e) //x轴镜像 - { - editor3D.AxisX.Mirror = checkMirrorX.Checked; - editor3D.Invalidate(); - } - - private void btnReset_Click(object sender, EventArgs e) //重置视图 - { - editor3D.SetCoefficients(1350, 70, 230); - editor3D.Invalidate(); - } - - private void btnScreenshot_Click(object sender, EventArgs e) //截图保存 - { - SaveFileDialog i_Dlg = new SaveFileDialog(); - i_Dlg.Title = "Save as PNG image"; - i_Dlg.Filter = "PNG Image|*.png"; - i_Dlg.DefaultExt = ".png"; - - if (DialogResult.Cancel == i_Dlg.ShowDialog(this)) - return; - - Bitmap i_Bitmap = editor3D.GetScreenshot(); - try - { - i_Bitmap.Save(i_Dlg.FileName, ImageFormat.Png); - } - catch (Exception Ex) - { - MessageBox.Show( - this, - Ex.Message, - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } - } - - private void comboMouse_SelectedIndexChanged(object sender, EventArgs e) //鼠标控制方式 - { - switch (comboMouse.SelectedIndex) - { - case 0: - editor3D.SetUserInputs(eMouseCtrl.L_Theta_R_Phi); - labelMouseInfo.Text = "鼠标左键:升高,鼠标右键:旋转"; - break; - - case 1: - editor3D.SetUserInputs(eMouseCtrl.L_Theta_L_Phi); - labelMouseInfo.Text = "鼠标左键:升高和旋转"; - break; - - case 2: - editor3D.SetUserInputs(eMouseCtrl.M_Theta_M_Phi); - labelMouseInfo.Text = "鼠标中键:升高和旋转"; - break; - - default: - Debug.Assert(false); - break; - } - - labelMouseInfo.Text += - ", 鼠标左键 + SHIFT:移动、鼠标左键 + CTRL 或滚轮:缩放、鼠标左键 + ALT:选择"; - } - - private void comboColors_SelectedIndexChanged(object sender, EventArgs e) - { - me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; - - //判断 points 是否为空,表示当前是否已经有真实数据 - if (etalon_points.Count > 0) - { - PointScatterPlot(etalon_points); //更新真实数据 - } - //else - //{ - // DemoScatterPlot(false); //更新虚拟数据 - //} - } - #endregion - - #region etalon文件解析 - private void moveToPoint() - { - DebugDfn.AddLogText("添加到运动队列"); - int timeout = 5000; - - //判断电机状态 - if (!mainFrom.totalAxisEnabled) - { - DebugDfn.AddLogText("存在电机未使能"); - - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_0, 1, timeout); //Y轴 - // Wait axis 1 enabled during 5 sec - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_1, 1, timeout); - - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_8, 1, timeout); - - //DebugDfn.AddLogText("电机已启用"); - } - - // 创建多点运动 - dwellTime = dwellTimes.Average() * 1000; //将秒转换为毫秒 - DebugDfn.AddLogText("平均停顿时间(毫秒):" + dwellTime); - _acs.MultiPointM(MotionFlags.ACSC_NONE, axes, dwellTime); - - //判断是否有点 - if (filteredPoints.Count == 0) - { - DebugDfn.AddLogText("没有点"); - return; - } - //打印点的数量 - DebugDfn.AddLogText("待添加点的数量:" + filteredPoints.Count); - // 添加符合条件的点到运动路径中 - foreach (var point in filteredPoints) - { - double[] points = new double[3]; - points[0] = point.Y; - points[1] = point.X; - points[2] = point.Z; - _acs.AddPointM(axes, points); - //打印添加的点 - DebugDfn.AddLogText("添加点:" + points[0] + " " + points[1] + " " + points[2]); - } - // 打印添加点的数量 - DebugDfn.AddLogText("已添加点的数量:" + filteredPoints.Count); - - // Finish the motion End of the multi-point motion - _acs.EndSequenceM(axes); - - mainFrom.StopCounting(); - //启动统计 - mainFrom.StartCounting(); - } - - private void btn_startmove_Click(object sender, EventArgs e) //开始运动 - { - int timeout = 5000; - //判断电机状态 - if (!mainFrom.totalAxisEnabled) - { - DebugDfn.AddLogText("存在电机未使能"); - - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_0, 1, timeout); //Y轴 - // Wait axis 1 enabled during 5 sec - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_1, 1, timeout); - - _acs.WaitMotorEnabled(Axis.ACSC_AXIS_8, 1, timeout); - - //DebugDfn.AddLogText("电机已启用"); - } - - //判断是否有点 - if (filteredPoints.Count == 0) - { - MessageBox.Show("没有需要移动的点,请先导入", "ERROR"); - return; - } - //打印点的数量 - DebugDfn.AddLogText("待添加点的数量:" + filteredPoints.Count); - - //启动运动定时器 - timer_move.Start(); - refresh_time.Start(); - } - - private void btn_stop_Click(object sender, EventArgs e) //停止运动 - { - DebugDfn.AddLogText("停止运动"); - try - { - Axis[] m_arrAxisList = new Axis[] - { - Axis.ACSC_AXIS_0, - Axis.ACSC_AXIS_1, - Axis.ACSC_AXIS_8, - Axis.ACSC_NONE, - }; - - if (m_arrAxisList != null) - _acs.HaltM(m_arrAxisList); - - DebugDfn.AddLogText("立即停止 已发送命令"); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - Debug.WriteLine(ex.Message); - } - - //停止统计 - mainFrom.StopCounting(); - refresh_time.Stop(); - timer_move.Stop(); - currentIndex = 0; - } - - private void btn_etalon_import_Click(object sender, EventArgs e) //解析Etalon文件 - { - //打开文件对号框,选择 mpf格式文件 - OpenFileDialog i_Dlg = new OpenFileDialog(); - i_Dlg.Title = "导入Etalon文件"; - i_Dlg.Filter = "Etalon文件|*.mpf"; - i_Dlg.DefaultExt = ".mpf"; - i_Dlg.Multiselect = false; - - if (DialogResult.Cancel == i_Dlg.ShowDialog(this)) - return; - - //读取文件 - string s_File = i_Dlg.FileName; - - //设置路径显示 - - DebugDfn.AddLogText("导入Etalon文件:" + s_File); - - //解析文件 - parse_mpf_file(s_File); - - DebugDfn.AddLogText("Etalon文件解析完成"); - } - - private bool parse_mpf_file(string mpf_file_path) // 编写解析mpf文件的函数 - { - //; Machine: ZIM - //; Position: 1 - //; Created: 2/15/2023 12:29:13 PM - //; Program: TRAC-CAL V48, Build: 10, 5/13/2022 8:29:25 AM - //; File: 'cncData.xml' - //G71 - //G90 - //G500 - //G01 X8000.000 Y200.000 Z-1400.000 F2000 //坐标 - //G04 F=2 // 停顿时间 - //G01 X7800.000 Y300.000 Z-1400.000 F2000 - //G04 F=2 - //G01 X7600.000 Y400.000 Z-1400.000 F2000 - //G04 F=2 - //G01 X7400.000 Y500.000 Z-1400.000 F2000 - //G04 F=2 - //G01 X7200.000 Y600.000 Z-1400.000 F2000 - - //判断文件是否存在 - if (!File.Exists(mpf_file_path)) - { - MessageBox.Show("文件不存在"); - return false; - } - - //清空之前的数据 - etalon_points.Clear(); - filteredPoints.Clear(); - dwellTimes.Clear(); - currentIndex = 0; //重置当前点 - - //读取文件 - string[] lines = File.ReadAllLines(mpf_file_path); - Regex regex = new Regex( - @"G01 X(?[-+]?\d*\.?\d+) Y(?[-+]?\d*\.?\d+) Z(?[-+]?\d*\.?\d+)" - ); - Regex dwellRegex = new Regex(@"G04 F=(?\d+)"); - - foreach (string line in lines) - { - Match match = regex.Match(line); - if (match.Success) - { - double x = double.Parse(match.Groups["X"].Value); - double y = double.Parse(match.Groups["Y"].Value); - double z = double.Parse(match.Groups["Z"].Value); - etalon_points.Add(new Point(x, y, z)); - } - - Match dwellMatch = dwellRegex.Match(line); - if (dwellMatch.Success) - { - int f = int.Parse(dwellMatch.Groups["F"].Value); - dwellTimes.Add(f); - } - } - - // 输出解析结果 - //Console.WriteLine("Points:"); - //foreach (var point in etalon_points) - //{ - // Console.WriteLine($"X: {point.X}, Y: {point.Y}, Z: {point.Z}"); - //} - - //Console.WriteLine("Dwell Times:"); - //foreach (var dwellTime in dwellTimes) - //{ - // Console.WriteLine($"F: {dwellTime}"); - //} - - //过滤点集 - foreach (var point in etalon_points) - { - if (MainFrom.IsWithinLimit(point)) //判断点是否在行程范围内 - { - filteredPoints.Add(point); - } - } - - //打印过滤后的点集大小 - DebugDfn.AddLogText("过滤后的点集大小:" + filteredPoints.Count); - PointScatterPlot(filteredPoints); //绘制散点图 - return true; - } - - #endregion - - #region 3D绘图逻辑 - private void PointScatterPlot(List points) //绘制散点图 - { - //清空绘图 - editor3D.Clear(); - editor3D.Normalize = eNormalize.Separate; - editor3D.Invalidate(); - - //判断点集是否为空 - comboColors.Enabled = true; // Some of the demos will disable this combobox - me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; - - checkMirrorX.Checked = editor3D.AxisX.Mirror; - checkMirrorY.Checked = editor3D.AxisY.Mirror; - - // 3 pixels for line width and for circle radius - const int SIZE = 3; - - cColorScheme i_Scheme = new cColorScheme(me_ColorScheme); - cScatterData i_ShapeData = new cScatterData(i_Scheme); - cLineData i_LineData = new cLineData(i_Scheme); - List i_Points = new List(); - - foreach (var point in points) - { - double d_X = point.X; - double d_Y = point.Y; - double d_Z = point.Z; - - cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); - - // You can store the returned shape in a variable and later modify it's properties - cShape3D i_Shape = i_ShapeData.AddShape(i_Point, eScatterShape.Circle, SIZE, null); - } - - // You can store the returned lines in a variable and later modify their properties - cLine3D[] i_Lines = i_LineData.AddConnectedLines(i_Points, SIZE, null); - - // Depending on your use case you can also specify MaintainXY or MaintainXYZ here - editor3D.Clear(); - editor3D.Normalize = eNormalize.Separate; - editor3D.AddRenderData(i_ShapeData, i_LineData); - - editor3D.Selection.HighlightColor = System.Drawing.Color.FromArgb(90, 90, 90); - editor3D.Selection.Callback = OnSelectEvent; - editor3D.Selection.MultiSelect = true; - editor3D.Selection.Enabled = true; - editor3D.UndoBuffer.Enabled = true; - editor3D.Invalidate(); - } - - private void btn_clear_Click(object sender, EventArgs e) //清空绘图 - { - editor3D.Clear(); - editor3D.Normalize = eNormalize.Separate; - editor3D.Invalidate(); - } - - private void btn_draw_test_Click(object sender, EventArgs e) - { - DemoScatterPlot(false); - } - - private void OnAnimationTimer(object sender, EventArgs e) - { - cScatterData i_ShapeData = new cScatterData(); - //currentIndex = mainFrom.GetInPosCount();//获取当前点的数量 - - for (int i = 0; i < filteredPoints.Count; i++) //遍历所有点 - { - if (i < currentIndex-1) //小于的部分,已跑完 为绿色 - { - double d_X = filteredPoints[i].X; - double d_Y = filteredPoints[i].Y; - double d_Z = filteredPoints[i].Z; - - cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); - SolidBrush i_brush = new SolidBrush(System.Drawing.Color.Green); - // You can store the returned shape in a variable and later modify it's properties - cShape3D i_Shape = i_ShapeData.AddShape( - i_Point, - eScatterShape.Circle, - 5, - i_brush, - null - ); - } - else if (i == currentIndex-1) //当前点为橙黄色 - { - double d_X = filteredPoints[i].X; - double d_Y = filteredPoints[i].Y; - double d_Z = filteredPoints[i].Z; - - cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); - SolidBrush i_brush = new SolidBrush(System.Drawing.Color.OrangeRed); - // You can store the returned shape in a variable and later modify it's properties - cShape3D i_Shape = i_ShapeData.AddShape( - i_Point, - eScatterShape.Circle, - 5, - i_brush, - null - ); - } - else - { - double d_X = filteredPoints[i].X; - double d_Y = filteredPoints[i].Y; - double d_Z = filteredPoints[i].Z; - - cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); - SolidBrush i_brush = new SolidBrush(System.Drawing.Color.Gray); - // You can store the returned shape in a variable and later modify it's properties - cShape3D i_Shape = i_ShapeData.AddShape( - i_Point, - eScatterShape.Circle, - 3, - i_brush, - null - ); - } - - editor3D.Clear(); - editor3D.Normalize = eNormalize.Separate; - editor3D.AddRenderData(i_ShapeData); - editor3D.Invalidate(); - } - } - #endregion - - #region 队列移动逻辑 - private void timer_move_Tick(object sender, EventArgs e) - { - // 检查是否到达目标点 - if (IsAtTarget()) - { - // 移动到下一个点 - MoveToNextPoint(); - } - } - - private void MoveToNextPoint() - { - if (filteredPoints.Count > 0 && currentIndex < filteredPoints.Count) - { - //增加2秒延时 - Thread.Sleep((int)dwellTime); + internal enum eDemo + { + Math_Callback, + Math_Formula, + Surface_Fill, + Surface_Grid, + Surface_Fill_Missing, + Surface_Grid_Missing, + Nested_Graphs, + Scatter_Plot, + Connected_Lines, + Scatter_Shapes, + Pyramid, + Sphere_Fill_Closed, + Sphere_Fill_Open, + Sphere_Grid, + Valentine, + Animation, + } + + public partial class EtalonForm : System.Windows.Forms.Form + { + private eDemo me_Demo; + private eColorScheme me_ColorScheme; + private System.Windows.Forms.Timer mi_StatusTimer = new System.Windows.Forms.Timer(); + private cMessgData mi_MesgTop = new cMessgData("", -7, 7, System.Drawing.Color.Blue); // For special hint + private cMessgData mi_MesgBottom = new cMessgData("", -7, -7, System.Drawing.Color.Gray); // For selection mode + + // Only for demo "Animation" + private System.Windows.Forms.Timer mi_AnimationTimer = new System.Windows.Forms.Timer(); + + private cScatterData mi_SinusData; + private cPoint3D[] mi_Pyramid; + private int ms32_AnimationAngle; + + // etalon解析变量 + private List etalon_points = new List(); + + private List filteredPoints = new List(); //过滤后的点 + private List dwellTimes = new List(); + + private static System.Windows.Forms.Timer refresh_time = new System.Windows.Forms.Timer(); + private static int currentIndex = 0; + + private readonly MainForm mainFrom; + private readonly Api _acs; //ACS控制器 + private double dwellTime = 2000; //停顿时间 + + public Axis[] axes = + { + Axis.ACSC_AXIS_0, + Axis.ACSC_AXIS_1, + Axis.ACSC_AXIS_8, + Axis.ACSC_NONE, + }; //使能的轴 + + public class Point + { + public double X { get; } + public double Y { get; } + public double Z { get; } + + public Point(double x, double y, double z) + { + X = x; + Y = y; + Z = z; + } + + // 从 Point 转换 + public static Point3D FromPoint(Point point) + { + return new Point3D(point.X, point.Y, point.Z); + } + } + + public EtalonForm(MainForm _mainFrom) + { + InitializeComponent(); + mainFrom = _mainFrom; + this._acs = _mainFrom._acs; + } + + private void EtalonForm_Load(object sender, EventArgs e) + { + InitScatterPlot(); //清空绘图 + + DebugDfn.textBox_Msg = this.text_etalon_info; + } + + #region 绘图区功能 + + private void InitScatterPlot() //绘图区初始化 + { + comboColors.Sorted = false; + foreach (eColorScheme e_Scheme in Enum.GetValues(typeof(eColorScheme))) + { + comboColors.Items.Add(e_Scheme.ToString().Replace('_', ' ')); + } + comboColors.SelectedIndex = (int)eColorScheme.Monochrome; //默认色卡 + + comboRaster.Sorted = false; + foreach (eRaster e_Raster in Enum.GetValues(typeof(eRaster))) + { + comboRaster.Items.Add(e_Raster); + } + comboRaster.SelectedIndex = (int)eRaster.Labels; //坐标系栅格样式 + + //设置默认 + comboMouse.SelectedIndex = 0; + + // 设置定时器 + refresh_time.Interval = 100; + refresh_time.Tick += new EventHandler(OnAnimationTimer); + refresh_time.Start(); + } + + private void DemoScatterPlot(bool b_Lines) + { + comboColors.Enabled = true; // Some of the demos will disable this combobox + me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; + + checkMirrorX.Checked = editor3D.AxisX.Mirror; + checkMirrorY.Checked = editor3D.AxisY.Mirror; + + // 3 pixels for line width and for circle radius + const int SIZE = 3; + + cColorScheme i_Scheme = new cColorScheme(me_ColorScheme); + cScatterData i_ShapeData = new cScatterData(i_Scheme); + cLineData i_LineData = new cLineData(i_Scheme); + List i_Points = new List(); + + for (double P = -22.0; P < 22.0; P += 0.1) + { + double d_X = Math.Sin(P) * P; + double d_Y = Math.Cos(P) * P; + double d_Z = P; + if (d_Z > 0.0) + d_Z /= 3.0; + + cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); + if (b_Lines) + { + i_Points.Add(i_Point); + } + else // Shapes + { + // You can store the returned shape in a variable and later modify it's properties + cShape3D i_Shape = i_ShapeData.AddShape( + i_Point, + eScatterShape.Circle, + SIZE, + null + ); + } + } + + // You can store the returned lines in a variable and later modify their properties + cLine3D[] i_Lines = i_LineData.AddConnectedLines(i_Points, SIZE, null); + + // Depending on your use case you can also specify MaintainXY or MaintainXYZ here + editor3D.Clear(); + editor3D.Normalize = eNormalize.Separate; + editor3D.AddRenderData(i_ShapeData, i_LineData); + + editor3D.Selection.HighlightColor = System.Drawing.Color.FromArgb(90, 90, 90); + editor3D.Selection.Callback = OnSelectEvent; + editor3D.Selection.MultiSelect = true; + editor3D.Selection.Enabled = true; + editor3D.UndoBuffer.Enabled = true; + editor3D.Invalidate(); + } + + private eInvalidate OnSelectEvent( + eSelEvent e_Event, + Keys e_Modifiers, + int s32_DeltaX, + int s32_DeltaY, + cObject3D i_Object + ) // + { + eInvalidate e_Invalidate = eInvalidate.NoChange; + + bool b_CTRL = (e_Modifiers & Keys.Control) > 0; + + // The left mouse button went down with ALT key down and CTRL key up + if (e_Event == eSelEvent.MouseDown && !b_CTRL && i_Object != null) + { + i_Object.Selected = !i_Object.Selected; + + // After changing the selection status the object must be redrawn. + e_Invalidate = eInvalidate.Invalidate; + } + else if (e_Event == eSelEvent.MouseDrag && b_CTRL) + { + // The user is dragging the mouse with ALT + CTRL keys down. Convert the mouse + // movement in the 2D space into a movement in the 3D space. + cPoint3D i_Project = editor3D.ReverseProject(s32_DeltaX, s32_DeltaY); + + // GetSelectedPoints() returns only unique points. + cPoint3D[] i_Selected = editor3D.Selection.GetSelectedPoints(eSelType.All); + foreach (cPoint3D i_Point in i_Selected) + { + switch (me_Demo) + { + case eDemo.Pyramid: + case eDemo.Scatter_Shapes: + case eDemo.Scatter_Plot: + case eDemo.Connected_Lines: + // The pyramid line end points / scatter shapes can be moved freely in + // the 3D space + i_Point.Move(i_Project.X, i_Project.Y, i_Project.Z); + break; + + case eDemo.Surface_Fill: + case eDemo.Surface_Grid: + case eDemo.Surface_Fill_Missing: + case eDemo.Surface_Grid_Missing: + // The points in the Surface grid have a fixed X,Y position, only Z can + // be modified. + i_Point.Move(0, 0, i_Project.Z); + break; + + default: + Debug.Assert(false); + break; + } + } + + // Set flag to recalculate the coordinate system, then Invalidate() + e_Invalidate = eInvalidate.CoordSystem; + } + + mi_StatusTimer.Stop(); + + mi_StatusTimer.Start(); + return e_Invalidate; + } + + private void comboRaster_SelectedIndexChanged(object sender, EventArgs e) //坐标栅格样式 + { + editor3D.Raster = (eRaster)comboRaster.SelectedIndex; + editor3D.Invalidate(); + } + + private void checkMirrorY_CheckedChanged(object sender, EventArgs e) //Y轴镜像 + { + editor3D.AxisY.Mirror = checkMirrorY.Checked; + editor3D.Invalidate(); + } + + private void checkMirrorX_CheckedChanged(object sender, EventArgs e) //x轴镜像 + { + editor3D.AxisX.Mirror = checkMirrorX.Checked; + editor3D.Invalidate(); + } + + private void btnReset_Click(object sender, EventArgs e) //重置视图 + { + editor3D.SetCoefficients(1350, 70, 230); + editor3D.Invalidate(); + } + + private void btnScreenshot_Click(object sender, EventArgs e) //截图保存 + { + SaveFileDialog i_Dlg = new SaveFileDialog(); + i_Dlg.Title = "Save as PNG image"; + i_Dlg.Filter = "PNG Image|*.png"; + i_Dlg.DefaultExt = ".png"; + + if (DialogResult.Cancel == i_Dlg.ShowDialog(this)) + return; + + Bitmap i_Bitmap = editor3D.GetScreenshot(); + try + { + i_Bitmap.Save(i_Dlg.FileName, ImageFormat.Png); + } + catch (Exception Ex) + { + MessageBox.Show( + this, + Ex.Message, + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + + private void comboMouse_SelectedIndexChanged(object sender, EventArgs e) //鼠标控制方式 + { + switch (comboMouse.SelectedIndex) + { + case 0: + editor3D.SetUserInputs(eMouseCtrl.L_Theta_R_Phi); + labelMouseInfo.Text = "鼠标左键:升高,鼠标右键:旋转"; + break; + + case 1: + editor3D.SetUserInputs(eMouseCtrl.L_Theta_L_Phi); + labelMouseInfo.Text = "鼠标左键:升高和旋转"; + break; + + case 2: + editor3D.SetUserInputs(eMouseCtrl.M_Theta_M_Phi); + labelMouseInfo.Text = "鼠标中键:升高和旋转"; + break; + + default: + Debug.Assert(false); + break; + } + + labelMouseInfo.Text += + ", 鼠标左键 + SHIFT:移动、鼠标左键 + CTRL 或滚轮:缩放、鼠标左键 + ALT:选择"; + } + + private void comboColors_SelectedIndexChanged(object sender, EventArgs e) + { + me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; + + //判断 points 是否为空,表示当前是否已经有真实数据 + if (etalon_points.Count > 0) + { + PointScatterPlot(etalon_points); //更新真实数据 + } + //else + //{ + // DemoScatterPlot(false); //更新虚拟数据 + //} + } + + #endregion 绘图区功能 + + #region etalon文件解析 + + private void moveToPoint() + { + DebugDfn.AddLogText("添加到运动队列"); + int timeout = 5000; + + //判断电机状态 + if (!mainFrom.totalAxisEnabled) + { + DebugDfn.AddLogText("存在电机未使能"); + + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_0, 1, timeout); //Y轴 + // Wait axis 1 enabled during 5 sec + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_1, 1, timeout); + + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_8, 1, timeout); + + //DebugDfn.AddLogText("电机已启用"); + } + + // 创建多点运动 + dwellTime = dwellTimes.Average() * 1000; //将秒转换为毫秒 + DebugDfn.AddLogText("平均停顿时间(毫秒):" + dwellTime); + _acs.MultiPointM(MotionFlags.ACSC_NONE, axes, dwellTime); + + //判断是否有点 + if (filteredPoints.Count == 0) + { + DebugDfn.AddLogText("没有点"); + return; + } + //打印点的数量 + DebugDfn.AddLogText("待添加点的数量:" + filteredPoints.Count); + // 添加符合条件的点到运动路径中 + foreach (var point in filteredPoints) + { + double[] points = new double[3]; + points[0] = point.Y; + points[1] = point.X; + points[2] = point.Z; + _acs.AddPointM(axes, points); + //打印添加的点 + DebugDfn.AddLogText("添加点:" + points[0] + " " + points[1] + " " + points[2]); + } + // 打印添加点的数量 + DebugDfn.AddLogText("已添加点的数量:" + filteredPoints.Count); + + // Finish the motion End of the multi-point motion + _acs.EndSequenceM(axes); + + mainFrom.StopCounting(); + //启动统计 + mainFrom.StartCounting(); + } + + private void btn_startmove_Click(object sender, EventArgs e) //开始运动 + { + int timeout = 5000; + //判断电机状态 + if (!mainFrom.totalAxisEnabled) + { + DebugDfn.AddLogText("存在电机未使能"); + + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_0, 1, timeout); //Y轴 + // Wait axis 1 enabled during 5 sec + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_1, 1, timeout); + + _acs.WaitMotorEnabled(Axis.ACSC_AXIS_8, 1, timeout); + + //DebugDfn.AddLogText("电机已启用"); + } + + //判断是否有点 + if (filteredPoints.Count == 0) + { + MessageBox.Show("没有需要移动的点,请先导入", "ERROR"); + return; + } + //打印点的数量 + DebugDfn.AddLogText("待添加点的数量:" + filteredPoints.Count); + + //启动运动定时器 + timer_move.Start(); + refresh_time.Start(); + } + + private void btn_stop_Click(object sender, EventArgs e) //停止运动 + { + DebugDfn.AddLogText("停止运动"); + try + { + Axis[] m_arrAxisList = new Axis[] + { + Axis.ACSC_AXIS_0, + Axis.ACSC_AXIS_1, + Axis.ACSC_AXIS_8, + Axis.ACSC_NONE, + }; + + if (m_arrAxisList != null) + _acs.HaltM(m_arrAxisList); + + DebugDfn.AddLogText("立即停止 已发送命令"); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + Debug.WriteLine(ex.Message); + } + + //停止统计 + mainFrom.StopCounting(); + refresh_time.Stop(); + timer_move.Stop(); + currentIndex = 0; + } + + private void btn_etalon_import_Click(object sender, EventArgs e) //解析Etalon文件 + { + //打开文件对号框,选择 mpf格式文件 + OpenFileDialog i_Dlg = new OpenFileDialog(); + i_Dlg.Title = "导入Etalon文件"; + i_Dlg.Filter = "Etalon文件|*.mpf"; + i_Dlg.DefaultExt = ".mpf"; + i_Dlg.Multiselect = false; + + if (DialogResult.Cancel == i_Dlg.ShowDialog(this)) + return; + + //读取文件 + string s_File = i_Dlg.FileName; + + //设置路径显示 + + DebugDfn.AddLogText("导入Etalon文件:" + s_File); + + //解析文件 + parse_mpf_file(s_File); + + DebugDfn.AddLogText("Etalon文件解析完成"); + } + + private bool parse_mpf_file(string mpf_file_path) // 编写解析mpf文件的函数 + { + //; Machine: ZIM + //; Position: 1 + //; Created: 2/15/2023 12:29:13 PM + //; Program: TRAC-CAL V48, Build: 10, 5/13/2022 8:29:25 AM + //; File: 'cncData.xml' + //G71 + //G90 + //G500 + //G01 X8000.000 Y200.000 Z-1400.000 F2000 //坐标 + //G04 F=2 // 停顿时间 + //G01 X7800.000 Y300.000 Z-1400.000 F2000 + //G04 F=2 + //G01 X7600.000 Y400.000 Z-1400.000 F2000 + //G04 F=2 + //G01 X7400.000 Y500.000 Z-1400.000 F2000 + //G04 F=2 + //G01 X7200.000 Y600.000 Z-1400.000 F2000 + + //判断文件是否存在 + if (!File.Exists(mpf_file_path)) + { + MessageBox.Show("文件不存在"); + return false; + } + + //清空之前的数据 + etalon_points.Clear(); + filteredPoints.Clear(); + dwellTimes.Clear(); + currentIndex = 0; //重置当前点 + + //读取文件 + string[] lines = File.ReadAllLines(mpf_file_path); + Regex regex = new Regex( + @"G01 X(?[-+]?\d*\.?\d+) Y(?[-+]?\d*\.?\d+) Z(?[-+]?\d*\.?\d+)" + ); + Regex dwellRegex = new Regex(@"G04 F=(?\d+)"); + + foreach (string line in lines) + { + Match match = regex.Match(line); + if (match.Success) + { + double x = double.Parse(match.Groups["X"].Value); + double y = double.Parse(match.Groups["Y"].Value); + double z = double.Parse(match.Groups["Z"].Value); + etalon_points.Add(new Point(x, y, z)); + } + + Match dwellMatch = dwellRegex.Match(line); + if (dwellMatch.Success) + { + int f = int.Parse(dwellMatch.Groups["F"].Value); + dwellTimes.Add(f); + } + } + + // 输出解析结果 + //Console.WriteLine("Points:"); + //foreach (var point in etalon_points) + //{ + // Console.WriteLine($"X: {point.X}, Y: {point.Y}, Z: {point.Z}"); + //} + + //Console.WriteLine("Dwell Times:"); + //foreach (var dwellTime in dwellTimes) + //{ + // Console.WriteLine($"F: {dwellTime}"); + //} + + //过滤点集 + foreach (var point in etalon_points) + { + if (MainForm.IsWithinLimit(point)) //判断点是否在行程范围内 + { + filteredPoints.Add(point); + } + } + + //打印过滤后的点集大小 + DebugDfn.AddLogText("过滤后的点集大小:" + filteredPoints.Count); + PointScatterPlot(filteredPoints); //绘制散点图 + return true; + } + + #endregion etalon文件解析 + + #region 3D绘图逻辑 + + private void PointScatterPlot(List points) //绘制散点图 + { + //清空绘图 + editor3D.Clear(); + editor3D.Normalize = eNormalize.Separate; + editor3D.Invalidate(); + + //判断点集是否为空 + comboColors.Enabled = true; // Some of the demos will disable this combobox + me_ColorScheme = (eColorScheme)comboColors.SelectedIndex; + + checkMirrorX.Checked = editor3D.AxisX.Mirror; + checkMirrorY.Checked = editor3D.AxisY.Mirror; + + // 3 pixels for line width and for circle radius + const int SIZE = 3; + + cColorScheme i_Scheme = new cColorScheme(me_ColorScheme); + cScatterData i_ShapeData = new cScatterData(i_Scheme); + cLineData i_LineData = new cLineData(i_Scheme); + List i_Points = new List(); + + foreach (var point in points) + { + double d_X = point.X; + double d_Y = point.Y; + double d_Z = point.Z; + + cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); + + // You can store the returned shape in a variable and later modify it's properties + cShape3D i_Shape = i_ShapeData.AddShape(i_Point, eScatterShape.Circle, SIZE, null); + } + + // You can store the returned lines in a variable and later modify their properties + cLine3D[] i_Lines = i_LineData.AddConnectedLines(i_Points, SIZE, null); + + // Depending on your use case you can also specify MaintainXY or MaintainXYZ here + editor3D.Clear(); + editor3D.Normalize = eNormalize.Separate; + editor3D.AddRenderData(i_ShapeData, i_LineData); + + editor3D.Selection.HighlightColor = System.Drawing.Color.FromArgb(90, 90, 90); + editor3D.Selection.Callback = OnSelectEvent; + editor3D.Selection.MultiSelect = true; + editor3D.Selection.Enabled = true; + editor3D.UndoBuffer.Enabled = true; + editor3D.Invalidate(); + } + + private void btn_clear_Click(object sender, EventArgs e) //清空绘图 + { + editor3D.Clear(); + editor3D.Normalize = eNormalize.Separate; + editor3D.Invalidate(); + } + + private void btn_draw_test_Click(object sender, EventArgs e) + { + DemoScatterPlot(false); + } + + private void OnAnimationTimer(object sender, EventArgs e) + { + cScatterData i_ShapeData = new cScatterData(); + //currentIndex = mainFrom.GetInPosCount();//获取当前点的数量 + + for (int i = 0; i < filteredPoints.Count; i++) //遍历所有点 + { + if (i < currentIndex - 1) //小于的部分,已跑完 为绿色 + { + double d_X = filteredPoints[i].X; + double d_Y = filteredPoints[i].Y; + double d_Z = filteredPoints[i].Z; + + cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); + SolidBrush i_brush = new SolidBrush(System.Drawing.Color.Green); + // You can store the returned shape in a variable and later modify it's properties + cShape3D i_Shape = i_ShapeData.AddShape( + i_Point, + eScatterShape.Circle, + 5, + i_brush, + null + ); + } + else if (i == currentIndex - 1) //当前点为橙黄色 + { + double d_X = filteredPoints[i].X; + double d_Y = filteredPoints[i].Y; + double d_Z = filteredPoints[i].Z; + + cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); + SolidBrush i_brush = new SolidBrush(System.Drawing.Color.OrangeRed); + // You can store the returned shape in a variable and later modify it's properties + cShape3D i_Shape = i_ShapeData.AddShape( + i_Point, + eScatterShape.Circle, + 5, + i_brush, + null + ); + } + else + { + double d_X = filteredPoints[i].X; + double d_Y = filteredPoints[i].Y; + double d_Z = filteredPoints[i].Z; + + cPoint3D i_Point = new cPoint3D(d_X, d_Y, d_Z, "Scatter Point"); + SolidBrush i_brush = new SolidBrush(System.Drawing.Color.Gray); + // You can store the returned shape in a variable and later modify it's properties + cShape3D i_Shape = i_ShapeData.AddShape( + i_Point, + eScatterShape.Circle, + 3, + i_brush, + null + ); + } + + editor3D.Clear(); + editor3D.Normalize = eNormalize.Separate; + editor3D.AddRenderData(i_ShapeData); + editor3D.Invalidate(); + } + } + + #endregion 3D绘图逻辑 + + #region 队列移动逻辑 + + private void timer_move_Tick(object sender, EventArgs e) + { + // 检查是否到达目标点 + if (IsAtTarget()) + { + // 移动到下一个点 + MoveToNextPoint(); + } + } + + private void MoveToNextPoint() + { + if (filteredPoints.Count > 0 && currentIndex < filteredPoints.Count) + { + //增加2秒延时 + Thread.Sleep((int)dwellTime); //int currentDwellTime = dwellTimes[currentIndex] * 1000; // 将秒转换为毫秒 //Thread.Sleep(currentDwellTime); Point nextPoint = filteredPoints[currentIndex]; - //打印 nextPoint - DebugDfn.AddLogText( - "下发指令:" - + currentIndex - + " 点:" - + nextPoint.X - + " " - + nextPoint.Y - + " " - + nextPoint.Z - ); - currentIndex++; - mainFrom.SetPositionXyz(Point.FromPoint(nextPoint)); //移动到下一个点 - } - else - { - DebugDfn.AddLogText("All points have been visited."); - timer_move.Stop(); - currentIndex = 0; - } - } + //打印 nextPoint + DebugDfn.AddLogText( + "下发指令:" + + currentIndex + + " 点:" + + nextPoint.X + + " " + + nextPoint.Y + + " " + + nextPoint.Z + ); + currentIndex++; + mainFrom.SetPositionXyz(Point.FromPoint(nextPoint)); //移动到下一个点 + } + else + { + DebugDfn.AddLogText("All points have been visited."); + timer_move.Stop(); + currentIndex = 0; + } + } - private bool IsAtTarget() - { - // 这里你需要实现实际的到位判断逻辑 - // 例如,通过读取传感器数据或运动控制器的状态 - // 这里假设总是返回 true 作为示例 + private bool IsAtTarget() + { + // 这里你需要实现实际的到位判断逻辑 例如,通过读取传感器数据或运动控制器的状态 这里假设总是返回 true 作为示例 - return mainFrom.GetIsMoving(); - } + return mainFrom.GetIsMoving(); + } - #endregion - } -} + #endregion 队列移动逻辑 + } +} \ No newline at end of file diff --git a/HexcalMC/Motion/MonitorForm.cs b/HexcalMC/Motion/MonitorForm.cs index 9cf9ad8..c4befca 100644 --- a/HexcalMC/Motion/MonitorForm.cs +++ b/HexcalMC/Motion/MonitorForm.cs @@ -1,26 +1,13 @@ using ACS.SPiiPlusNET; using HexcalMC.Base; -using ScottPlot.Renderable; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Timers; using System.Windows.Forms; -using System.Windows.Markup; -using Telerik.WinControls.UI; -using static ScottPlot.Generate; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Rebar; namespace HexcalMC { public partial class MonitorForm : System.Windows.Forms.Form { - private readonly MainFrom mainFrom; + private readonly MainForm mainFrom; private readonly Api _acs; //ACS控制器 private double[] dataX; // X轴数据(时间戳) @@ -28,7 +15,7 @@ namespace HexcalMC private int dataLength = 200; // 显示的数据点数量‌:ml-citation{ref="4,5" data="citationList"} private Random rand = new Random(); - public MonitorForm(MainFrom _mainFrom) + public MonitorForm(MainForm _mainFrom) { InitializeComponent(); @@ -65,7 +52,6 @@ namespace HexcalMC } // 定时器事件:更新数据并刷新图表 - private void dataTimer_Tick(object sender, EventArgs e) { @@ -91,10 +77,8 @@ namespace HexcalMC }); } - private double GetSpeed(int axis) { - //判断对象是否为空 if (_acs == null) { diff --git a/HexcalMC/Motion/Motion.cs b/HexcalMC/Motion/Motion.cs index 3fe1baf..cbb5e60 100644 --- a/HexcalMC/Motion/Motion.cs +++ b/HexcalMC/Motion/Motion.cs @@ -1,10 +1,10 @@ -using System; +using ACS.SPiiPlusNET; +using HexcalMC.Base; +using HexcalMC.Properties; +using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Forms; -using ACS.SPiiPlusNET; -using HexcalMC.Base; -using HexcalMC.Properties; using Telerik.WinControls.UI; // ACS .NET Library @@ -21,10 +21,9 @@ namespace HexcalMC private readonly Api _acs; private readonly int _motionTimeout = 50000; //延时时间 - private readonly MainFrom mainFrom; + private readonly MainForm mainFrom; - - private readonly Axis[] UseAxis = MainFrom.UseAxis; //获取激活的轴 + private readonly Axis[] UseAxis = MainForm.UseAxis; //获取激活的轴 private MotionStates _currentMotionState = MotionStates.None; //运动状态 private Axis[] _mArrAxisList; private Array _mArrReadVector; @@ -32,11 +31,10 @@ namespace HexcalMC private bool _mBConnected; private Button[] _mBtnOutput; - //定义Jog运动 速度 private double _mJogVel = 10.0f; - private Label[] _mLblInput; + private Label[] _mLblInput; private Label[] _mLblLeftLimit; //左限位 private Label[] _mLblOutput; @@ -45,6 +43,7 @@ namespace HexcalMC // For update values private MotorStates _mNMotorState; //运动状态 + private ProgramStates _mNProgramState; //程序状态 private int _mNTotalAxis; @@ -52,7 +51,7 @@ namespace HexcalMC private int _mNValues, _mNOutputState; private object _mObjReadVar; - public Motion(MainFrom mainFrom) + public Motion(MainForm mainFrom) { InitializeComponent(); @@ -65,8 +64,7 @@ namespace HexcalMC private void BtnSetZero_Click(object sender, EventArgs e) { - // Change current poisition as you want - // SetFPosition(Axis number, new position) + // Change current poisition as you want SetFPosition(Axis number, new position) _acs.SetFPosition((Axis)cboAxisNo.SelectedIndex, 0); } @@ -94,7 +92,7 @@ namespace HexcalMC } } - #endregion + #endregion 绝对运动 #region 通用IO @@ -119,12 +117,10 @@ namespace HexcalMC 0 // 0 = OFF, 1 = ON ); - // If your I/O device is EtherCAT type, you cannot use this function - // You can use WriteVariable function and Command function - // - // Ex) If EtherCAT mapped variable is 'EC_DOUT' - // Want to ON bit '3' - // _ACS.Command("EC_DOUT.3=1"); + // If your I/O device is EtherCAT type, you cannot use this function You can use + // WriteVariable function and Command function + // + // Ex) If EtherCAT mapped variable is 'EC_DOUT' Want to ON bit '3' _ACS.Command("EC_DOUT.3=1"); } else { @@ -137,7 +133,7 @@ namespace HexcalMC } } - #endregion + #endregion 通用IO #region 初始化 @@ -206,32 +202,25 @@ namespace HexcalMC #region 副屏显示功能 - // Screen[] screens = Screen.AllScreens; - // Screen secondaryScreen = screens.Length > 1 ? screens[1] : screens[0]; // 如果有多个屏幕,选择第二个屏幕作为副屏幕 + // Screen[] screens = Screen.AllScreens; Screen secondaryScreen = screens.Length > 1 ? + // screens[1] : screens[0]; // 如果有多个屏幕,选择第二个屏幕作为副屏幕 // - // // 创建一个新的窗体实例 - // Form secondaryForm = new Form(); + // // 创建一个新的窗体实例 Form secondaryForm = new Form(); // - // // 设置窗体的位置和大小以适应副屏幕 - // secondaryForm.StartPosition = FormStartPosition.Manual; - // secondaryForm.Location = secondaryScreen.Bounds.Location; - // secondaryForm.Size = secondaryScreen.Bounds.Size; + // // 设置窗体的位置和大小以适应副屏幕 secondaryForm.StartPosition = FormStartPosition.Manual; + // secondaryForm.Location = secondaryScreen.Bounds.Location; secondaryForm.Size = secondaryScreen.Bounds.Size; // - // // 可选:设置副屏窗体的标题、样式等其他属性 - // secondaryForm.Text = "副屏窗体"; - // // ... 其他属性设置 + // // 可选:设置副屏窗体的标题、样式等其他属性 secondaryForm.Text = "副屏窗体"; // ... 其他属性设置 // - // // 显示副屏窗体 - // secondaryForm.Show(); - - #endregion + // // 显示副屏窗体 secondaryForm.Show(); + #endregion 副屏显示功能 #region 初始化 InitMotion(); - #endregion + #endregion 初始化 } private void Motion_FormClosed(object sender, FormClosedEventArgs e) @@ -239,7 +228,7 @@ namespace HexcalMC tmrMonitor.Stop(); } - #endregion + #endregion 初始化 #region 通讯建立 @@ -248,7 +237,6 @@ namespace HexcalMC string strTemp; int i; - _mBConnected = _acs.IsConnected; // Get Total number of axes strTemp = _acs.Transaction("?SYSINFO(13)"); @@ -293,11 +281,9 @@ namespace HexcalMC DebugDfn.AddLogText("运动平台已连接"); } - InitMotion(); _mBConnected = true; } - catch (COMException comex) { MessageBox.Show("Connection fail", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); @@ -305,7 +291,6 @@ namespace HexcalMC _mBConnected = false; } - catch (Exception ex) { MessageBox.Show(ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); @@ -323,7 +308,6 @@ namespace HexcalMC btnClose.Enabled = false; } - private void TernminateUMD_Connection() { try @@ -343,12 +327,12 @@ namespace HexcalMC } } - #endregion + #endregion 通讯建立 #region UI 刷新 /// - /// Update Motion Profile from Controller + /// Update Motion Profile from Controller /// private void UpdateProfile() { @@ -380,7 +364,7 @@ namespace HexcalMC { _mNMotorState = _acs.GetMotorState((Axis)iAxisNo); - // Returned value is integer, you need to use bitmaks + // Returned value is integer, you need to use bitmaks if ((_mNMotorState & MotorStates.ACSC_MST_MOVE) != 0) lblMoving.Image = Resources.On; else lblMoving.Image = Resources.Off; // 运动中 if ((_mNMotorState & MotorStates.ACSC_MST_INPOS) != 0) lblInPos.Image = Resources.On; @@ -390,16 +374,14 @@ namespace HexcalMC if ((_mNMotorState & MotorStates.ACSC_MST_ENABLE) != 0) lblEnable.Image = Resources.On; else lblEnable.Image = Resources.Off; // 使能 - // Reference position - // ACSPL+ Variable : RPOS (real) + // Reference position ACSPL+ Variable : RPOS (real) _mLfRPos = _acs.GetRPosition((Axis)iAxisNo); // 参考位置 - // Feedback position (Encoder value) - // ACSPL+ Variable : FPO (real) - _mLfFPos = _acs.GetFPosition((Axis)iAxisNo); //反馈位置 + // Feedback position (Encoder value) ACSPL+ Variable : FPO (real) + _mLfFPos = _acs.GetFPosition((Axis)iAxisNo); //反馈位置 - // PE (Position Error) - // There is no function in library. We need to use ReadVariable function + // PE (Position Error) There is no function in library. We need to use + // ReadVariable function _mLfPe = (double)_acs.ReadVariable("PE", ProgramBuffer.ACSC_NONE, iAxisNo, iAxisNo); //位置误差 // Feedback Velocity @@ -410,9 +392,9 @@ namespace HexcalMC txtPE.Text = string.Format("{0:0.0000}", _mLfPe); txtFVEL.Text = string.Format("{0:0.0000}", _mLfFvel); - // Program State 运动状态 - // State : Compiled, Running, Suspended, Autoroutine is running (ON syntax) - // + // Program State 运动状态 State : Compiled, Running, Suspended, Autoroutine is + // running (ON syntax) + // // ACSPL+ Variable : PST (integer) _mNProgramState = _acs.GetProgramState((ProgramBuffer)iBufferNo); if ((_mNProgramState & ProgramStates.ACSC_PST_RUN) != 0) @@ -426,8 +408,7 @@ namespace HexcalMC lblPRG_Status.Text = "Stop"; } - // Read left/right hardware limits state - // ACSPL+ Variable : FAULT (integer) + // Read left/right hardware limits state ACSPL+ Variable : FAULT (integer) _mObjReadVar = _acs.ReadVariableAsVector("FAULT", ProgramBuffer.ACSC_NONE, 0, _mNTotalAxis - 1); if (_mObjReadVar != null) @@ -442,8 +423,8 @@ namespace HexcalMC } } - // Read digital input/output (Port means all of bits) - // If you want to read only 1 bit (not an integer), use "GetInput" function + // Read digital input/output (Port means all of bits) If you want to read only 1 + // bit (not an integer), use "GetInput" function _mNValues = _acs.GetInputPort(0); // _ACS.ReadVariableAsVector("IN", -1, 0, 0, -1, -1); UpdateIoState(_mNValues, true); @@ -461,7 +442,6 @@ namespace HexcalMC } } - private void UpdateLimitState(int axisNo, int fault) //刷新限位状态 { if (axisNo < MaxUiLimitCnt) @@ -475,7 +455,6 @@ namespace HexcalMC } } - private void UpdateIoState(int value, bool isInput) //刷新IO状态 { int bitUpCnt = 0x01; @@ -526,7 +505,7 @@ namespace HexcalMC lbl_Z_target.Text = textBox_z.Text; } - #endregion + #endregion UI 刷新 #region 电机使能 @@ -535,12 +514,12 @@ namespace HexcalMC // Enable selected axis _acs.Enable((Axis)cboAxisNo.SelectedIndex); - // If you want to enable several axes, - // + // If you want to enable several axes, + // // Ex) Eanble three axes (0, 1, 6) // - // int[] AxisList = new int[] { 0, 1, 6, -1 }; !!!! Important !! Must insert '-1' at the last - // _ACS.EnableM(AxisList); + // int[] AxisList = new int[] { 0, 1, 6, -1 }; !!!! Important !! Must insert '-1' at the + // last _ACS.EnableM(AxisList); } private void BtnDisable_Click(object sender, EventArgs e) @@ -556,7 +535,7 @@ namespace HexcalMC _acs.DisableAll(); } - #endregion + #endregion 电机使能 #region 相对移动 @@ -605,7 +584,7 @@ namespace HexcalMC } } - #endregion + #endregion 相对移动 #region 停止运动 @@ -637,7 +616,7 @@ namespace HexcalMC } } - #endregion + #endregion 停止运动 #region JOG 功能 @@ -671,7 +650,6 @@ namespace HexcalMC } } - private void BtnJogPos_MouseDown(object sender, MouseEventArgs e) { double lfVelocity = 0.0f; @@ -702,7 +680,6 @@ namespace HexcalMC { _acs.Halt((Axis)cboAxisNo.SelectedIndex); - //自定义Jog if (sender is Button button) { @@ -712,18 +689,23 @@ namespace HexcalMC case "btn_X_left": _acs.Halt(Axis.ACSC_AXIS_1); break; + case "btn_X_right": _acs.Halt(Axis.ACSC_AXIS_1); break; + case "btn_Y_Forward": _acs.Halt(Axis.ACSC_AXIS_0); break; + case "btn_Y_Back": _acs.Halt(Axis.ACSC_AXIS_0); break; + case "btn_Z_Up": _acs.Halt(Axis.ACSC_AXIS_8); break; + case "btn_Z_Down": _acs.Halt(Axis.ACSC_AXIS_8); break; @@ -731,7 +713,7 @@ namespace HexcalMC } } - #endregion + #endregion JOG 功能 #region 运行 Buffer Program @@ -774,7 +756,7 @@ namespace HexcalMC _acs.StopBuffer((ProgramBuffer)cboBufferNo.SelectedIndex); } - #endregion + #endregion 运行 Buffer Program #region 修改运动参数 @@ -788,7 +770,7 @@ namespace HexcalMC { // Allow numbers (0 ~ 9), . (DOT), Backspace if ((e.KeyChar >= 0x30 && e.KeyChar <= 0x39) || e.KeyChar == 0x2E || e.KeyChar == 0x08 || - e.KeyChar == (char)Keys.Return || e.KeyChar == (char)Keys.Enter) + e.KeyChar == (char)Keys.Return || e.KeyChar == (char)Keys.Enter) { if (e.KeyChar == 0x2E && textBox.Text.Contains(Convert.ToString(0x2E))) e.KeyChar = (char)0x00; @@ -799,21 +781,25 @@ namespace HexcalMC lfTemp = Convert.ToDouble(textBox.Text.Trim()); switch (textBox.TabIndex) { - // Immediately change value (On the fly) : SetVelocityImm() - // Affect next motion : SetVelocity() + // Immediately change value (On the fly) : SetVelocityImm() Affect + // next motion : SetVelocity() case 0: _acs.SetVelocityImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 1: _acs.SetAccelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 2: _acs.SetDecelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 3: _acs.SetKillDecelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 4: _acs.SetJerkImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; @@ -859,15 +845,19 @@ namespace HexcalMC case 0: _acs.SetVelocityImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 1: _acs.SetAccelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 2: _acs.SetDecelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 3: _acs.SetKillDecelerationImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; + case 4: _acs.SetJerkImm((Axis)cboAxisNo.SelectedIndex, lfTemp); break; @@ -881,7 +871,7 @@ namespace HexcalMC } } - #endregion + #endregion 修改运动参数 #region 事件回调 @@ -896,8 +886,7 @@ namespace HexcalMC { int bit = 0x01; int axisNo = 0; - // Param value is bit number - // Bit Number = Axis Number + // Param value is bit number Bit Number = Axis Number for (int i = 0; i < 64; i++) { if ((int)axis == bit) @@ -927,13 +916,11 @@ namespace HexcalMC lstLog.Items.Add("PROGRAM_END event enabled"); } - private void ACS_PROGRAMEND(BufferMasks buffer) { int bit = 0x01; int bufferNo = 0; - // Param value is bit number - // Bit Number = Axis Number + // Param value is bit number Bit Number = Axis Number for (int i = 0; i < 32; i++) { if ((int)buffer == bit) @@ -953,7 +940,7 @@ namespace HexcalMC }); } - #endregion + #endregion 事件回调 #region 命令行响应 @@ -998,8 +985,7 @@ namespace HexcalMC rtxtTerminal.ScrollToCaret(); } - #endregion - + #endregion 命令行响应 #region 按钮 @@ -1007,7 +993,7 @@ namespace HexcalMC { //判断是否为textBox_x空 if (string.IsNullOrWhiteSpace(textBox_x.Text) || string.IsNullOrEmpty(textBox_y.Text) || - string.IsNullOrEmpty(textBox_z.Text)) + string.IsNullOrEmpty(textBox_z.Text)) { MessageBox.Show("输入文本框为空,请修改", "警告"); return; @@ -1019,8 +1005,7 @@ namespace HexcalMC double z = double.Parse(textBox_z.Text); Point3D _point3D = new Point3D(x, y, z); - - if (MainFrom.IsWithinLimit(_point3D)) //判断点是否在行程范围内 + if (MainForm.IsWithinLimit(_point3D)) //判断点是否在行程范围内 { double[] pointsArray = { @@ -1053,7 +1038,6 @@ namespace HexcalMC } } - private void btn_home_Click(object sender, EventArgs e) //回零 { double[] pointsArray = @@ -1125,7 +1109,6 @@ namespace HexcalMC { _mJogVel = Convert.ToDouble(txtJogVel.Text.Trim()); - _acs.Jog( MotionFlags.ACSC_AMF_VELOCITY, // Velocity flag 速度标志 Axis.ACSC_AXIS_1, // Axis number @@ -1172,7 +1155,6 @@ namespace HexcalMC } } - private void btn_Y_Back_MouseDown(object sender, MouseEventArgs e) { try @@ -1181,7 +1163,6 @@ namespace HexcalMC { _mJogVel = Convert.ToDouble(txtJogVel.Text.Trim()); - _acs.Jog( MotionFlags.ACSC_AMF_VELOCITY, // Velocity flag 速度标志 Axis.ACSC_AXIS_0, // Axis number @@ -1237,7 +1218,6 @@ namespace HexcalMC { _mJogVel = Convert.ToDouble(txtJogVel.Text.Trim()); - _acs.Jog( MotionFlags.ACSC_AMF_VELOCITY, // Velocity flag 速度标志 Axis.ACSC_AXIS_8, // Axis number @@ -1257,9 +1237,6 @@ namespace HexcalMC } } - - - - #endregion + #endregion 按钮 } } \ No newline at end of file diff --git a/HexcalMC/Program.cs b/HexcalMC/Program.cs index f55bcbb..4b16055 100644 --- a/HexcalMC/Program.cs +++ b/HexcalMC/Program.cs @@ -1,9 +1,8 @@ -using System; +using HexcalMC.Base; +using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; -using HexcalMC.Base; - //////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -49,7 +48,7 @@ namespace HexcalMC Trace.Listeners.Add(logger); MyBase.TraceWriteLine("--软件Main函数开始"); - Application.Run(new MainFrom()); + Application.Run(new MainForm()); } } } \ No newline at end of file diff --git a/HexcalMC/Properties/AssemblyInfo.cs b/HexcalMC/Properties/AssemblyInfo.cs index cdbfe6f..706f209 100644 --- a/HexcalMC/Properties/AssemblyInfo.cs +++ b/HexcalMC/Properties/AssemblyInfo.cs @@ -1,8 +1,6 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - [assembly: AssemblyTitle("HexcalMC")] [assembly: AssemblyDescription("本软件为海克斯康制造智能技术(青岛)有限公司(简称海克斯康)版权所有。本软件的任何部分,未经海克斯康公司事前书面许可,均不能复制、存储在检索系统中,或者以任何形式传播,或以任何方法(机械、电子及其他方法)影印、复制。")] [assembly: AssemblyConfiguration("")] @@ -12,12 +10,9 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - [assembly: ComVisible(false)] - [assembly: Guid("6215eb36-92d3-4f96-9331-1e8cbda161f4")] - [assembly: AssemblyVersion("0.0.3")] -[assembly: AssemblyFileVersion("0.0.3")] +[assembly: AssemblyFileVersion("0.0.3")] \ No newline at end of file diff --git a/HexcalMC/Resources/speed.png b/HexcalMC/Resources/speed.png new file mode 100644 index 0000000..278ef9e Binary files /dev/null and b/HexcalMC/Resources/speed.png differ diff --git a/HexcalMC/app.config b/HexcalMC/app.config index 8b88e42..220ed47 100644 --- a/HexcalMC/app.config +++ b/HexcalMC/app.config @@ -1,12 +1,12 @@ - + - - + + - + \ No newline at end of file diff --git a/HexcalMC/bin/Debug/HexcalMC.exe b/HexcalMC/bin/Debug/HexcalMC.exe deleted file mode 100644 index 95d7eee..0000000 Binary files a/HexcalMC/bin/Debug/HexcalMC.exe and /dev/null differ diff --git a/HexcalMC/bin/x64/Debug/HexCal.exe.11172.dmp b/HexcalMC/bin/x64/Debug/HexCal.exe.11172.dmp deleted file mode 100644 index 98d16b4..0000000 Binary files a/HexcalMC/bin/x64/Debug/HexCal.exe.11172.dmp and /dev/null differ diff --git a/HexcalMC/bin/x64/Debug/HexcalMC.application b/HexcalMC/bin/x64/Debug/HexcalMC.application index 9d97852..63e7384 100644 --- a/HexcalMC/bin/x64/Debug/HexcalMC.application +++ b/HexcalMC/bin/x64/Debug/HexcalMC.application @@ -21,7 +21,7 @@ - kw+hFfwnop75FYiMm+CkPl/Ui8EpkT8BuDrRnaok1W0= + lMEM3iQRIwDj5xL2Aaf6lHIuYYSvnK+5moagVnmWLIY= diff --git a/HexcalMC/bin/x64/Debug/HexcalMC.exe b/HexcalMC/bin/x64/Debug/HexcalMC.exe index 18f02ff..ea5470e 100644 Binary files a/HexcalMC/bin/x64/Debug/HexcalMC.exe and b/HexcalMC/bin/x64/Debug/HexcalMC.exe differ diff --git a/HexcalMC/bin/x64/Debug/HexcalMC.exe.config b/HexcalMC/bin/x64/Debug/HexcalMC.exe.config index 8b88e42..220ed47 100644 --- a/HexcalMC/bin/x64/Debug/HexcalMC.exe.config +++ b/HexcalMC/bin/x64/Debug/HexcalMC.exe.config @@ -1,12 +1,12 @@ - + - - + + - + \ No newline at end of file diff --git a/HexcalMC/bin/x64/Debug/HexcalMC.exe.manifest b/HexcalMC/bin/x64/Debug/HexcalMC.exe.manifest index b169e22..f0c23c3 100644 --- a/HexcalMC/bin/x64/Debug/HexcalMC.exe.manifest +++ b/HexcalMC/bin/x64/Debug/HexcalMC.exe.manifest @@ -55,14 +55,14 @@ - + - 5H0iiPDRd1YOihJPwyJD4tzIp7TNDn1eXdVNt91nqVg= + c//54i6mMSmqOysqRtrzN/1gINwehBCIXR60WbnWmfY= @@ -303,13 +303,13 @@ 32HYGtH3JppndtsILkwzp/GWhHh3Iq4VdbCQd7284ww= - + - oxwFyeXS8BP9BAS4rAjMQJBMtU+40+onBndoHIYtMog= + Nehw75tWncmh7MKTU45ORJgPSsOI3SkwzVWBcGk6J7w= diff --git a/HexcalMC/bin/x64/Debug/HexcalMC.pdb b/HexcalMC/bin/x64/Debug/HexcalMC.pdb index 59e4f5e..8db9bcf 100644 Binary files a/HexcalMC/bin/x64/Debug/HexcalMC.pdb and b/HexcalMC/bin/x64/Debug/HexcalMC.pdb differ diff --git a/HexcalMC/bin/x64/Debug/app.publish/HexcalMC.exe b/HexcalMC/bin/x64/Debug/app.publish/HexcalMC.exe index 7ec9d39..ea5470e 100644 Binary files a/HexcalMC/bin/x64/Debug/app.publish/HexcalMC.exe and b/HexcalMC/bin/x64/Debug/app.publish/HexcalMC.exe differ