Add cutter radius compensation. That's a bolt on thing at the end;

just applies an offset to the DXF before exporting. Useful enough
to be worth the ugliness, though.

This is the stupid routines from SketchFlat, slightly reworked.

[git-p4: depot-paths = "//depot/solvespace/": change = 1866]
This commit is contained in:
Jonathan Westhues 2008-08-14 00:28:25 -08:00
parent 32372aee7a
commit 962cb1af4a
7 changed files with 198 additions and 6 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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 {

View File

@ -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");

View File

@ -370,6 +370,7 @@ public:
double cameraTangent;
DWORD edgeColor;
float exportScale;
float exportOffset;
int drawBackFaces;
int CircleSides(double r);

View File

@ -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: {

2
ui.h
View File

@ -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);
};