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:
parent
32372aee7a
commit
962cb1af4a
30
export.cpp
30
export.cpp
|
@ -110,6 +110,28 @@ void SolveSpace::ExportDxfTo(char *filename) {
|
||||||
|
|
||||||
havepoly:
|
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");
|
FILE *f = fopen(filename, "wb");
|
||||||
if(!f) {
|
if(!f) {
|
||||||
Error("Couldn't write to '%s'", filename);
|
Error("Couldn't write to '%s'", filename);
|
||||||
|
@ -160,7 +182,6 @@ havepoly:
|
||||||
" 2\r\n"
|
" 2\r\n"
|
||||||
"ENTITIES\r\n");
|
"ENTITIES\r\n");
|
||||||
|
|
||||||
int i, j;
|
|
||||||
for(i = 0; i < sp.l.n; i++) {
|
for(i = 0; i < sp.l.n; i++) {
|
||||||
SContour *sc = &(sp.l.elem[i]);
|
SContour *sc = &(sp.l.elem[i]);
|
||||||
|
|
||||||
|
@ -168,9 +189,6 @@ havepoly:
|
||||||
Vector p0 = sc->l.elem[j-1].p,
|
Vector p0 = sc->l.elem[j-1].p,
|
||||||
p1 = sc->l.elem[j].p;
|
p1 = sc->l.elem[j].p;
|
||||||
|
|
||||||
Point2d e0 = p0.Project2d(u, v),
|
|
||||||
e1 = p1.Project2d(u, v);
|
|
||||||
|
|
||||||
double s = SS.exportScale;
|
double s = SS.exportScale;
|
||||||
|
|
||||||
fprintf(f,
|
fprintf(f,
|
||||||
|
@ -191,8 +209,8 @@ havepoly:
|
||||||
" 31\r\n" // zB
|
" 31\r\n" // zB
|
||||||
"%.6f\r\n",
|
"%.6f\r\n",
|
||||||
0,
|
0,
|
||||||
e0.x/s, e0.y/s, 0.0,
|
p0.x/s, p0.y/s, 0.0,
|
||||||
e1.x/s, e1.y/s, 0.0);
|
p1.x/s, p1.y/s, 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
140
polygon.cpp
140
polygon.cpp
|
@ -383,3 +383,143 @@ void SPolygon::TriangulateInto(SMesh *m, STriMeta meta) {
|
||||||
gluDeleteTess(gt);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ public:
|
||||||
bool IsClockwiseProjdToNormal(Vector n);
|
bool IsClockwiseProjdToNormal(Vector n);
|
||||||
bool ContainsPointProjdToNormal(Vector n, Vector p);
|
bool ContainsPointProjdToNormal(Vector n, Vector p);
|
||||||
bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt);
|
bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt);
|
||||||
|
void OffsetInto(SContour *dest, double r);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -113,6 +114,7 @@ public:
|
||||||
bool AllPointsInPlane(Vector *notCoplanarAt);
|
bool AllPointsInPlane(Vector *notCoplanarAt);
|
||||||
bool IsEmpty(void);
|
bool IsEmpty(void);
|
||||||
Vector AnyPoint(void);
|
Vector AnyPoint(void);
|
||||||
|
void OffsetInto(SPolygon *dest, double r);
|
||||||
};
|
};
|
||||||
|
|
||||||
class STriangle {
|
class STriangle {
|
||||||
|
|
|
@ -49,6 +49,8 @@ void SolveSpace::Init(char *cmdLine) {
|
||||||
edgeColor = CnfThawDWORD(RGB(0, 0, 0), "EdgeColor");
|
edgeColor = CnfThawDWORD(RGB(0, 0, 0), "EdgeColor");
|
||||||
// Export scale factor
|
// Export scale factor
|
||||||
exportScale = CnfThawFloat(1.0f, "ExportScale");
|
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)
|
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||||
drawBackFaces = CnfThawDWORD(1, "DrawBackFaces");
|
drawBackFaces = CnfThawDWORD(1, "DrawBackFaces");
|
||||||
// Recent files menus
|
// Recent files menus
|
||||||
|
@ -109,6 +111,8 @@ void SolveSpace::Exit(void) {
|
||||||
CnfFreezeDWORD(edgeColor, "EdgeColor");
|
CnfFreezeDWORD(edgeColor, "EdgeColor");
|
||||||
// Export scale (a float, stored as a DWORD)
|
// Export scale (a float, stored as a DWORD)
|
||||||
CnfFreezeFloat(exportScale, "ExportScale");
|
CnfFreezeFloat(exportScale, "ExportScale");
|
||||||
|
// Export offset (cutter radius comp)
|
||||||
|
CnfFreezeFloat(exportOffset, "ExportOffset");
|
||||||
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||||
CnfFreezeDWORD(drawBackFaces, "DrawBackFaces");
|
CnfFreezeDWORD(drawBackFaces, "DrawBackFaces");
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,7 @@ public:
|
||||||
double cameraTangent;
|
double cameraTangent;
|
||||||
DWORD edgeColor;
|
DWORD edgeColor;
|
||||||
float exportScale;
|
float exportScale;
|
||||||
|
float exportOffset;
|
||||||
int drawBackFaces;
|
int drawBackFaces;
|
||||||
|
|
||||||
int CircleSides(double r);
|
int CircleSides(double r);
|
||||||
|
|
|
@ -595,6 +595,13 @@ void TextWindow::ScreenChangeExportScale(int link, DWORD v) {
|
||||||
ShowTextEditControl(59, 3, str);
|
ShowTextEditControl(59, 3, str);
|
||||||
SS.TW.edit.meaning = EDIT_EXPORT_SCALE;
|
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) {
|
void TextWindow::ScreenChangeBackFaces(int link, DWORD v) {
|
||||||
SS.drawBackFaces = !SS.drawBackFaces;
|
SS.drawBackFaces = !SS.drawBackFaces;
|
||||||
InvalidateGraphics();
|
InvalidateGraphics();
|
||||||
|
@ -652,6 +659,10 @@ void TextWindow::ShowConfiguration(void) {
|
||||||
Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E",
|
Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E",
|
||||||
(double)SS.exportScale,
|
(double)SS.exportScale,
|
||||||
&ScreenChangeExportScale, 0);
|
&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, "");
|
||||||
Printf(false, "%Ft draw back faces: "
|
Printf(false, "%Ft draw back faces: "
|
||||||
|
@ -871,6 +882,20 @@ void TextWindow::EditControlDone(char *s) {
|
||||||
}
|
}
|
||||||
break;
|
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_TURNS:
|
||||||
case EDIT_HELIX_PITCH:
|
case EDIT_HELIX_PITCH:
|
||||||
case EDIT_HELIX_DRADIUS: {
|
case EDIT_HELIX_DRADIUS: {
|
||||||
|
|
2
ui.h
2
ui.h
|
@ -75,6 +75,7 @@ public:
|
||||||
static const int EDIT_CAMERA_TANGENT = 15;
|
static const int EDIT_CAMERA_TANGENT = 15;
|
||||||
static const int EDIT_EDGE_COLOR = 16;
|
static const int EDIT_EDGE_COLOR = 16;
|
||||||
static const int EDIT_EXPORT_SCALE = 17;
|
static const int EDIT_EXPORT_SCALE = 17;
|
||||||
|
static const int EDIT_EXPORT_OFFSET = 18;
|
||||||
// For the helical sweep
|
// For the helical sweep
|
||||||
static const int EDIT_HELIX_TURNS = 20;
|
static const int EDIT_HELIX_TURNS = 20;
|
||||||
static const int EDIT_HELIX_PITCH = 21;
|
static const int EDIT_HELIX_PITCH = 21;
|
||||||
|
@ -158,6 +159,7 @@ public:
|
||||||
static void ScreenChangeCameraTangent(int link, DWORD v);
|
static void ScreenChangeCameraTangent(int link, DWORD v);
|
||||||
static void ScreenChangeEdgeColor(int link, DWORD v);
|
static void ScreenChangeEdgeColor(int link, DWORD v);
|
||||||
static void ScreenChangeExportScale(int link, DWORD v);
|
static void ScreenChangeExportScale(int link, DWORD v);
|
||||||
|
static void ScreenChangeExportOffset(int link, DWORD v);
|
||||||
|
|
||||||
void EditControlDone(char *s);
|
void EditControlDone(char *s);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user