Use a separate value of chord tolerance for exporting.

Before this commit, a single chord tolerance was used for both
displaying and exporting geometry. Moreover, this chord tolerance
was specified in screen pixels, and as such depended on zoom level.
This was inconvenient: exporting geometry with a required level of
precision required awkward manipulations of viewport. Moreover,
since some operations, e.g. mesh watertightness checking, were done
on triangle meshes which are generated differently depending on
the zoom level, these operations could report wildly different
and quite confusing results depending on zoom level.

The chord tolerance for display and export pursue completely distinct
goals: display chord tolerance should be set high enough to achieve
both fast regeneration and legible rendering, whereas export chord
tolerance should be set to match the dimension tolerance of
the fabrication process.

This commit introduces two distinct chord tolerances: a display
and an export one. Both chord tolerances are absolute and expressed
in millimeters; this is inappropriate for display purposes but
will be fixed in the next commits.

After exporting, the geometry is redrawn with the chord tolerance
configured for the export and an overlay message is displayed;
pressing Esc clears the message and returns the display back to
normal.
pull/4/head
EvilSpirit 2016-01-27 10:07:54 +06:00 committed by whitequark
parent 139dd80b48
commit 89eb208660
11 changed files with 123 additions and 34 deletions

View File

@ -26,13 +26,27 @@ void TextWindow::ScreenChangeColor(int link, uint32_t v) {
}
void TextWindow::ScreenChangeChordTolerance(int link, uint32_t v) {
SS.TW.ShowEditControl(3, ssprintf("%.2f", SS.chordTol));
SS.TW.ShowEditControl(3, ssprintf("%lg", SS.chordTol));
SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE;
SS.TW.edit.i = 0;
}
void TextWindow::ScreenChangeMaxSegments(int link, uint32_t v) {
SS.TW.ShowEditControl(3, ssprintf("%d", SS.maxSegments));
SS.TW.edit.meaning = EDIT_MAX_SEGMENTS;
SS.TW.edit.i = 0;
}
void TextWindow::ScreenChangeExportChordTolerance(int link, uint32_t v) {
SS.TW.ShowEditControl(3, ssprintf("%lg", SS.exportChordTol));
SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE;
SS.TW.edit.i = 1;
}
void TextWindow::ScreenChangeExportMaxSegments(int link, uint32_t v) {
SS.TW.ShowEditControl(3, ssprintf("%d", SS.exportMaxSegments));
SS.TW.edit.meaning = EDIT_MAX_SEGMENTS;
SS.TW.edit.i = 1;
}
void TextWindow::ScreenChangeCameraTangent(int link, uint32_t v) {
@ -182,6 +196,16 @@ void TextWindow::ShowConfiguration(void) {
SS.maxSegments,
&ScreenChangeMaxSegments);
Printf(false, "");
Printf(false, "%Ft export chord tolerance (in mm)%E");
Printf(false, "%Ba %@ %Fl%Ll%f%D[change]%E",
SS.exportChordTol,
&ScreenChangeExportChordTolerance, 0);
Printf(false, "%Ft export max piecewise linear segments%E");
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
SS.exportMaxSegments,
&ScreenChangeExportMaxSegments);
Printf(false, "");
Printf(false, "%Ft perspective factor (0 for parallel)%E");
Printf(false, "%Ba %# %Fl%Ll%f%D[change]%E",
@ -312,13 +336,21 @@ bool TextWindow::EditControlDoneForConfiguration(const char *s) {
break;
}
case EDIT_CHORD_TOLERANCE: {
SS.chordTol = min(10.0, max(0.1, atof(s)));
SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
if(edit.i == 0) {
SS.chordTol = max(0.0, atof(s));
SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
} else {
SS.exportChordTol = max(0.0, atof(s));
}
break;
}
case EDIT_MAX_SEGMENTS: {
SS.maxSegments = min(1000, max(7, atoi(s)));
SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
if(edit.i == 0) {
SS.maxSegments = min(1000, max(7, atoi(s)));
SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
} else {
SS.exportMaxSegments = min(1000, max(7, atoi(s)));
}
break;
}
case EDIT_CAMERA_TANGENT: {

View File

@ -796,28 +796,39 @@ nogrid:;
// A note to indicate the origin in the just-exported file.
if(SS.justExportedInfo.draw) {
Vector p, u, v;
if(SS.justExportedInfo.showOrigin) {
p = SS.justExportedInfo.pt,
u = SS.justExportedInfo.u,
v = SS.justExportedInfo.v;
} else {
p = SS.GW.offset.ScaledBy(-1);
u = SS.GW.projRight;
v = SS.GW.projUp;
}
ssglColorRGB(Style::Color(Style::DATUM));
Vector p = SS.justExportedInfo.pt,
u = SS.justExportedInfo.u,
v = SS.justExportedInfo.v;
ssglLineWidth(1.5);
glBegin(GL_LINES);
ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(u.WithMagnitude(30/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(30/scale)));
glEnd();
ssglWriteText("(x, y) = (0, 0) for file just exported",
ssglWriteText("previewing exported geometry; press Esc to return",
DEFAULT_TEXT_HEIGHT,
p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)),
u, v, NULL, NULL);
ssglWriteText("press Esc to clear this message",
DEFAULT_TEXT_HEIGHT,
p.Plus(u.ScaledBy(40/scale)).Plus(
v.ScaledBy(-(DEFAULT_TEXT_HEIGHT)/scale)),
u, v, NULL, NULL);
if(SS.justExportedInfo.showOrigin) {
ssglLineWidth(1.5);
glBegin(GL_LINES);
ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(u.WithMagnitude(30/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(30/scale)));
glEnd();
ssglWriteText("(x, y) = (0, 0) for file just exported",
DEFAULT_TEXT_HEIGHT,
p.Plus(u.ScaledBy(40/scale)).Plus(
v.ScaledBy(-(DEFAULT_TEXT_HEIGHT)/scale)),
u, v, NULL, NULL);
}
}
// And finally the toolbar.

View File

@ -112,6 +112,9 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir
SEdgeList edges = {};
SBezierList beziers = {};
SS.exportMode = true;
GenerateAll(GENERATE_ALL);
SMesh *sm = NULL;
if(SS.GW.showShaded) {
Group *g = SK.GetGroup(SS.GW.activeGroup);
@ -178,12 +181,16 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir
// These file formats don't have a canvas size, so they just
// get exported in the raw coordinate system. So indicate what
// that was on-screen.
SS.justExportedInfo.draw = true;
SS.justExportedInfo.showOrigin = true;
SS.justExportedInfo.pt = origin;
SS.justExportedInfo.u = u;
SS.justExportedInfo.v = v;
InvalidateGraphics();
} else {
SS.justExportedInfo.showOrigin = false;
}
SS.justExportedInfo.draw = true;
InvalidateGraphics();
}
edges.Clear();
@ -365,7 +372,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
SEdge notClosedAt;
sbl->l.ClearTags();
sblss.FindOuterFacesFrom(sbl, &spxyz, &srf,
SS.ChordTolMm()*s,
SS.ExportChordTolMm(),
&allClosed, &notClosedAt,
NULL, NULL,
&leftovers);
@ -509,7 +516,7 @@ void VectorFileWriter::Output(SBezierLoopSetSet *sblss, SMesh *sm) {
void VectorFileWriter::BezierAsPwl(SBezier *sb) {
List<Vector> lv = {};
sb->MakePwlInto(&lv, SS.ChordTolMm() / SS.exportScale);
sb->MakePwlInto(&lv, SS.ExportChordTolMm());
int i;
for(i = 1; i < lv.n; i++) {
SBezier sb = SBezier::From(lv.elem[i-1], lv.elem[i]);
@ -528,7 +535,7 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) {
sb->Finish().Minus(t1.ScaledBy(1.0/3)),
sb->Finish());
double tol = SS.ChordTolMm() / SS.exportScale;
double tol = SS.ExportChordTolMm();
// Arbitrary choice, but make it a little finer than pwl tolerance since
// it should be easier to achieve that with the smooth curves.
tol /= 2;
@ -559,6 +566,12 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) {
// Export a triangle mesh, in the requested format.
//-----------------------------------------------------------------------------
void SolveSpaceUI::ExportMeshTo(const std::string &filename) {
SS.exportMode = true;
GenerateAll(GENERATE_ALL);
Group *g = SK.GetGroup(SS.GW.activeGroup);
g->GenerateDisplayItems();
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
if(m->IsEmpty()) {
Error("Active group mesh is empty; nothing to export.");
@ -585,6 +598,10 @@ void SolveSpaceUI::ExportMeshTo(const std::string &filename) {
}
fclose(f);
SS.justExportedInfo.showOrigin = false;
SS.justExportedInfo.draw = true;
InvalidateGraphics();
}
//-----------------------------------------------------------------------------

View File

@ -237,7 +237,7 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
// tolerance are required, because they are used to calculate the
// contour directions and determine inner vs. outer contours.
sblss.FindOuterFacesFrom(sbl, &spxyz, ss,
SS.ChordTolMm() / SS.exportScale,
SS.ExportChordTolMm(),
&allClosed, &notClosedAt,
NULL, NULL,
NULL);

View File

@ -732,6 +732,10 @@ void GraphicsWindow::MenuEdit(int id) {
for(p = SK.param.First(); p; p = SK.param.NextAfter(p)) {
p->free = false;
}
if(SS.exportMode) {
SS.exportMode = false;
SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
}
break;
case MNU_SELECT_ALL: {

View File

@ -36,10 +36,16 @@ void SolveSpaceUI::Init() {
lightDir[1].x = CnfThawFloat( 1.0f, "LightDir_1_Right" );
lightDir[1].y = CnfThawFloat( 0.0f, "LightDir_1_Up" );
lightDir[1].z = CnfThawFloat( 0.0f, "LightDir_1_Forward" );
exportMode = false;
// Chord tolerance
chordTol = CnfThawFloat(2.0f, "ChordTolerance");
// Max pwl segments to generate
maxSegments = CnfThawInt(10, "MaxSegments");
// Chord tolerance
exportChordTol = CnfThawFloat(0.1f, "ExportChordTolerance");
// Max pwl segments to generate
exportMaxSegments = CnfThawInt(64, "ExportMaxSegments");
// View units
viewUnits = (Unit)CnfThawInt((uint32_t)UNIT_MM, "ViewUnits");
// Number of digits after the decimal point
@ -157,6 +163,10 @@ void SolveSpaceUI::Exit(void) {
CnfFreezeFloat((float)chordTol, "ChordTolerance");
// Max pwl segments to generate
CnfFreezeInt((uint32_t)maxSegments, "MaxSegments");
// Export Chord tolerance
CnfFreezeFloat((float)exportChordTol, "ExportChordTolerance");
// Export Max pwl segments to generate
CnfFreezeInt((uint32_t)exportMaxSegments, "ExportMaxSegments");
// View units
CnfFreezeInt((uint32_t)viewUnits, "ViewUnits");
// Number of digits after the decimal point
@ -259,7 +269,15 @@ double SolveSpaceUI::StringToMm(const std::string &str) {
return std::stod(str) * MmPerUnit();
}
double SolveSpaceUI::ChordTolMm(void) {
return SS.chordTol / SS.GW.scale;
if(exportMode) return ExportChordTolMm();
return chordTol / GW.scale;
}
double SolveSpaceUI::ExportChordTolMm(void) {
return exportChordTol / exportScale;
}
int SolveSpaceUI::GetMaxSegments(void) {
if(exportMode) return exportMaxSegments;
return maxSegments;
}
int SolveSpaceUI::UnitDigitsAfterDecimal(void) {
return (viewUnits == UNIT_INCHES) ? afterDecimalInch : afterDecimalMm;

View File

@ -752,6 +752,8 @@ public:
double ambientIntensity;
double chordTol;
int maxSegments;
double exportChordTol;
int exportMaxSegments;
double cameraTangent;
float gridSpacing;
float exportScale;
@ -764,6 +766,7 @@ public:
bool exportShadedTriangles;
bool exportPwlCurves;
bool exportCanvasSizeAuto;
bool exportMode;
struct {
float left;
float right;
@ -800,6 +803,8 @@ public:
int UnitDigitsAfterDecimal(void);
void SetUnitDigitsAfterDecimal(int v);
double ChordTolMm(void);
double ExportChordTolMm(void);
int GetMaxSegments(void);
bool usePerspectiveProj;
double CameraTangent(void);
@ -892,7 +897,7 @@ public:
Vector origin;
} bgImage;
struct {
bool draw;
bool draw, showOrigin;
Vector pt, u, v;
} justExportedInfo;

View File

@ -281,7 +281,7 @@ void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, double chordT
Vector pm = PointAt((ta + tb) / 2.0);
double d = pm.DistanceToLine(pa, pb.Minus(pa));
double step = 1.0/SS.maxSegments;
double step = 1.0/SS.GetMaxSegments();
if((tb - ta) < step || d < chordTol) {
// A previous call has already added the beginning of our interval.
l->Add(&pb);
@ -311,7 +311,7 @@ void SBezier::MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double
pm3.DistanceToLine(pa, dir)
});
double step = 1.0/SS.maxSegments;
double step = 1.0/SS.GetMaxSegments();
if((tb - ta) < step || d < chordTol) {
// A previous call has already added the beginning of our interval.
l->Add(&pb);

View File

@ -376,7 +376,7 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
// Our chord tolerance is whatever the user specified
double maxtol = SS.ChordTolMm();
int maxsteps = max(300, SS.maxSegments*3);
int maxsteps = max(300, SS.GetMaxSegments()*3);
// The curve starts at our starting point.
SCurvePt padd = {};

View File

@ -409,7 +409,7 @@ void SSurface::MakeTriangulationGridInto(List<double> *l, double vs, double vf,
worst = max(worst, pm2.DistanceToLine(ps, pf.Minus(ps)));
}
double step = 1.0/SS.maxSegments;
double step = 1.0/SS.GetMaxSegments();
if((vf - vs) < step || worst < SS.ChordTolMm()) {
l->Add(&vf);
} else {

View File

@ -299,6 +299,8 @@ public:
static void ScreenChangeColor(int link, uint32_t v);
static void ScreenChangeChordTolerance(int link, uint32_t v);
static void ScreenChangeMaxSegments(int link, uint32_t v);
static void ScreenChangeExportChordTolerance(int link, uint32_t v);
static void ScreenChangeExportMaxSegments(int link, uint32_t v);
static void ScreenChangeCameraTangent(int link, uint32_t v);
static void ScreenChangeGridSpacing(int link, uint32_t v);
static void ScreenChangeDigitsAfterDecimal(int link, uint32_t v);