public static double? VertexTest(Cutter c,Geo.Point e, Geo.Point p) { // c.R and c.r define the cutter // e.x and e.y is the xy-position of the cutter (e.z is ignored) // p is the vertex tested against // q is the distance along xy-plane from e to vertex double q = Math.Sqrt(Math.Pow(e.x - p.x, 2) + Math.Pow((e.y - p.y), 2)); if (q > c.R) { // vertex is outside cutter. no need to do anything! return null; } else if (q <= (c.R - c.r)) { // vertex is in the cylindical/flat part of the cutter return p.z; } else if ((q > (c.R - c.r)) && (q <= c.R)) { // vertex is in the toroidal part of the cutter double h2 = Math.Sqrt(Math.Pow(c.r, 2) - Math.Pow((q - (c.R - c.r)), 2)); double h1 = c.r - h2; return p.z - h1; } else { // SERIOUS ERROR, we should not be here! System.Console.WriteLine("DropCutter: VertexTest: ERROR!"); return null; } } // end VertexTest
public static void search_kdtree(List<Tri> tlist, Point p, Cutter c, kd_node node) { ns+=1; if (node.tris != null) { if (node.tris.Count > 0) { // add all triangles of a bucket node foreach (Tri t in node.tris) { // check that t belongs if ((p.x + c.R) < t.bb.minx) return; else if ((p.x - c.R) > t.bb.maxx) return; else if ((p.y + c.R) < t.bb.miny) return; else if ((p.y - c.R) > t.bb.maxy) return; else tlist.Add(t); } return; } } switch (node.dim) { case 0: // cut along xplus if (node.cutval <= p.x - c.R) search_kdtree(tlist, p, c, node.hi); else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 1: // cut along xminus if (node.cutval >= p.x + c.R) search_kdtree(tlist, p, c, node.lo); else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 2: // cut along yplus if (node.cutval <= p.y - c.R) search_kdtree(tlist, p, c, node.hi); else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 3: // cut along yminus if (node.cutval >= p.y + c.R) search_kdtree(tlist, p, c, node.lo); { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; } return; }
public static void stlmachine(GLWindow g, STLSurf s) { List<Geo.Point> pointlist=new List<Geo.Point>(); // seems to work... // foreach (Geo.Tri t in s.tris) // System.Console.WriteLine("loop1 triangles " + t); // System.Console.ReadKey(); // recalculate normal data // create bounding box data foreach (Geo.Tri t in s.tris) { t.recalc_normals(); // FIXME why don't new values stick?? t.calc_bbox(); // FIXME: why doen't bb-data 'stick' ?? } /* // FIXME: if we check bb-data here it is gone!!(??) foreach (Geo.Tri t in s.tris) { System.Console.WriteLine("loop2 triangles " + t); System.Console.WriteLine("loop2 direct maxx" + t.bb.maxx + " minx:" + t.bb.minx); } System.Console.ReadKey(); */ // find bounding box (this should probably be done in the STLSurf class?) double minx = 0, maxx = 10, miny = 0, maxy = 10; // generate XY pattern (a general zigzag-strategy, needed also for pocketing) double Nx=50; double Ny=50; double dx=(maxx-minx)/(double)(Nx-1); double dy = (maxy - miny) / (double)(Ny-1); double x = minx; for (int n = 0; n < Nx; n++) { if (n%2==0) { double y = miny; for (int m = 0; m < Ny; m++) { pointlist.Add(new Geo.Point(x,y,5)); // System.Console.WriteLine("x:"+x+" y:"+y); y += dy; // System.Console.ReadKey(); } } else { double y = maxy; for (int m = 0; m < Ny; m++) { pointlist.Add(new Geo.Point(x,y,5)); //System.Console.WriteLine("x:" + x + " y:" + y); y -= dy; //System.Console.ReadKey(); } } x += dx; } // drop cutter (i.e. add z-data) double R=1,r=0.2; Cutter cu = new Cutter(R,r); List<Geo.Point> drop_points = new List<Geo.Point>(); double redundant = 0; double checks = 0; foreach (Geo.Point p in pointlist) { double? v1 = null,v2=null,v3=null,z_new=null,f=null,e1=null,e2=null,e3=null; List<double> zlist = new List<double>(); foreach (Geo.Tri t in s.tris) { checks++; t.calc_bbox(); // why do we have to re-calculate bb-data here?? //System.Console.WriteLine("testing triangle" + t); if (t.bb.minx > (p.x + cu.R)) { redundant++; continue; } else if (t.bb.maxx < (p.x - cu.R)) { redundant++; continue; } if (t.bb.miny > (p.y + cu.R)) { redundant++; continue; } if (t.bb.maxy < (p.y - cu.R)) { redundant++; continue; } v1 = DropCutter.VertexTest(cu, p, t.p[0]); v2 = DropCutter.VertexTest(cu, p, t.p[1]); v3 = DropCutter.VertexTest(cu, p, t.p[2]); if (v2 != null) { zlist.Add((double)v2); } if (v1 != null) { zlist.Add((double)v1); } if (v3 != null) { zlist.Add((double)v3); } f = DropCutter.FacetTest(cu, p, t); if (f != null) { zlist.Add((double)f); } e1 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[1]); e2 = DropCutter.EdgeTest(cu, p, t.p[1], t.p[2]); e3 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[2]); if (e1 != null) zlist.Add((double)e1); if (e2 != null) zlist.Add((double)e2); if (e3 != null) zlist.Add((double)e3); /* if (zlist.Count > 1) { System.Console.Write("Before: "); foreach (double d in zlist) System.Console.Write(d.ToString() + " "); System.Console.Write("\n"); } */ zlist.Sort(); /* if (zlist.Count > 2) { System.Console.Write("After: "); foreach (double d in zlist) System.Console.Write(d.ToString() + " "); System.Console.Write("\n"); } */ // System.Console.Write("Sorted: "); // foreach (double d in zlist) // System.Console.Write(d.ToString() + " "); // System.Console.Write("\n"); if (zlist.Count > 0) z_new = zlist[zlist.Count-1]; /* if (zlist.Count > 1) System.Console.WriteLine("chosen: " + z_new); */ // System.Console.ReadKey(); } // end triangle loop if (z_new != null) { drop_points.Add(new Geo.Point(p.x, p.y, (double)z_new)); } } // end point-list loop System.Console.WriteLine("checked: "+ checks + " redundant: " + redundant); System.Console.WriteLine("relevant: "+(checks-redundant) + " ("+100*(double)(checks-redundant)/(double)checks+"%)"); // check to see that STL has not changed // display drop-points int i = 1; Geo.Point p0=new Geo.Point(); foreach (Geo.Point p in drop_points) { if (i == 1) // first move { p0 = new Geo.Point(p.x, p.y, 10); GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Yellow; g.addGeom(l); p0 = p; } else // don't do anything for last move { GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Magenta; g.addGeom(l); p0 = p; } i++; /* GeoPoint pg = new GeoPoint(p); pg.color = System.Drawing.Color.Aqua; g.addGeom(pg); */ } // display zigzag and points /* i = 1; foreach (Geo.Point p in pointlist) { if (i == 1) { p0 = new Geo.Point(p.x, p.y, 10); GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Yellow; g.addGeom(l); p0 = p; } else { GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Cyan; g.addGeom(l); p0 = p; } i++; } */ // dummy test: /* foreach (Geo.Tri t in s.tris) { GeoPoint p = new GeoPoint(t.p[0].x, t.p[0].y, t.p[0].z); pointlist.Add(p); } */ }
public static void stlmachine(STLSurf s, GeoCollection g) { List <Point> pointlist = new List <Point>(); // seems to work... // foreach (Geo.Tri t in s.tris) // System.Console.WriteLine("loop1 triangles " + t); // System.Console.ReadKey(); // recalculate normal data // create bounding box data foreach (Tri t in s.tris) { t.recalc_normals(); // FIXME why don't new values stick?? t.calc_bbox(); // FIXME: why doen't bb-data 'stick' ?? } /* * // FIXME: if we check bb-data here it is gone!!(??) * foreach (Geo.Tri t in s.tris) * { * System.Console.WriteLine("loop2 triangles " + t); * System.Console.WriteLine("loop2 direct maxx" + t.bb.maxx + " minx:" + t.bb.minx); * } * System.Console.ReadKey(); */ // find bounding box (this should probably be done in the STLSurf class?) double minx = 0, maxx = 10, miny = 0, maxy = 10; // generate XY pattern (a general zigzag-strategy, needed also for pocketing) // store in a list called pointlist double Nx = 30; double Ny = 40; double dx = (maxx - minx) / (double)(Nx - 1); double dy = (maxy - miny) / (double)(Ny - 1); double x = minx; for (int n = 0; n < Nx; n++) { if (n % 2 == 0) { double y = miny; for (int m = 0; m < Ny; m++) { pointlist.Add(new Point(x, y, 5)); // System.Console.WriteLine("x:"+x+" y:"+y); y += dy; // go forward in the y-axis direction // System.Console.ReadKey(); } } else { double y = maxy; for (int m = 0; m < Ny; m++) { pointlist.Add(new Point(x, y, 5)); //System.Console.WriteLine("x:" + x + " y:" + y); y -= dy; // go backward in the y-axis direction //System.Console.ReadKey(); } } x += dx; } // drop cutter (i.e. add z-data) double R = 1, r = 0.2; // this is the cutter definition Cutter cu = new Cutter(R, r); List <Point> drop_points = new List <Point>(); double redundant = 0; // number of unneccesary calls to drop-cutter double checks = 0; // number of relevant calls // build the kd-tree Stopwatch st = new Stopwatch(); Console.WriteLine("Building kd-tree. Stopwatch start"); st.Start(); kd_node root; root = kdtree.build_kdtree(s.tris); st.Stop(); Console.WriteLine("Elapsed = {0}", st.Elapsed.ToString()); // FIXME: these calls to drop-cutter are independent of each other // thus the points could/should be divided into many subsets // and each subset is processed by a seprarate thread // this should give a substantial speedup on multi-core cpus Console.WriteLine("Running drop-cutter. Stopwatch start"); st.Start(); foreach (Point p in pointlist) // loop through each point { double?v1 = null, v2 = null, v3 = null, z_new = null, f = null, e1 = null, e2 = null, e3 = null; // store the possible z-values in this list // the highest one of these should be chosen in the end List <double> zlist = new List <double>(); // find triangles under cutter using kd-tree int mode = 1; List <Tri> tris_to_search = new List <Tri>(); if (mode == 0) { tris_to_search = s.tris; } else if (mode == 1) { kdtree.search_kdtree(tris_to_search, p, cu, root); } //Console.WriteLine("searching {0} tris",tris_to_search.Count); //Console.ReadKey(); // loop through each triangle foreach (Tri t in tris_to_search) { checks++; t.calc_bbox(); // FIXME: why do we have to re-calculate bb-data here?? //System.Console.WriteLine("testing triangle" + t); // here are four ways the triangle bounding box can be // outside the cutter bounding box // redundant could be used to test the performance of bucketing/kd-tree if (t.bb.minx > (p.x + cu.R)) { redundant++; continue; } else if (t.bb.maxx < (p.x - cu.R)) { redundant++; continue; } if (t.bb.miny > (p.y + cu.R)) { redundant++; continue; } if (t.bb.maxy < (p.y - cu.R)) { redundant++; continue; } // test cutter against each vertex v1 = DropCutter.VertexTest(cu, p, t.p[0]); v2 = DropCutter.VertexTest(cu, p, t.p[1]); v3 = DropCutter.VertexTest(cu, p, t.p[2]); if (v2 != null) { zlist.Add((double)v2); } if (v1 != null) { zlist.Add((double)v1); } if (v3 != null) { zlist.Add((double)v3); } // test cutter against facet f = DropCutter.FacetTest(cu, p, t); if (f != null) { zlist.Add((double)f); } // test cutter against each edge e1 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[1]); e2 = DropCutter.EdgeTest(cu, p, t.p[1], t.p[2]); e3 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[2]); if (e1 != null) { zlist.Add((double)e1); } if (e2 != null) { zlist.Add((double)e2); } if (e3 != null) { zlist.Add((double)e3); } // now we have some suggestions for z in zlist // by sorting it we get the highest one at the end of the list zlist.Sort(); // if there's anything in the list, return the last element if (zlist.Count > 0) { z_new = zlist[zlist.Count - 1]; } } // end triangle loop // we've gone through all triangles for this XY-location // if we found a z-value, let's add the valid cutter location // to a list drop_points if (z_new != null) { drop_points.Add(new Point(p.x, p.y, (double)z_new)); } } // end point-list loop st.Stop(); Console.WriteLine("Elapsed = {0}", st.Elapsed.ToString()); // print some statistics: System.Console.WriteLine("checked: " + checks + " redundant: " + redundant); double fraction = (100 * (double)(checks - redundant) / (double)checks); System.Console.WriteLine("relevant: " + (checks - redundant) + " (" + fraction.ToString("N3") + "%)"); // FIXME: now a toolpath object should be created // that has rapids/feeds according to the points calculated above int i = 1; Point p0 = new Point(); // this is needed so we get decimal points, not commas System.Globalization.CultureInfo glob = new System.Globalization.CultureInfo("en-GB"); Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-GB"); foreach (Point p in drop_points) { if (i == 1) // first move { p0 = new Point(p.x, p.y, 12); camtest.outfile.WriteLine("Cylinder"); camtest.outfile.WriteLine("{0},{1},{2}", p0.x.ToString("0.000", glob), p0.y.ToString("0.000", glob), p0.z.ToString("0.000", glob)); camtest.outfile.WriteLine("{0}", 0.01.ToString("0.000", glob)); camtest.outfile.WriteLine("{0},{1},{2}", p.x.ToString("0.000", glob), p.y.ToString("0.000", glob), p.z.ToString("0.000", glob)); Line l = new Line(p0, p); g.add(l); // ADD geometry to toolpath p0 = p; } else { camtest.outfile.WriteLine("Cylinder"); camtest.outfile.WriteLine("{0},{1},{2}", p0.x.ToString("0.000", glob), p0.y.ToString("0.000", glob), p0.z.ToString("0.000", glob)); camtest.outfile.WriteLine("{0}", 0.01.ToString("0.000", glob)); camtest.outfile.WriteLine("{0},{1},{2}", p.x.ToString("0.000", glob), p.y.ToString("0.000", glob), p.z.ToString("0.000", glob)); Line l = new Line(p0, p); g.add(l); // ADD geometry to toolpath p0 = p; } i++; } }
public static double? EdgeTest(Cutter cu, Point e, Point p1, Point p2) { // contact cutter against edge from p1 to p2 // translate segment so that cutter is at (0,0) Point start = new Point(p1.x - e.x, p1.y - e.y, p1.z); Point end = new Point(p2.x - e.x, p2.y - e.y, p2.z); // find angle btw. segment and X-axis double dx = end.x - start.x; double dy = end.y - start.y; double alfa; if (dx != 0) alfa = Math.Atan(dy / dx); else alfa = Math.PI / 2; //alfa = -alfa; // rotation matrix for rotation around z-axis: // should probably implement a matrix class later // rotate by angle alfa // need copy of data that does not change as we go through each line: double sx = start.x, sy = start.y, ex = end.x, ey = end.y; start.x = sx * Math.Cos(alfa) + sy * Math.Sin(alfa); start.y = -sx * Math.Sin(alfa) + sy * Math.Cos(alfa); end.x = ex * Math.Cos(alfa) + ey * Math.Sin(alfa); end.y = -ex * Math.Sin(alfa) + ey * Math.Cos(alfa); // check if segment is below cutter if (start.y > 0) { alfa = alfa+Math.PI; start.x = sx * Math.Cos(alfa) + sy * Math.Sin(alfa); start.y = -sx * Math.Sin(alfa) + sy * Math.Cos(alfa); end.x = ex * Math.Cos(alfa) + ey * Math.Sin(alfa); end.y = -ex * Math.Sin(alfa) + ey * Math.Cos(alfa); } if (Math.Abs(start.y-end.y)>0.0000001) { System.Console.WriteLine("EdgeTest ERROR! (start.y - end.y) = " +(start.y-end.y)); return null; } double l = -start.y; // distance from cutter to edge if (l < 0) System.Console.WriteLine("EdgeTest ERROR! l<0 !"); // System.Console.WriteLine("l=" + l+" start.y="+start.y+" end.y="+end.y); // now we have two different algorithms depending on the cutter: if (cu.r == 0) { // this is the flat endmill case // it is easier and faster than the general case, so we handle it separately if (l > cu.R) // edge is outside of the cutter return null; else // we are inside the cutter { // so calculate CC point double xc1 = Math.Sqrt(Math.Pow(cu.R, 2) - Math.Pow(l, 2)); double xc2 = -xc1; double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // choose the higher point double zc,xc; if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } // now that we have a CC point, check if it's in the edge if ((start.x > xc) && (xc < end.x)) return null; else if ((end.x < xc) && (xc > start.x)) return null; else return zc; } // unreachable place (according to compiler) } // end of flat endmill (r=0) case else if (cu.r > 0) { // System.Console.WriteLine("edgetest r>0 case!"); // this is the general case (r>0) ball-nose or bull-nose (spherical or toroidal) // later a separate case for the ball-cutter might be added (for performance) double xd=0, w=0, h=0, xd1=0, xd2=0, xc=0 , ze=0, zc=0; if (l > cu.R) // edge is outside of the cutter return null; else if (((cu.R-cu.r)<l)&&(l<=cu.R)) { // toroidal case xd=0; // center of ellipse w=Math.Sqrt(Math.Pow(cu.R,2)-Math.Pow(l,2)); // width of ellipse h=Math.Sqrt(Math.Pow(cu.r,2)-Math.Pow((l-(cu.R-cu.r)),2)); // height of ellipse } else if ((cu.R-cu.r)>=l) { // quarter ellipse case xd1=Math.Sqrt( Math.Pow((cu.R-cu.r),2)-Math.Pow(l,2)); xd2=-xd1; h=cu.r; // ellipse height w=Math.Sqrt( Math.Pow(cu.R,2)-Math.Pow(l,2) )- Math.Sqrt( Math.Pow((cu.R-cu.r),2)-Math.Pow(l,2) ); // ellipse height } // now there is a special case where the theta calculation will fail if // the segment is horziontal, i.e. start.z==end.z so we need to catch that here if (start.z==end.z) { if ((cu.R-cu.r)<l) { // half-ellipse case xc=0; h=Math.Sqrt(Math.Pow(cu.r,2)-Math.Pow((l-(cu.R-cu.r)),2)); ze = start.z + h - cu.r; } else if ((cu.R - cu.r) > l) { // quarter ellipse case xc = 0; ze = start.z; } // now we have a CC point // so we need to check if the CC point is in the edge if (isinrange(start.x, end.x, xc)) return ze; else return null; } // end horizontal edge special case // now the general case where the theta calculation works double theta = Math.Atan( h*(start.x-end.x)/(w*(start.z-end.z)) ); // based on this calculate the CC point if (((cu.R - cu.r) < l) && (cu.R <= l)) { // half-ellipse case double xc1 = xd + Math.Abs(w * Math.Cos(theta)); double xc2 = xd - Math.Abs(w * Math.Cos(theta)); double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // select the higher point: if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } } else if ((cu.R - cu.r) > l) { // quarter ellipse case double xc1 = xd1 + Math.Abs(w * Math.Cos(theta)); double xc2 = xd2 - Math.Abs(w * Math.Cos(theta)); double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // select the higher point: if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } } // now we have a valid xc value, so calculate the ze value: ze = zc + Math.Abs(h * Math.Sin(theta)) - cu.r; // finally, check that the CC point is in the edge if (isinrange(start.x,end.x,xc)) return ze; else return null; // this line is unreachable (according to compiler) } // end of toroidal/spherical case // if we ever get here it is probably a serious error! System.Console.WriteLine("EdgeTest: ERROR: no case returned a valid ze!"); return null; }
public static double? VertexTest(Cutter c,Point e, Point p) { // c.R and c.r define the cutter // e.x and e.y is the xy-position of the cutter (e.z is ignored) // p is the vertex tested against // q is the distance along xy-plane from e to vertex double q = Math.Sqrt(Math.Pow(e.x - p.x, 2) + Math.Pow((e.y - p.y), 2)); if (q > c.R) { // vertex is outside cutter. no need to do anything! return null; } else if (q <= (c.R - c.r)) { // vertex is in the cylindical/flat part of the cutter return p.z; } else if ((q > (c.R - c.r)) && (q <= c.R)) { // vertex is in the toroidal part of the cutter double h2 = Math.Sqrt(Math.Pow(c.r, 2) - Math.Pow((q - (c.R - c.r)), 2)); double h1 = c.r - h2; return p.z - h1; } else { // SERIOUS ERROR, we should not be here! System.Console.WriteLine("DropCutter: VertexTest: ERROR!"); return null; } }
public static double? FacetTest(Cutter cu, Point e, Tri t) { // local copy of the surface normal t.recalc_normals(); // don't trust the pre-calculated normal! calculate it separately here. Vector n = new Vector(t.n.x, t.n.y, t.n.z); Point cc; if (n.z == 0) { // vertical plane, can't touch cutter against that! return null; } else if (n.z < 0) { // flip the normal so it points up (? is this always required?) n = -1*n; } // define plane containing facet double a = n.x; double b = n.y; double c = n.z; double d = - n.x * t.p[0].x - n.y * t.p[0].y - n.z * t.p[0].z; // the z-direction normal is a special case (?required?) // in debug phase, see if this is a useful case! if ((a == 0) && (b == 0)) { // System.Console.WriteLine("facet-test:z-dir normal case!"); e.z = t.p[0].z; cc = new Point(e.x,e.y,e.z); if (isinside(t, cc)) { // System.Console.WriteLine("facet-test:z-dir normal case!, returning {0}",e.z); // System.Console.ReadKey(); return e.z; } else return null; } // System.Console.WriteLine("facet-test:general case!"); // facet test general case // uses trigonometry, so might be too slow? // flat endmill and ballnose should be simple to do without trig // toroidal case might require offset-ellipse idea? /* theta = asin(c); zf= -d/c - (a*xe+b*ye)/c+ (R-r)/tan(theta) + r/sin(theta) -r; e=[xe ye zf]; u=[0 0 1]; rc=e + ((R-r)*tan(theta)+r)*u - ((R-r)/cos(theta) + r)*n; t=isinside(p1,p2,p3,rc); */ double theta = Math.Asin(c); double zf = -d/c - (a*e.x+b*e.y)/c + (cu.R-cu.r)/Math.Tan(theta) + cu.r/Math.Sin(theta) - cu.r; Vector ve = new Vector(e.x,e.y,zf); Vector u = new Vector(0,0,1); Vector rc = new Vector(); rc = ve +((cu.R-cu.r)*Math.Tan(theta)+cu.r)*u - ((cu.R-cu.r)/Math.Cos(theta)+cu.r)*n; /* if (rc.z > 1000) System.Console.WriteLine("z>1000 !"); */ cc = new Point(rc.x, rc.y, rc.z); // check that CC lies in plane: // a*rc(1)+b*rc(2)+c*rc(3)+d double test = a * cc.x + b * cc.y + c * cc.z + d; if (test > 0.000001) System.Console.WriteLine("FacetTest ERROR! CC point not in plane"); if (isinside(t, cc)) { if (Math.Abs(zf) > 100) { System.Console.WriteLine("serious problem... at" +e.x + "," + e.y); } return zf; } else return null; }
} // end VertexTest public static double? FacetTest(Cutter cu, Geo.Point e, Geo.Tri t) { // local copy of the surface normal t.recalc_normals(); // don't trust the pre-calculated normal! calculate it separately here. Vector n = new Vector(t.n.x, t.n.y, t.n.z); Geo.Point cc; if (n.z == 0) { // vertical plane, can't touch cutter against that! return null; } else if (n.z < 0) { // flip the normal so it points up (? is this always required?) n = -1*n; } // define plane containing facet double a = n.x; double b = n.y; double c = n.z; double d = - n.x * t.p[0].x - n.y * t.p[0].y - n.z * t.p[0].z; // the z-direction normal is a special case (?required?) // in debug phase, see if this is a useful case! if ((a == 0) && (b == 0)) { // System.Console.WriteLine("facet-test:z-dir normal case!"); e.z = t.p[0].z; cc = new Geo.Point(e.x,e.y,e.z); if (isinside(t, cc)) { // System.Console.WriteLine("facet-test:z-dir normal case!, returning {0}",e.z); // System.Console.ReadKey(); return e.z; } else return null; } // System.Console.WriteLine("facet-test:general case!"); // facet test general case // uses trigonometry, so might be too slow? // flat endmill and ballnose should be simple to do without trig // toroidal case might require offset-ellipse idea? /* theta = asin(c); zf= -d/c - (a*xe+b*ye)/c+ (R-r)/tan(theta) + r/sin(theta) -r; e=[xe ye zf]; u=[0 0 1]; rc=e + ((R-r)*tan(theta)+r)*u - ((R-r)/cos(theta) + r)*n; t=isinside(p1,p2,p3,rc); */ double theta = Math.Asin(c); double zf = -d/c - (a*e.x+b*e.y)/c + (cu.R-cu.r)/Math.Tan(theta) + cu.r/Math.Sin(theta) - cu.r; Vector ve = new Vector(e.x,e.y,zf); Vector u = new Vector(0,0,1); Vector rc = new Vector(); rc = ve +((cu.R-cu.r)*Math.Tan(theta)+cu.r)*u - ((cu.R-cu.r)/Math.Cos(theta)+cu.r)*n; /* if (rc.z > 1000) System.Console.WriteLine("z>1000 !"); */ cc = new Geo.Point(rc.x, rc.y, rc.z); // check that CC lies in plane: // a*rc(1)+b*rc(2)+c*rc(3)+d double test = a * cc.x + b * cc.y + c * cc.z + d; if (test > 0.000001) System.Console.WriteLine("FacetTest ERROR! CC point not in plane"); if (isinside(t, cc)) { if (Math.Abs(zf) > 100) { System.Console.WriteLine("serious problem... at" +e.x + "," + e.y); } return zf; } else return null; } // end FacetTest
} // end FacetTest public static double? EdgeTest(Cutter cu, Geo.Point e, Geo.Point p1, Geo.Point p2) { // contact cutter against edge from p1 to p2 // translate segment so that cutter is at (0,0) Geo.Point start = new Geo.Point(p1.x - e.x, p1.y - e.y, p1.z); Geo.Point end = new Geo.Point(p2.x - e.x, p2.y - e.y, p2.z); // find angle btw. segment and X-axis double dx = end.x - start.x; double dy = end.y - start.y; double alfa; if (dx != 0) alfa = Math.Atan(dy / dx); else alfa = Math.PI / 2; //alfa = -alfa; // rotation matrix for rotation around z-axis: // should probably implement a matrix class later // rotate by angle alfa // need copy of data that does not change as we go through each line: double sx = start.x, sy = start.y, ex = end.x, ey = end.y; start.x = sx * Math.Cos(alfa) + sy * Math.Sin(alfa); start.y = -sx * Math.Sin(alfa) + sy * Math.Cos(alfa); end.x = ex * Math.Cos(alfa) + ey * Math.Sin(alfa); end.y = -ex * Math.Sin(alfa) + ey * Math.Cos(alfa); // check if segment is below cutter if (start.y > 0) { alfa = alfa+Math.PI; start.x = sx * Math.Cos(alfa) + sy * Math.Sin(alfa); start.y = -sx * Math.Sin(alfa) + sy * Math.Cos(alfa); end.x = ex * Math.Cos(alfa) + ey * Math.Sin(alfa); end.y = -ex * Math.Sin(alfa) + ey * Math.Cos(alfa); } if (Math.Abs(start.y-end.y)>0.0000001) { System.Console.WriteLine("EdgeTest ERROR! (start.y - end.y) = " +(start.y-end.y)); return null; } double l = -start.y; // distance from cutter to edge if (l < 0) System.Console.WriteLine("EdgeTest ERROR! l<0 !"); // System.Console.WriteLine("l=" + l+" start.y="+start.y+" end.y="+end.y); // now we have two different algorithms depending on the cutter: if (cu.r == 0) { // this is the flat endmill case // it is easier and faster than the general case, so we handle it separately if (l > cu.R) // edge is outside of the cutter return null; else // we are inside the cutter { // so calculate CC point double xc1 = Math.Sqrt(Math.Pow(cu.R, 2) - Math.Pow(l, 2)); double xc2 = -xc1; double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // choose the higher point double zc,xc; if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } // now that we have a CC point, check if it's in the edge if ((start.x > xc) && (xc < end.x)) return null; else if ((end.x < xc) && (xc > start.x)) return null; else return zc; } // unreachable place (according to compiler) } // end of flat endmill (r=0) case else if (cu.r > 0) { // System.Console.WriteLine("edgetest r>0 case!"); // this is the general case (r>0) ball-nose or bull-nose (spherical or toroidal) // later a separate case for the ball-cutter might be added (for performance) double xd=0, w=0, h=0, xd1=0, xd2=0, xc=0 , ze=0, zc=0; if (l > cu.R) // edge is outside of the cutter return null; else if (((cu.R-cu.r)<l)&&(l<=cu.R)) { // toroidal case xd=0; // center of ellipse w=Math.Sqrt(Math.Pow(cu.R,2)-Math.Pow(l,2)); // width of ellipse h=Math.Sqrt(Math.Pow(cu.r,2)-Math.Pow((l-(cu.R-cu.r)),2)); // height of ellipse } else if ((cu.R-cu.r)>=l) { // quarter ellipse case xd1=Math.Sqrt( Math.Pow((cu.R-cu.r),2)-Math.Pow(l,2)); xd2=-xd1; h=cu.r; // ellipse height w=Math.Sqrt( Math.Pow(cu.R,2)-Math.Pow(l,2) )- Math.Sqrt( Math.Pow((cu.R-cu.r),2)-Math.Pow(l,2) ); // ellipse height } // now there is a special case where the theta calculation will fail if // the segment is horziontal, i.e. start.z==end.z so we need to catch that here if (start.z==end.z) { if ((cu.R-cu.r)<l) { // half-ellipse case xc=0; h=Math.Sqrt(Math.Pow(cu.r,2)-Math.Pow((l-(cu.R-cu.r)),2)); ze = start.z + h - cu.r; } else if ((cu.R - cu.r) > l) { // quarter ellipse case xc = 0; ze = start.z; } // now we have a CC point // so we need to check if the CC point is in the edge if (isinrange(start.x, end.x, xc)) return ze; else return null; } // end horizontal edge special case // now the general case where the theta calculation works double theta = Math.Atan( h*(start.x-end.x)/(w*(start.z-end.z)) ); // based on this calculate the CC point if (((cu.R - cu.r) < l) && (cu.R <= l)) { // half-ellipse case double xc1 = xd + Math.Abs(w * Math.Cos(theta)); double xc2 = xd - Math.Abs(w * Math.Cos(theta)); double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // select the higher point: if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } } else if ((cu.R - cu.r) > l) { // quarter ellipse case double xc1 = xd1 + Math.Abs(w * Math.Cos(theta)); double xc2 = xd2 - Math.Abs(w * Math.Cos(theta)); double zc1 = ((xc1 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; double zc2 = ((xc2 - start.x) / (end.x - start.x)) * (end.z - start.z) + start.z; // select the higher point: if (zc1 > zc2) { zc = zc1; xc = xc1; } else { zc = zc2; xc = xc2; } } // now we have a valid xc value, so calculate the ze value: ze = zc + Math.Abs(h * Math.Sin(theta)) - cu.r; // finally, check that the CC point is in the edge if (isinrange(start.x,end.x,xc)) return ze; else return null; // this line is unreachable (according to compiler) } // end of toroidal/spherical case // if we ever get here it is probably a serious error! System.Console.WriteLine("EdgeTest: ERROR: no case returned a valid ze!"); return null; } // end of EdgeTest method
public static void search_kdtree(List <Tri> tlist, Point p, Cutter c, kd_node node) { ns += 1; if (node.tris != null) { if (node.tris.Count > 0) { // add all triangles of a bucket node foreach (Tri t in node.tris) { // check that t belongs if ((p.x + c.R) < t.bb.minx) { return; } else if ((p.x - c.R) > t.bb.maxx) { return; } else if ((p.y + c.R) < t.bb.miny) { return; } else if ((p.y - c.R) > t.bb.maxy) { return; } else { tlist.Add(t); } } return; } } switch (node.dim) { case 0: // cut along xplus if (node.cutval <= p.x - c.R) { search_kdtree(tlist, p, c, node.hi); } else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 1: // cut along xminus if (node.cutval >= p.x + c.R) { search_kdtree(tlist, p, c, node.lo); } else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 2: // cut along yplus if (node.cutval <= p.y - c.R) { search_kdtree(tlist, p, c, node.hi); } else { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; case 3: // cut along yminus if (node.cutval >= p.y + c.R) { search_kdtree(tlist, p, c, node.lo); } { search_kdtree(tlist, p, c, node.hi); search_kdtree(tlist, p, c, node.lo); } break; } return; }
public static void stlmachine(STLSurf s, GeoCollection g) { List<Point> pointlist=new List<Point>(); // seems to work... // foreach (Geo.Tri t in s.tris) // System.Console.WriteLine("loop1 triangles " + t); // System.Console.ReadKey(); // recalculate normal data // create bounding box data foreach (Tri t in s.tris) { t.recalc_normals(); // FIXME why don't new values stick?? t.calc_bbox(); // FIXME: why doen't bb-data 'stick' ?? } /* // FIXME: if we check bb-data here it is gone!!(??) foreach (Geo.Tri t in s.tris) { System.Console.WriteLine("loop2 triangles " + t); System.Console.WriteLine("loop2 direct maxx" + t.bb.maxx + " minx:" + t.bb.minx); } System.Console.ReadKey(); */ // find bounding box (this should probably be done in the STLSurf class?) double minx = 0, maxx = 10, miny = 0, maxy = 10; // generate XY pattern (a general zigzag-strategy, needed also for pocketing) // store in a list called pointlist double Nx=30; double Ny=40; double dx=(maxx-minx)/(double)(Nx-1); double dy = (maxy - miny) / (double)(Ny-1); double x = minx; for (int n = 0; n < Nx; n++) { if (n%2==0) { double y = miny; for (int m = 0; m < Ny; m++) { pointlist.Add(new Point(x,y,5)); // System.Console.WriteLine("x:"+x+" y:"+y); y += dy; // go forward in the y-axis direction // System.Console.ReadKey(); } } else { double y = maxy; for (int m = 0; m < Ny; m++) { pointlist.Add(new Point(x,y,5)); //System.Console.WriteLine("x:" + x + " y:" + y); y -= dy; // go backward in the y-axis direction //System.Console.ReadKey(); } } x += dx; } // drop cutter (i.e. add z-data) double R=1,r=0.2; // this is the cutter definition Cutter cu = new Cutter(R,r); List<Point> drop_points = new List<Point>(); double redundant = 0; // number of unneccesary calls to drop-cutter double checks = 0; // number of relevant calls // build the kd-tree Stopwatch st = new Stopwatch(); Console.WriteLine("Building kd-tree. Stopwatch start"); st.Start(); kd_node root; root = kdtree.build_kdtree(s.tris); st.Stop(); Console.WriteLine("Elapsed = {0}", st.Elapsed.ToString()); // FIXME: these calls to drop-cutter are independent of each other // thus the points could/should be divided into many subsets // and each subset is processed by a seprarate thread // this should give a substantial speedup on multi-core cpus Console.WriteLine("Running drop-cutter. Stopwatch start"); st.Start(); foreach (Point p in pointlist) // loop through each point { double? v1 = null,v2=null,v3=null,z_new=null,f=null,e1=null,e2=null,e3=null; // store the possible z-values in this list // the highest one of these should be chosen in the end List<double> zlist = new List<double>(); // find triangles under cutter using kd-tree int mode = 1; List<Tri> tris_to_search = new List<Tri>(); if (mode == 0) { tris_to_search = s.tris; } else if (mode == 1) { kdtree.search_kdtree(tris_to_search, p, cu, root); } //Console.WriteLine("searching {0} tris",tris_to_search.Count); //Console.ReadKey(); // loop through each triangle foreach (Tri t in tris_to_search) { checks++; t.calc_bbox(); // FIXME: why do we have to re-calculate bb-data here?? //System.Console.WriteLine("testing triangle" + t); // here are four ways the triangle bounding box can be // outside the cutter bounding box // redundant could be used to test the performance of bucketing/kd-tree if (t.bb.minx > (p.x + cu.R)) { redundant++; continue; } else if (t.bb.maxx < (p.x - cu.R)) { redundant++; continue; } if (t.bb.miny > (p.y + cu.R)) { redundant++; continue; } if (t.bb.maxy < (p.y - cu.R)) { redundant++; continue; } // test cutter against each vertex v1 = DropCutter.VertexTest(cu, p, t.p[0]); v2 = DropCutter.VertexTest(cu, p, t.p[1]); v3 = DropCutter.VertexTest(cu, p, t.p[2]); if (v2 != null) { zlist.Add((double)v2); } if (v1 != null) { zlist.Add((double)v1); } if (v3 != null) { zlist.Add((double)v3); } // test cutter against facet f = DropCutter.FacetTest(cu, p, t); if (f != null) { zlist.Add((double)f); } // test cutter against each edge e1 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[1]); e2 = DropCutter.EdgeTest(cu, p, t.p[1], t.p[2]); e3 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[2]); if (e1 != null) zlist.Add((double)e1); if (e2 != null) zlist.Add((double)e2); if (e3 != null) zlist.Add((double)e3); // now we have some suggestions for z in zlist // by sorting it we get the highest one at the end of the list zlist.Sort(); // if there's anything in the list, return the last element if (zlist.Count > 0) z_new = zlist[zlist.Count-1]; } // end triangle loop // we've gone through all triangles for this XY-location // if we found a z-value, let's add the valid cutter location // to a list drop_points if (z_new != null) { drop_points.Add(new Point(p.x, p.y, (double)z_new)); } } // end point-list loop st.Stop(); Console.WriteLine("Elapsed = {0}", st.Elapsed.ToString()); // print some statistics: System.Console.WriteLine("checked: "+ checks + " redundant: " + redundant); double fraction=(100*(double)(checks-redundant)/(double)checks); System.Console.WriteLine("relevant: "+(checks-redundant) + " ("+fraction.ToString("N3")+"%)"); // FIXME: now a toolpath object should be created // that has rapids/feeds according to the points calculated above int i = 1; Point p0=new Point(); // this is needed so we get decimal points, not commas System.Globalization.CultureInfo glob = new System.Globalization.CultureInfo("en-GB"); Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-GB"); foreach (Point p in drop_points) { if (i == 1) // first move { p0 = new Point(p.x, p.y, 12); camtest.outfile.WriteLine("Cylinder"); camtest.outfile.WriteLine("{0},{1},{2}", p0.x.ToString("0.000", glob), p0.y.ToString("0.000", glob), p0.z.ToString("0.000", glob)); camtest.outfile.WriteLine("{0}", 0.01.ToString("0.000", glob)); camtest.outfile.WriteLine("{0},{1},{2}", p.x.ToString("0.000", glob), p.y.ToString("0.000", glob), p.z.ToString("0.000", glob)); Line l = new Line(p0, p); g.add(l); // ADD geometry to toolpath p0 = p; } else { camtest.outfile.WriteLine("Cylinder"); camtest.outfile.WriteLine("{0},{1},{2}", p0.x.ToString("0.000", glob), p0.y.ToString("0.000", glob), p0.z.ToString("0.000", glob)); camtest.outfile.WriteLine("{0}", 0.01.ToString("0.000", glob)); camtest.outfile.WriteLine("{0},{1},{2}", p.x.ToString("0.000", glob), p.y.ToString("0.000", glob), p.z.ToString("0.000", glob)); Line l = new Line(p0, p); g.add(l); // ADD geometry to toolpath p0 = p; } i++; } }
public static void stlmachine(GLWindow g, STLSurf s) { List <Geo.Point> pointlist = new List <Geo.Point>(); // seems to work... // foreach (Geo.Tri t in s.tris) // System.Console.WriteLine("loop1 triangles " + t); // System.Console.ReadKey(); // recalculate normal data // create bounding box data foreach (Geo.Tri t in s.tris) { t.recalc_normals(); // FIXME why don't new values stick?? t.calc_bbox(); // FIXME: why doen't bb-data 'stick' ?? } /* * // FIXME: if we check bb-data here it is gone!!(??) * foreach (Geo.Tri t in s.tris) * { * System.Console.WriteLine("loop2 triangles " + t); * System.Console.WriteLine("loop2 direct maxx" + t.bb.maxx + " minx:" + t.bb.minx); * } * System.Console.ReadKey(); */ // find bounding box (this should probably be done in the STLSurf class?) double minx = 0, maxx = 10, miny = 0, maxy = 10; // generate XY pattern (a general zigzag-strategy, needed also for pocketing) double Nx = 50; double Ny = 50; double dx = (maxx - minx) / (double)(Nx - 1); double dy = (maxy - miny) / (double)(Ny - 1); double x = minx; for (int n = 0; n < Nx; n++) { if (n % 2 == 0) { double y = miny; for (int m = 0; m < Ny; m++) { pointlist.Add(new Geo.Point(x, y, 5)); // System.Console.WriteLine("x:"+x+" y:"+y); y += dy; // System.Console.ReadKey(); } } else { double y = maxy; for (int m = 0; m < Ny; m++) { pointlist.Add(new Geo.Point(x, y, 5)); //System.Console.WriteLine("x:" + x + " y:" + y); y -= dy; //System.Console.ReadKey(); } } x += dx; } // drop cutter (i.e. add z-data) double R = 1, r = 0.2; Cutter cu = new Cutter(R, r); List <Geo.Point> drop_points = new List <Geo.Point>(); double redundant = 0; double checks = 0; foreach (Geo.Point p in pointlist) { double? v1 = null, v2 = null, v3 = null, z_new = null, f = null, e1 = null, e2 = null, e3 = null; List <double> zlist = new List <double>(); foreach (Geo.Tri t in s.tris) { checks++; t.calc_bbox(); // why do we have to re-calculate bb-data here?? //System.Console.WriteLine("testing triangle" + t); if (t.bb.minx > (p.x + cu.R)) { redundant++; continue; } else if (t.bb.maxx < (p.x - cu.R)) { redundant++; continue; } if (t.bb.miny > (p.y + cu.R)) { redundant++; continue; } if (t.bb.maxy < (p.y - cu.R)) { redundant++; continue; } v1 = DropCutter.VertexTest(cu, p, t.p[0]); v2 = DropCutter.VertexTest(cu, p, t.p[1]); v3 = DropCutter.VertexTest(cu, p, t.p[2]); if (v2 != null) { zlist.Add((double)v2); } if (v1 != null) { zlist.Add((double)v1); } if (v3 != null) { zlist.Add((double)v3); } f = DropCutter.FacetTest(cu, p, t); if (f != null) { zlist.Add((double)f); } e1 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[1]); e2 = DropCutter.EdgeTest(cu, p, t.p[1], t.p[2]); e3 = DropCutter.EdgeTest(cu, p, t.p[0], t.p[2]); if (e1 != null) { zlist.Add((double)e1); } if (e2 != null) { zlist.Add((double)e2); } if (e3 != null) { zlist.Add((double)e3); } /* * if (zlist.Count > 1) * { * System.Console.Write("Before: "); * foreach (double d in zlist) * System.Console.Write(d.ToString() + " "); * System.Console.Write("\n"); * } */ zlist.Sort(); /* * if (zlist.Count > 2) * { * System.Console.Write("After: "); * foreach (double d in zlist) * System.Console.Write(d.ToString() + " "); * System.Console.Write("\n"); * } */ // System.Console.Write("Sorted: "); // foreach (double d in zlist) // System.Console.Write(d.ToString() + " "); // System.Console.Write("\n"); if (zlist.Count > 0) { z_new = zlist[zlist.Count - 1]; } /* * if (zlist.Count > 1) * System.Console.WriteLine("chosen: " + z_new); */ // System.Console.ReadKey(); } // end triangle loop if (z_new != null) { drop_points.Add(new Geo.Point(p.x, p.y, (double)z_new)); } } // end point-list loop System.Console.WriteLine("checked: " + checks + " redundant: " + redundant); System.Console.WriteLine("relevant: " + (checks - redundant) + " (" + 100 * (double)(checks - redundant) / (double)checks + "%)"); // check to see that STL has not changed // display drop-points int i = 1; Geo.Point p0 = new Geo.Point(); foreach (Geo.Point p in drop_points) { if (i == 1) // first move { p0 = new Geo.Point(p.x, p.y, 10); GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Yellow; g.addGeom(l); p0 = p; } else // don't do anything for last move { GeoLine l = new GeoLine(p0, p); l.color = System.Drawing.Color.Magenta; g.addGeom(l); p0 = p; } i++; /* * GeoPoint pg = new GeoPoint(p); * pg.color = System.Drawing.Color.Aqua; * g.addGeom(pg); */ } // display zigzag and points /* * i = 1; * foreach (Geo.Point p in pointlist) * { * if (i == 1) * { * p0 = new Geo.Point(p.x, p.y, 10); * GeoLine l = new GeoLine(p0, p); * l.color = System.Drawing.Color.Yellow; * g.addGeom(l); * p0 = p; * } * else * { * GeoLine l = new GeoLine(p0, p); * l.color = System.Drawing.Color.Cyan; * g.addGeom(l); * p0 = p; * } * i++; * } */ // dummy test: /* * foreach (Geo.Tri t in s.tris) * { * GeoPoint p = new GeoPoint(t.p[0].x, t.p[0].y, t.p[0].z); * pointlist.Add(p); * } */ }