diff --git a/export.cpp b/export.cpp index 90d3890..fa53e8c 100644 --- a/export.cpp +++ b/export.cpp @@ -110,6 +110,28 @@ void SolveSpace::ExportDxfTo(char *filename) { havepoly: + int i, j; + // Project into the export plane; so when we're done, z doesn't matter, + // and x and y are what goes in the DXF. + for(i = 0; i < sp.l.n; i++) { + SContour *sc = &(sp.l.elem[i]); + for(j = 0; j < sc->l.n; j++) { + Vector *p = &(sc->l.elem[j].p); + *p = p->DotInToCsys(u, v, n); + } + } + + // If cutter radius compensation is requested, then perform it now. + if(fabs(SS.exportOffset) > LENGTH_EPS) { + SPolygon compd; + ZERO(&compd); + sp.normal = Vector::From(0, 0, -1); + sp.FixContourDirections(); + sp.OffsetInto(&compd, SS.exportOffset); + sp.Clear(); + sp = compd; + } + FILE *f = fopen(filename, "wb"); if(!f) { Error("Couldn't write to '%s'", filename); @@ -160,7 +182,6 @@ havepoly: " 2\r\n" "ENTITIES\r\n"); - int i, j; for(i = 0; i < sp.l.n; i++) { SContour *sc = &(sp.l.elem[i]); @@ -168,9 +189,6 @@ havepoly: Vector p0 = sc->l.elem[j-1].p, p1 = sc->l.elem[j].p; - Point2d e0 = p0.Project2d(u, v), - e1 = p1.Project2d(u, v); - double s = SS.exportScale; fprintf(f, @@ -191,8 +209,8 @@ havepoly: " 31\r\n" // zB "%.6f\r\n", 0, - e0.x/s, e0.y/s, 0.0, - e1.x/s, e1.y/s, 0.0); + p0.x/s, p0.y/s, 0.0, + p1.x/s, p1.y/s, 0.0); } } diff --git a/polygon.cpp b/polygon.cpp index 4e68e7f..c12e25c 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -383,3 +383,143 @@ void SPolygon::TriangulateInto(SMesh *m, STriMeta meta) { gluDeleteTess(gt); } +//----------------------------------------------------------------------------- +// Low-quality routines to cutter radius compensate a polygon. Assumes the +// polygon is in the xy plane, and the contours all go in the right direction +// with respect to normal (0, 0, -1). +//----------------------------------------------------------------------------- +void SPolygon::OffsetInto(SPolygon *dest, double r) { + int i; + dest->Clear(); + for(i = 0; i < l.n; i++) { + SContour *sc = &(l.elem[i]); + dest->AddEmptyContour(); + sc->OffsetInto(&(dest->l.elem[dest->l.n-1]), r); + } +} +//----------------------------------------------------------------------------- +// Calculate the intersection point of two lines. Returns true for success, +// false if they're parallel. +//----------------------------------------------------------------------------- +static bool IntersectionOfLines(double x0A, double y0A, double dxA, double dyA, + double x0B, double y0B, double dxB, double dyB, + double *xi, double *yi) +{ + double A[2][2]; + double b[2]; + + // The line is given to us in the form: + // (x(t), y(t)) = (x0, y0) + t*(dx, dy) + // so first rewrite it as + // (x - x0, y - y0) dot (dy, -dx) = 0 + // x*dy - x0*dy - y*dx + y0*dx = 0 + // x*dy - y*dx = (x0*dy - y0*dx) + + // So write the matrix, pre-pivoted. + if(fabs(dyA) > fabs(dyB)) { + A[0][0] = dyA; A[0][1] = -dxA; b[0] = x0A*dyA - y0A*dxA; + A[1][0] = dyB; A[1][1] = -dxB; b[1] = x0B*dyB - y0B*dxB; + } else { + A[1][0] = dyA; A[1][1] = -dxA; b[1] = x0A*dyA - y0A*dxA; + A[0][0] = dyB; A[0][1] = -dxB; b[0] = x0B*dyB - y0B*dxB; + } + + // Check the determinant; if it's zero then no solution. + if(fabs(A[0][0]*A[1][1] - A[0][1]*A[1][0]) < LENGTH_EPS) { + return false; + } + + // Solve + double v = A[1][0] / A[0][0]; + A[1][0] -= A[0][0]*v; + A[1][1] -= A[0][1]*v; + b[1] -= b[0]*v; + + // Back-substitute. + *yi = b[1] / A[1][1]; + *xi = (b[0] - A[0][1]*(*yi)) / A[0][0]; + + return true; +} +void SContour::OffsetInto(SContour *dest, double r) { + int i; + + for(i = 0; i < l.n; i++) { + Vector a, b, c; + Vector dp, dn; + double thetan, thetap; + + a = l.elem[WRAP(i-1, (l.n-1))].p; + b = l.elem[WRAP(i, (l.n-1))].p; + c = l.elem[WRAP(i+1, (l.n-1))].p; + + dp = a.Minus(b); + thetap = atan2(dp.y, dp.x); + + dn = b.Minus(c); + thetan = atan2(dn.y, dn.x); + + // A short line segment in a badly-generated polygon might look + // okay but screw up our sense of direction. + if(dp.Magnitude() < LENGTH_EPS || dn.Magnitude() < LENGTH_EPS) { + continue; + } + + if(thetan > thetap && (thetan - thetap) > PI) { + thetap += 2*PI; + } + if(thetan < thetap && (thetap - thetan) > PI) { + thetan += 2*PI; + } + + if(fabs(thetan - thetap) < (1*PI)/180) { + Vector p = { b.x - r*sin(thetap), b.y + r*cos(thetap) }; + dest->AddPoint(p); + } else if(thetan < thetap) { + // This is an inside corner. We have two edges, Ep and En. Move + // out from their intersection by radius, normal to En, and + // then draw a line parallel to En. Move out from their + // intersection by radius, normal to Ep, and then draw a second + // line parallel to Ep. The point that we want to generate is + // the intersection of these two lines--it removes as much + // material as we can without removing any that we shouldn't. + double px0, py0, pdx, pdy; + double nx0, ny0, ndx, ndy; + double x, y; + + px0 = b.x - r*sin(thetap); + py0 = b.y + r*cos(thetap); + pdx = cos(thetap); + pdy = sin(thetap); + + nx0 = b.x - r*sin(thetan); + ny0 = b.y + r*cos(thetan); + ndx = cos(thetan); + ndy = sin(thetan); + + IntersectionOfLines(px0, py0, pdx, pdy, + nx0, ny0, ndx, ndy, + &x, &y); + + dest->AddPoint(Vector::From(x, y, 0)); + } else { + if(fabs(thetap - thetan) < (6*PI)/180) { + Vector pp = { b.x - r*sin(thetap), + b.y + r*cos(thetap), 0 }; + dest->AddPoint(pp); + + Vector pn = { b.x - r*sin(thetan), + b.y + r*cos(thetan), 0 }; + dest->AddPoint(pn); + } else { + double theta; + for(theta = thetap; theta <= thetan; theta += (6*PI)/180) { + Vector p = { b.x - r*sin(theta), + b.y + r*cos(theta), 0 }; + dest->AddPoint(p); + } + } + } + } +} + diff --git a/polygon.h b/polygon.h index 34eafa9..dfba724 100644 --- a/polygon.h +++ b/polygon.h @@ -89,6 +89,7 @@ public: bool IsClockwiseProjdToNormal(Vector n); bool ContainsPointProjdToNormal(Vector n, Vector p); bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); + void OffsetInto(SContour *dest, double r); }; typedef struct { @@ -113,6 +114,7 @@ public: bool AllPointsInPlane(Vector *notCoplanarAt); bool IsEmpty(void); Vector AnyPoint(void); + void OffsetInto(SPolygon *dest, double r); }; class STriangle { diff --git a/solvespace.cpp b/solvespace.cpp index ffcf987..9a8bec8 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -49,6 +49,8 @@ void SolveSpace::Init(char *cmdLine) { edgeColor = CnfThawDWORD(RGB(0, 0, 0), "EdgeColor"); // Export scale factor exportScale = CnfThawFloat(1.0f, "ExportScale"); + // Export offset (cutter radius comp) + exportOffset = CnfThawFloat(0.0f, "ExportOffset"); // Draw back faces of triangles (when mesh is leaky/self-intersecting) drawBackFaces = CnfThawDWORD(1, "DrawBackFaces"); // Recent files menus @@ -109,6 +111,8 @@ void SolveSpace::Exit(void) { CnfFreezeDWORD(edgeColor, "EdgeColor"); // Export scale (a float, stored as a DWORD) CnfFreezeFloat(exportScale, "ExportScale"); + // Export offset (cutter radius comp) + CnfFreezeFloat(exportOffset, "ExportOffset"); // Draw back faces of triangles (when mesh is leaky/self-intersecting) CnfFreezeDWORD(drawBackFaces, "DrawBackFaces"); diff --git a/solvespace.h b/solvespace.h index c06aae2..8df57e2 100644 --- a/solvespace.h +++ b/solvespace.h @@ -370,6 +370,7 @@ public: double cameraTangent; DWORD edgeColor; float exportScale; + float exportOffset; int drawBackFaces; int CircleSides(double r); diff --git a/textscreens.cpp b/textscreens.cpp index ceb2eb1..bf01678 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -595,6 +595,13 @@ void TextWindow::ScreenChangeExportScale(int link, DWORD v) { ShowTextEditControl(59, 3, str); SS.TW.edit.meaning = EDIT_EXPORT_SCALE; } +void TextWindow::ScreenChangeExportOffset(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f", (double)SS.exportOffset); + + ShowTextEditControl(63, 3, str); + SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; +} void TextWindow::ScreenChangeBackFaces(int link, DWORD v) { SS.drawBackFaces = !SS.drawBackFaces; InvalidateGraphics(); @@ -652,6 +659,10 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", (double)SS.exportScale, &ScreenChangeExportScale, 0); + Printf(false, "%Ft cutter radius offset (always in mm) "); + Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E", + (double)SS.exportOffset, + &ScreenChangeExportOffset, 0); Printf(false, ""); Printf(false, "%Ft draw back faces: " @@ -871,6 +882,20 @@ void TextWindow::EditControlDone(char *s) { } break; } + case EDIT_EXPORT_OFFSET: { + Expr *e = Expr::From(s); + if(e) { + double ev = e->Eval(); + if(isnan(ev) || ev < 0) { + Error("Cutter radius offset must not be negative!"); + } else { + SS.exportOffset = (float)ev; + } + } else { + Error("Not a valid number or expression: '%s'", s); + } + break; + } case EDIT_HELIX_TURNS: case EDIT_HELIX_PITCH: case EDIT_HELIX_DRADIUS: { diff --git a/ui.h b/ui.h index d446db7..1cd56e2 100644 --- a/ui.h +++ b/ui.h @@ -75,6 +75,7 @@ public: static const int EDIT_CAMERA_TANGENT = 15; static const int EDIT_EDGE_COLOR = 16; static const int EDIT_EXPORT_SCALE = 17; + static const int EDIT_EXPORT_OFFSET = 18; // For the helical sweep static const int EDIT_HELIX_TURNS = 20; static const int EDIT_HELIX_PITCH = 21; @@ -158,6 +159,7 @@ public: static void ScreenChangeCameraTangent(int link, DWORD v); static void ScreenChangeEdgeColor(int link, DWORD v); static void ScreenChangeExportScale(int link, DWORD v); + static void ScreenChangeExportOffset(int link, DWORD v); void EditControlDone(char *s); };