公司的切割机用的是梅塞尔的等离子切割机,技术部在下发套料图的时候,会将指令文件做简化处理,形式如下:
%
N4G92X0Y0
N6F5000
N8G71
N10G91
N12G00X561.243Y1277.726
N14M11
N16M09
N18G01X75.071Y160.966
N20G01X-44.119Y75.746
N22G01X86.386Y14.882
N24G01X75.071Y160.966
N26M10
N28G00X53.516Y-0.015
N30M09
N32G01X-75.071Y-160.966
N34G01X44.119Y-75.746
N36G01X-86.386Y-14.882
N38G01X-75.071Y-160.966
N40M10
N42G00X15.005Y495.332
N44M09
N46G01X94.148Y237.483
N48G01X-48.505Y73.015
N50G01X85.359Y19.946
N168G03X2.562Y9.562I-3.5J6.062
查了一下手册,%表示指令文件开始;N指令表示行号,但是我没弄明白为什么行号都是偶数;G命令表示切割与定位。XYIJ表示坐标命令,XY为相对相对坐标增量,IJ表示圆心(由于我公司加工的钢板零件都是直线和圆弧形式,且切割点都为相对增量模式,所以其他命令暂时没有研究)
以N168G03X2.562Y9.562I-3.5J6.062 这句指令为例:N168,表示是第168个指令行,对切割机无实际作用,可忽略;G03,表示以顺时针方向切割圆弧,起始点为上一命令的切割点,终止点为此命令行的坐标点,即为X2.562Y9.562,圆心为I-3.5J6.062。注意:坐标值皆以上一命令点为基准作增量运算。
命令代码绝大多数为 G00(走空直线,即切割嘴不点火定位)、G01(切割直线,即切割嘴点火切割定位)、G02/G03(切割圆弧,G02为顺时针切割,G03为逆时针切割)
I和J为圆心坐标。
下面用C#解析G代码文件:
1. 先把指令文件中与切割无关的命令过滤掉,生成一个临时的指令文件:
1 public static string GenFile(string filename) 2 { 3 StreamReader r_sReader = new StreamReader(filename); 4 StreamWriter w_sWriter = new StreamWriter(filename + ".tmp"); 5 6 string r_Line = string.Empty; 7 int g_index = -1; 8 r_Line = r_sReader.ReadLine(); 9 if (r_Line != "%") 10 { 11 r_sReader.Close(); 12 w_sWriter.Close(); 13 throw new Exception("命令: " + filename + " 的格式不在本程序处理范围内,请联系软件开发人员!"); 14 } 15 for (; r_Line != null; r_Line = r_sReader.ReadLine()) 16 { 17 if (Lex.FindKey(r_Line, "G0") && Lex.FindKey(r_Line, 'X')) 18 { 19 g_index = r_Line.IndexOf("G0"); 20 r_Line = r_Line.Substring(g_index); 21 w_sWriter.WriteLine(r_Line); 22 } 23 } 24 r_sReader.Close(); 25 w_sWriter.Close(); 26 return filename + ".tmp"; 27 }
生成的临时指令文件类似于:
G01X-733.139Y129.272
G01X-61.743Y-62.224
G01X-36.738Y79.589
G03X7.0Y7.0I0.0J7.0
G01X0.0Y534.0
G03X35.0Y35.0I0.0J35.0
G01X185.0Y0.0
这样看起来就清晰多了。只需要处理G命令和相应的坐标了:
1 public static void CalculateLength(string filename, out double CuttingLength, out Double WalkLength, out int FireTimes) 2 { 3 CuttingLength = 0.0; 4 WalkLength = 0.0; 5 FireTimes = 0; 6 7 Point2D p_start = new Point2D(0.0, 0.0); 8 Point2D p_end = new Point2D(0.0, 0.0); 9 Point2D p_center = new Point2D(0.0, 0.0); 10 11 Arc2D tmp_arc = new Arc2D(p_center, p_start, p_end); 12 LineSegment2D tmp_seg = new LineSegment2D(p_start, p_end); 13 StreamReader r_sReader = new StreamReader(filename); 14 // read line. 15 string r_Line = string.Empty; 16 // instruction appearing in current line. 17 string instruction = string.Empty; 18 #region Relative Coordinate 19 Point2D p_current = new Point2D(); 20 for (int i=0; r_Line != null; r_Line = r_sReader.ReadLine(), i++) 21 { 22 if (r_Line == string.Empty) 23 continue; 24 25 instruction = GetAxisValue(r_Line, 'G'); 26 p_current.X = Convert.ToDouble(GetAxisValue(r_Line, 'X')); 27 p_current.Y = Convert.ToDouble(GetAxisValue(r_Line, 'Y')); 28 29 switch (instruction) 30 { 31 case "00": 32 tmp_seg.EndPt.X = p_current.X; 33 tmp_seg.EndPt.Y = p_current.Y; 34 WalkLength += tmp_seg.Length; 35 FireTimes++; 36 break; 37 case "01": 38 tmp_seg.EndPt.X = p_current.X; 39 tmp_seg.EndPt.Y = p_current.Y; 40 CuttingLength += tmp_seg.Length; 41 break; 42 case "02": 43 tmp_arc.CenterPt.X = Convert.ToDouble(GetAxisValue(r_Line, 'I')); 44 tmp_arc.CenterPt.Y = Convert.ToDouble(GetAxisValue(r_Line, 'J')); 45 tmp_arc.EndPt.X = p_current.X; 46 tmp_arc.EndPt.Y = p_current.Y; 47 CuttingLength += tmp_arc.ClockWiseLength; 48 break; 49 case "03": 50 tmp_arc.CenterPt.X = Convert.ToDouble(GetAxisValue(r_Line, 'I')); 51 tmp_arc.CenterPt.Y = Convert.ToDouble(GetAxisValue(r_Line, 'J')); 52 tmp_arc.EndPt.X = p_current.X; 53 tmp_arc.EndPt.Y = p_current.Y; 54 CuttingLength += tmp_arc.CounterClockWiseLength; 55 break; 56 } 57 } 58 #endregion 59 r_sReader.Close(); 60 }
上面的代码有我自定义的几个类,用于计算线段长度和圆弧长度:
1 public static class m_Math 2 { 3 public const Double Pi = 3.1415926535; 4 public const Double E = 2.7182818284; 5 public const Double _precision = 0.00001; 6 7 public static Double sqrt(Double n) 8 { 9 double result = 0.0, delta = 0.0; 10 double x = 0.618 * n; 11 do 12 { 13 result = 0.5 * (x + (n / x)); 14 delta = Math.Abs(x - result); 15 x = result; 16 } while (delta > m_Math._precision); 17 return result; 18 } 19 20 public static Double RadianToAngle(Double radian) 21 { 22 return (180.0 * radian) / m_Math.Pi; 23 } 24 25 public static Double AngleToRadian(Double angle) 26 { 27 return (m_Math.Pi * angle) / 180.0; 28 } 29 30 public static double[,] MatrixProduction(double[,] a, double[,] b) 31 { 32 int Length=a.Length; 33 double[,] c=new double[Length,Length]; 34 return c; 35 } 36 } 37 38 public class Point2D 39 { 40 public Point2D() { } 41 public Point2D(Double x,Double y) 42 { 43 this.X = x; 44 this.Y = y; 45 } 46 47 /// <summary> 48 /// X coordinate figure 49 /// </summary> 50 private Double _X; 51 52 /// <summary> 53 /// Y coordinate figure 54 /// </summary> 55 private Double _Y; 56 57 public Double X 58 { 59 get 60 { 61 return this._X; 62 } 63 64 set 65 { 66 this._X = value; 67 } 68 } 69 70 public Double Y 71 { 72 get 73 { 74 return this._Y; 75 } 76 77 set 78 { 79 this._Y = value; 80 } 81 } 82 83 public Double GetDistanceTo(Point2D p) 84 { 85 Double dx = this.X - p.X; 86 Double dy = this.Y - p.Y; 87 return Math.Sqrt(dx * dx + dy * dy); 88 } 89 90 public Vector2D GetVector2DTo(Point2D p) 91 { 92 return new Vector2D(p.X - this.X, p.Y - this.Y); 93 } 94 95 public void SetValueTo(Point2D p) 96 { 97 this.X = p.X; 98 this.Y = p.Y; 99 } 100 101 public bool IsEqualTo(Point2D p) 102 { 103 return (this.X == p.X && this.Y == p.Y); 104 } 105 106 public bool IsOnSeg(LineSegment2D seg) 107 { 108 if (this.IsOnSegLine(seg)) 109 { 110 return (this.X <= Math.Max(seg.StartPt.X, seg.EndPt.X)) && (this.X >= Math.Min(seg.StartPt.X, seg.EndPt.X)); 111 } 112 113 return false; 114 } 115 116 public bool IsOnSegLine(LineSegment2D seg) 117 { 118 Vector2D vseg = seg.StartPt.GetVector2DTo(seg.EndPt); 119 Vector2D vthis = seg.StartPt.GetVector2DTo(this); 120 return Math.Abs(vseg.CrossProductTo(vthis)) < m_Math._precision; 121 } 122 123 public static Point2D operator + (Point2D a, Point2D b) 124 { 125 return new Point2D((a.X + b.X), (a.Y + b.Y)); 126 } 127 128 public static Point2D operator - (Point2D a, Point2D b) 129 { 130 return new Point2D((a.X - b.X), (a.Y - b.Y)); 131 } 132 133 public override String ToString() 134 { 135 return "(" + this.X + "," + this.Y + ")"; 136 } 137 } 138 139 public class LineSegment2D 140 { 141 public LineSegment2D() 142 { 143 this.StartPt = new Point2D(); 144 this.EndPt = new Point2D(); 145 146 } 147 public LineSegment2D(Point2D p1, Point2D p2) 148 { 149 this.StartPt = new Point2D(); 150 this.EndPt = new Point2D(); 151 this.StartPt.SetValueTo(p1); 152 this.EndPt.SetValueTo(p2); 153 } 154 155 private Point2D _StartPt; 156 private Point2D _EndPt; 157 158 public Point2D StartPt 159 { 160 get 161 { 162 return this._StartPt; 163 } 164 165 set 166 { 167 this._StartPt = value; 168 } 169 } 170 171 public Point2D EndPt 172 { 173 get 174 { 175 return this._EndPt; 176 } 177 178 set 179 { 180 this._EndPt = value; 181 } 182 } 183 184 public Double Length 185 { 186 get 187 { 188 return StartPt.GetDistanceTo(EndPt); 189 } 190 } 191 192 public void SetPt(Point2D p1, Point2D p2) 193 { 194 this.StartPt.X = p1.X; 195 this.StartPt.Y = p1.Y; 196 this.EndPt.X = p2.X; 197 this.EndPt.Y = p2.Y; 198 } 199 200 public bool IsIntersectionWith(LineSegment2D seg) 201 { 202 Vector2D s1s2 = this.StartPt.GetVector2DTo(seg.StartPt); 203 Vector2D s1e2 = this.StartPt.GetVector2DTo(seg.EndPt); 204 Vector2D s1e1 = this.StartPt.GetVector2DTo(this.EndPt); 205 double straddle1 = (s1e1.CrossProductTo(s1s2)) * (s1e1.CrossProductTo(s1e2)); 206 Vector2D s2s1 = seg.StartPt.GetVector2DTo(this.StartPt); 207 Vector2D s2e1 = seg.StartPt.GetVector2DTo(this.EndPt); 208 Vector2D s2e2 = seg.StartPt.GetVector2DTo(seg.EndPt); 209 double straddle2 = (s2e2.CrossProductTo(s2s1)) * (s2e2.CrossProductTo(s2e1)); 210 211 if (straddle1 < 0 && straddle2 < 0) 212 { 213 return true; 214 } 215 else 216 { 217 return (this.StartPt.IsOnSeg(seg) || this.EndPt.IsOnSeg(seg) || seg.StartPt.IsOnSeg(this) || seg.EndPt.IsOnSeg(this)); 218 } 219 } 220 } 221 222 public class Vector2D 223 { 224 public Vector2D() { } 225 public Vector2D(Double x, Double y) 226 { 227 this.X = x; 228 this.Y = y; 229 } 230 /// <summary> 231 /// X coordinate figure 232 /// </summary> 233 private Double _X; 234 /// <summary> 235 /// Y coordinate figure 236 /// </summary> 237 private Double _Y; 238 239 public Double X 240 { 241 get 242 { 243 return this._X; 244 } 245 246 set 247 { 248 this._X = value; 249 } 250 } 251 252 public Double Y 253 { 254 get 255 { 256 return this._Y; 257 } 258 259 set 260 { 261 this._Y = value; 262 } 263 } 264 265 public Double Length 266 { 267 get 268 { 269 return Math.Sqrt(this.X * this.X + this.Y * this.Y); 270 } 271 } 272 273 public Double StartAngle 274 { 275 get 276 { 277 Double angle = Math.Acos(this.X / this.Length); 278 if (this.Y<0) 279 { 280 return 2 * m_Math.Pi - angle; 281 } 282 return angle; 283 } 284 } 285 286 /// <summary> 287 /// Counter clock wise Cross-Production to Vector2D(v) 288 /// as the two vectors are both on X-Y plane, their Cross-Production is on Z-Axis 289 /// </summary> 290 public Double CrossProductTo(Vector2D v) 291 { 292 return (this.X * v.Y - this.Y * v.X); 293 } 294 295 public Double GetNormalAngleTo(Vector2D v) 296 { 297 Double DotProduction = this * v; 298 // may be "projection" is 1.00000000002 or some other, in this case, the Math.Acos() method will return NaN(Not a Number). 299 // so if |projection| > 1 and | |projection| - 1 |< _precision, must set it to 1. 300 Double projection = (DotProduction) / (this.Length * v.Length); 301 if (Math.Abs(projection) > 1) 302 { 303 double delta = Math.Abs(Math.Abs(projection) - 1); 304 if (delta < m_Math._precision) 305 { 306 if (projection > 1) 307 { 308 projection = 1; 309 } 310 else if (projection < -1) 311 { 312 projection = -1; 313 } 314 } 315 } 316 return Math.Acos(projection); 317 } 318 319 public bool IsSameDirectionTo(Vector2D v) 320 { 321 Double DeltaAngle = Math.Abs(v.StartAngle-this.StartAngle) ; 322 return DeltaAngle > 0 && DeltaAngle < m_Math._precision; 323 } 324 325 public bool IsOppositeDirectionTo(Vector2D v) 326 { 327 Double DeltaAngle = Math.Abs(Math.Abs(v.StartAngle - this.StartAngle)-m_Math.Pi); 328 return DeltaAngle > 0 && DeltaAngle < m_Math._precision; 329 } 330 331 public bool IsClockWiseTo(Vector2D v) 332 { 333 return this.CrossProductTo(v) < 0; 334 } 335 336 public bool IsCounterClockWiseTo(Vector2D v) 337 { 338 return this.CrossProductTo(v) > 0; 339 } 340 341 public Double GetCounterClockWiseAngleTo(Vector2D v) 342 { 343 Double Angle = this.GetNormalAngleTo(v); 344 if (this.IsClockWiseTo(v)) 345 { 346 return 2 * m_Math.Pi - Angle; 347 } 348 return Angle; 349 } 350 351 public override String ToString() 352 { 353 return "(" + this.X + "," + this.Y + ")"; 354 } 355 356 public static Vector2D operator + (Vector2D a, Vector2D b) 357 { 358 return new Vector2D((a.X + b.X), (a.Y + b.Y)); 359 } 360 361 public static Vector2D operator - (Vector2D a, Vector2D b) 362 { 363 return new Vector2D((a.X - b.X), (a.Y - b.Y)); 364 } 365 366 public static Double operator * (Vector2D a, Vector2D b) 367 { 368 return (a.X * b.X + a.Y * b.Y); 369 } 370 } 371 372 public class Arc2D 373 { 374 public Arc2D() 375 { 376 this.CenterPt = new Point2D(); 377 this.StartPt = new Point2D(); 378 this.EndPt = new Point2D(); 379 } 380 381 public Arc2D(Point2D center, Point2D start, Point2D end) 382 { 383 if (Math.Abs(center.GetDistanceTo(start) - center.GetDistanceTo(end)) > m_Math._precision) 384 { 385 //throw new Exception("Radius length is not equal!"); 386 throw new Exception("半径长度不相等, 不能构成圆!"); 387 } 388 389 this.CenterPt = new Point2D(); 390 this.StartPt = new Point2D(); 391 this.EndPt = new Point2D(); 392 393 this.CenterPt.SetValueTo(center); 394 this.StartPt.SetValueTo(start); 395 this.EndPt.SetValueTo(end); 396 } 397 398 private Point2D _CenterPt; 399 private Point2D _StartPt; 400 private Point2D _EndPt; 401 402 public Point2D CenterPt 403 { 404 get 405 { 406 return this._CenterPt; 407 } 408 409 set 410 { 411 this._CenterPt = value; 412 } 413 } 414 415 public Point2D StartPt 416 { 417 get 418 { 419 return this._StartPt; 420 } 421 422 set 423 { 424 this._StartPt = value; 425 } 426 } 427 428 public Point2D EndPt 429 { 430 get 431 { 432 return this._EndPt; 433 } 434 435 set 436 { 437 this._EndPt = value; 438 } 439 } 440 441 public Vector2D StartRadius 442 { 443 get 444 { 445 return this.CenterPt.GetVector2DTo(this.StartPt); 446 } 447 } 448 449 public Vector2D EndRadius 450 { 451 get 452 { 453 return this.CenterPt.GetVector2DTo(this.EndPt); 454 } 455 } 456 457 public Double RadiusLength 458 { 459 get 460 { 461 return this.StartRadius.Length; 462 } 463 } 464 465 public Double StartAngle 466 { 467 get 468 { 469 return this.StartRadius.StartAngle; 470 } 471 } 472 473 public Double EndAngle 474 { 475 get 476 { 477 return this.EndRadius.StartAngle; 478 } 479 } 480 481 /// <summary> 482 /// Angle is from StartAngle to EndAngle in Counter-Clock-Wise 483 /// </summary> 484 public Double Angle 485 { 486 get 487 { 488 Double angle = this.StartRadius.GetCounterClockWiseAngleTo(this.EndRadius); 489 return angle; 490 } 491 } 492 493 public Double Bulge 494 { 495 get 496 { 497 return Math.Tan((this.EndAngle - this.StartAngle) / 4.0); 498 } 499 } 500 501 public Double CounterClockWiseLength 502 { 503 get 504 { 505 return this.StartRadius.Length * this.Angle; 506 } 507 } 508 509 public Double ClockWiseLength 510 { 511 get 512 { 513 return 2 * m_Math.Pi * this.StartRadius.Length - this.CounterClockWiseLength; 514 } 515 } 516 517 public Double CounterClockWiseArea 518 { 519 get 520 { 521 return RadiusLength * RadiusLength * this.Angle; 522 } 523 } 524 525 public Double ClockWisePatchArea 526 { 527 get 528 { 529 return 2 * m_Math.Pi * this.RadiusLength * this.RadiusLength - this.CounterClockWiseArea; 530 } 531 } 532 }
Point2D 是二维点类,方便计算线段长度;LineSegment2D是线段类,没有方向,只用于计算长度;
Vector2D二维向量类,用于计算圆弧的圆心角;Arc2D圆弧类,用于计算顺时针与逆时针的圆弧长度。
还有两个方法:
用于检验是否是数字:
public static bool IsDigit(char ch) { return (ch >= '0' && ch <= '9'); }
用于获取指令值与坐标值:
1 public static string GetAxisValue(string inLine, char AxisSymbol) 2 { 3 string Line = Lex.TrimAllSpace(inLine); 4 int pos = Line.IndexOf(AxisSymbol); 5 if (pos == -1) 6 { 7 // if the current line has no AxisSymbol, then it contains no this AxisSymbol value at all. 8 return string.Empty; 9 } 10 11 //scan the current line until it meets a char that is not a digit. 12 bool DecimalPointFound = false; 13 string m_value = string.Empty; 14 for (int i = pos + 1; i < Line.Length; i++) 15 { 16 if (Lex.IsDigit(Line[i])) 17 { 18 m_value += Line[i]; 19 } 20 // check whether the '-' is a negative sign or not. 21 else if (Line[i] == '-') 22 { 23 if (i < Line.Length - 1) 24 { 25 if ((Line[i - 1] == AxisSymbol) && Lex.IsDigit(Line[i + 1])) 26 { 27 m_value += Line[i]; 28 } 29 } 30 } 31 // if the current char is a dot('.') , and both its previous and latter char are digit, 32 // then it is a decimal point. 33 else if (Line[i] == '.') 34 { 35 if (i < Line.Length - 1) 36 { 37 if (Lex.IsDigit(Line[i - 1]) && Lex.IsDigit(Line[i + 1])) 38 { 39 if (DecimalPointFound) 40 { 41 // if Decimal Point is Found previously, 42 // then the digits set found is not a digit string(e.g. : 2011.04.17). 43 // that is, decimal point only appear once. 44 return string.Empty; 45 } 46 DecimalPointFound = true; 47 m_value += Line[i]; 48 } 49 else if (Lex.IsAlpha(Line[i])) 50 { 51 return string.Empty; 52 } 53 } 54 } 55 else 56 { 57 // if the current char is not a digit, then the left ones are not necessary to be scanned. 58 break; 59 } 60 } 61 return m_value; 62 }
程序还有好多漏洞,待完善。