Add ability to set export canvas size (paper size for PDF, bbox

size for EPS, etc.). This can either be fixed, with a given width
and height and offset, or automatic, by the left right bottom top
margins.

And draw nicer dimensions for length, with arrows and more
extension lines. Add code to trim those lines against the
(rectangular, axis-aligned) box that contains the actual number,
and use that (instead of the elliptical interpolation, which was
only approximately right) for diameter dimensions too.

[git-p4: depot-paths = "//depot/solvespace/": change = 2027]
solver
Jonathan Westhues 2009-09-03 00:13:09 -08:00
parent f7f9000c68
commit e989c86a38
8 changed files with 307 additions and 33 deletions

View File

@ -102,6 +102,142 @@ void Constraint::DoProjectedPoint(Vector *r) {
*r = p;
}
//-----------------------------------------------------------------------------
// There is a rectangular box, aligned to our display axes (projRight, projUp)
// centered at ref. This is where a dimension label will be drawn. We want to
// draw a line from A to B. If that line would intersect the label box, then
// trim the line to leave a gap for it, and return zero. If not, then extend
// the line to almost meet the box, and return either positive or negative,
// depending whether that extension was from A or from B.
//-----------------------------------------------------------------------------
int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b) {
Vector gu = SS.GW.projUp.WithMagnitude(1),
gr = SS.GW.projRight.WithMagnitude(1);
double pixels = 1.0 / SS.GW.scale;
char *s = Label();
double swidth = glxStrWidth(s) + 4*pixels,
sheight = glxStrHeight() + 8*pixels;
struct {
Vector n;
double d;
} planes[4];
// reference pos is the center of box occupied by text; build a rectangle
// around that, aligned to axes gr and gu, from four planes will all four
// normals pointing inward
planes[0].n = gu.ScaledBy(-1); planes[0].d = -(gu.Dot(ref) + sheight/2);
planes[1].n = gu; planes[1].d = gu.Dot(ref) - sheight/2;
planes[2].n = gr; planes[2].d = gr.Dot(ref) - swidth/2;
planes[3].n = gr.ScaledBy(-1); planes[3].d = -(gr.Dot(ref) + swidth/2);
double tmin = VERY_POSITIVE, tmax = VERY_NEGATIVE;
Vector dl = b.Minus(a);
for(int i = 0; i < 4; i++) {
bool parallel;
Vector p = Vector::AtIntersectionOfPlaneAndLine(
planes[i].n, planes[i].d,
a, b, &parallel);
if(parallel) continue;
int j;
for(j = 0; j < 4; j++) {
double d = (planes[j].n).Dot(p) - planes[j].d;
if(d < -LENGTH_EPS) break;
}
if(j < 4) continue;
double t = (p.Minus(a)).DivPivoting(dl);
tmin = min(t, tmin);
tmax = max(t, tmax);
}
int within = 0;
if(tmin > -0.01 && tmin < 1.01 && tmax > -0.01 && tmax < 1.01) {
// Both in range; so there's pieces of the line on both sides of the
// label box.
LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin)));
LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b);
} else if(tmin > -0.01 && tmin < 1.01) {
// Only one intersection in range; so the box is right on top of the
// endpoint
LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin)));
} else if(tmax > -0.01 && tmax < 1.01) {
// Likewise.
LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b);
} else {
// The line does not intersect the label; so the line should get
// extended to just barely meet the label.
if(tmin < 0.01 && tmax < 0.01) {
LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b);
within = 1;
} else if(tmin > 0.99 && tmax > 0.99) {
LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin)));
within = -1;
} else {
// This will happen if the entire line lies within the box.
LineDrawOrGetDistance(a, b);
}
}
// 0 means the label lies within the line, negative means it's outside
// and closer to b, positive means outside and closer to a.
return within;
}
//-----------------------------------------------------------------------------
// Draw a line with arrows on both ends, and possibly a gap in the middle for
// the dimension. We will use these for most length dimensions. The length
// being dimensioned is from A to B; but those points get extended perpendicular
// to the line AB, until the line between the extensions crosses ref (the
// center of the label).
//-----------------------------------------------------------------------------
void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b,
bool onlyOneExt)
{
Vector gn = (SS.GW.projRight.Cross(SS.GW.projUp)).WithMagnitude(1);
double pixels = 1.0 / SS.GW.scale;
Vector ab = a.Minus(b);
Vector ar = a.Minus(ref);
// Normal to a plane containing the line and the label origin.
Vector n = ab.Cross(ar);
// Within that plane, and normal to the line AB; so that's our extension
// line.
Vector out = ab.Cross(n).WithMagnitude(1);
out = out.ScaledBy(-out.Dot(ar));
Vector ae = a.Plus(out), be = b.Plus(out);
// Extension lines extend 10 pixels beyond where the arrows get
// drawn (which is at the same offset perpendicular from AB as the
// label).
LineDrawOrGetDistance(a, ae.Plus(out.WithMagnitude(10*pixels)));
if(!onlyOneExt) {
LineDrawOrGetDistance(b, be.Plus(out.WithMagnitude(10*pixels)));
}
int within = DoLineTrimmedAgainstBox(ref, ae, be);
// Arrow heads are 13 pixels long, with an 18 degree half-angle.
double theta = 18*PI/180;
Vector arrow = (be.Minus(ae)).WithMagnitude(13*pixels);
if(within != 0) {
arrow = arrow.ScaledBy(-1);
Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels);
if(within < 0) LineDrawOrGetDistance(ae, ae.Minus(seg));
if(within > 0) LineDrawOrGetDistance(be, be.Plus(seg));
}
LineDrawOrGetDistance(ae, ae.Plus(arrow.RotatedAbout(gn, theta)));
LineDrawOrGetDistance(ae, ae.Plus(arrow.RotatedAbout(gn, -theta)));
arrow = arrow.ScaledBy(-1);
LineDrawOrGetDistance(be, be.Plus(arrow.RotatedAbout(gn, theta)));
LineDrawOrGetDistance(be, be.Plus(arrow.RotatedAbout(gn, -theta)));
}
void Constraint::DoEqualLenTicks(Vector a, Vector b, Vector gn) {
Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3));
Vector ab = a.Minus(b);
@ -181,6 +317,9 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db,
prev = p;
}
// The elliptical approximation isn't exactly right, but the correct
// calculation (against the bounding box of the text) would be rather
// complex and this looks pretty good.
double tl = atan2(rm.Dot(gu), rm.Dot(gr));
double adj = EllipticalInterpolation(
glxStrWidth(Label())/2, glxStrHeight()/2, tl);
@ -224,16 +363,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
Vector ab = ap.Minus(bp);
Vector ar = ap.Minus(ref);
// Normal to a plan containing the line and the label origin.
Vector n = ab.Cross(ar);
Vector out = ab.Cross(n).WithMagnitude(1);
out = out.ScaledBy(-out.Dot(ar));
LineDrawOrGetDistance(ap, ap.Plus(out));
LineDrawOrGetDistance(bp, bp.Plus(out));
DoLineWithArrows(ref, ap, bp, false);
DoLabel(ref, labelPos, gr, gu);
break;
}
@ -252,11 +382,14 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
}
double d = (p.Minus(pt)).Dot(n);
Vector closest = pt.Plus(n.WithMagnitude(d));
LineDrawOrGetDistance(pt, closest);
Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);
if(!pt.Equals(closest)) {
DoLineWithArrows(ref, pt, closest, true);
}
DoLabel(ref, labelPos, gr, gu);
break;
}
@ -266,6 +399,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
Entity *line = SK.GetEntity(entityA);
Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
Vector dl = lB.Minus(lA);
if(workplane.v != Entity::FREE_IN_3D.v) {
lA = lA.ProjectInto(workplane);
@ -274,12 +408,15 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
}
// Find the closest point on the line
Vector closest = pt.ClosestPointOnLine(lA, (lA.Minus(lB)));
Vector closest = pt.ClosestPointOnLine(lA, dl);
LineDrawOrGetDistance(pt, closest);
Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);
DoLabel(ref, labelPos, gr, gu);
if(!pt.Equals(closest)) {
DoLineWithArrows(ref, pt, closest, true);
}
if(workplane.v != Entity::FREE_IN_3D.v) {
// Draw the projection marker from the closest point on the
// projected line to the projected point on the real line.
@ -298,17 +435,17 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
case DIAMETER: {
Entity *circle = SK.GetEntity(entityA);
Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
Vector n = q.RotationN().WithMagnitude(1);
double r = circle->CircleGetRadiusNum();
Vector ref = center.Plus(disp.offset);
double theta = atan2(disp.offset.Dot(gu), disp.offset.Dot(gr));
double adj = EllipticalInterpolation(
glxStrWidth(Label())/2, glxStrHeight()/2, theta);
Vector ref = center.Plus(disp.offset);
// Force the label into the same plane as the circle.
ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));
Vector mark = ref.Minus(center);
mark = mark.WithMagnitude(mark.Magnitude()-r);
LineDrawOrGetDistance(ref.Minus(mark.WithMagnitude(adj)),
ref.Minus(mark));
DoLineTrimmedAgainstBox(ref, ref, ref.Minus(mark));
DoLabel(ref, labelPos, gr, gu);
break;

View File

@ -202,7 +202,7 @@ void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
ZERO(&compd);
sp.normal = Vector::From(0, 0, -1);
sp.FixContourDirections();
sp.OffsetInto(&compd, SS.exportOffset);
sp.OffsetInto(&compd, SS.exportOffset*s);
sp.Clear();
compd.MakeEdgesInto(sel);
@ -382,6 +382,22 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) {
}
}
// And now we compute the canvas size.
double s = 1.0 / SS.exportScale;
if(SS.exportCanvasSizeAuto) {
// It's based on the calculated bounding box; we grow it along each
// boundary by the specified amount.
ptMin.x -= s*SS.exportMargin.left;
ptMax.x += s*SS.exportMargin.right;
ptMin.y -= s*SS.exportMargin.bottom;
ptMax.y += s*SS.exportMargin.top;
} else {
ptMin.x = -(s*SS.exportCanvas.dx);
ptMin.y = -(s*SS.exportCanvas.dy);
ptMax.x = ptMin.x + (s*SS.exportCanvas.width);
ptMax.y = ptMin.y + (s*SS.exportCanvas.height);
}
StartFile();
if(sm && SS.exportShadedTriangles) {
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {

View File

@ -557,6 +557,8 @@ public:
char *Label(void);
void DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db,
Vector offset, Vector *ref);
void DoLineWithArrows(Vector ref, Vector a, Vector b, bool onlyOneExt);
int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b);
void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu);
void DoProjectedPoint(Vector *p);
void DoEqualLenTicks(Vector a, Vector b, Vector gn);

View File

@ -76,6 +76,18 @@ void SolveSpace::Init(char *cmdLine) {
exportShadedTriangles = CnfThawDWORD(1, "ExportShadedTriangles");
// Export pwl curves (instead of exact) always
exportPwlCurves = CnfThawDWORD(0, "ExportPwlCurves");
// Whether export canvas size is fixed or derived from bbox
exportCanvasSizeAuto = CnfThawDWORD(1, "ExportCanvasSizeAuto");
// Margins for automatic canvas size
exportMargin.left = CnfThawFloat(5.0f, "ExportMargin_Left");
exportMargin.right = CnfThawFloat(5.0f, "ExportMargin_Right");
exportMargin.bottom = CnfThawFloat(5.0f, "ExportMargin_Bottom");
exportMargin.top = CnfThawFloat(5.0f, "ExportMargin_Top");
// Dimensions for fixed canvas size
exportCanvas.width = CnfThawFloat(100.0f, "ExportCanvas_Width");
exportCanvas.height = CnfThawFloat(100.0f, "ExportCanvas_Height");
exportCanvas.dx = CnfThawFloat( 5.0f, "ExportCanvas_Dx");
exportCanvas.dy = CnfThawFloat( 5.0f, "ExportCanvas_Dy");
// Show toolbar in the graphics window
showToolbar = CnfThawDWORD(1, "ShowToolbar");
// Recent files menus
@ -144,6 +156,18 @@ void SolveSpace::Exit(void) {
CnfFreezeDWORD(exportShadedTriangles, "ExportShadedTriangles");
// Export pwl curves (instead of exact) always
CnfFreezeDWORD(exportPwlCurves, "ExportPwlCurves");
// Whether export canvas size is fixed or derived from bbox
CnfFreezeDWORD(exportCanvasSizeAuto, "ExportCanvasSizeAuto");
// Margins for automatic canvas size
CnfFreezeFloat(exportMargin.left, "ExportMargin_Left");
CnfFreezeFloat(exportMargin.right, "ExportMargin_Right");
CnfFreezeFloat(exportMargin.bottom, "ExportMargin_Bottom");
CnfFreezeFloat(exportMargin.top, "ExportMargin_Top");
// Dimensions for fixed canvas size
CnfFreezeFloat(exportCanvas.width, "ExportCanvas_Width");
CnfFreezeFloat(exportCanvas.height, "ExportCanvas_Height");
CnfFreezeFloat(exportCanvas.dx, "ExportCanvas_Dx");
CnfFreezeFloat(exportCanvas.dy, "ExportCanvas_Dy");
// Show toolbar in the graphics window
CnfFreezeDWORD(showToolbar, "ShowToolbar");

View File

@ -529,6 +529,19 @@ public:
int showToolbar;
int exportShadedTriangles;
int exportPwlCurves;
int exportCanvasSizeAuto;
struct {
float left;
float right;
float bottom;
float top;
} exportMargin;
struct {
float width;
float height;
float dx;
float dy;
} exportCanvas;
int CircleSides(double r);
typedef enum {

View File

@ -640,10 +640,7 @@ void TextWindow::ScreenChangeExportScale(int link, DWORD v) {
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);
ShowTextEditControl(63, 3, SS.MmToString(SS.exportOffset));
SS.TW.edit.meaning = EDIT_EXPORT_OFFSET;
}
void TextWindow::ScreenChangeBackFaces(int link, DWORD v) {
@ -658,6 +655,37 @@ void TextWindow::ScreenChangePwlCurves(int link, DWORD v) {
SS.exportPwlCurves = !SS.exportPwlCurves;
InvalidateGraphics();
}
void TextWindow::ScreenChangeCanvasSizeAuto(int link, DWORD v) {
SS.exportCanvasSizeAuto = !SS.exportCanvasSizeAuto;
InvalidateGraphics();
}
void TextWindow::ScreenChangeCanvasSize(int link, DWORD v) {
double d;
switch(v) {
case 0: d = SS.exportMargin.left; break;
case 1: d = SS.exportMargin.right; break;
case 2: d = SS.exportMargin.bottom; break;
case 3: d = SS.exportMargin.top; break;
case 10: d = SS.exportCanvas.width; break;
case 11: d = SS.exportCanvas.height; break;
case 12: d = SS.exportCanvas.dx; break;
case 13: d = SS.exportCanvas.dy; break;
default: return;
}
int row = 77, col;
if(v < 10) {
row += v*2;
col = 11;
} else {
row += (v - 10)*2;
col = 13;
}
ShowTextEditControl(row, col, SS.MmToString(d));
SS.TW.edit.meaning = EDIT_CANVAS_SIZE;
SS.TW.edit.i = v;
}
void TextWindow::ShowConfiguration(void) {
int i;
Printf(true, "%Ft material color-(r, g, b)");
@ -711,9 +739,9 @@ 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 (in export units) ");
Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E",
(double)SS.exportOffset,
Printf(false, "%Ft cutter radius offset (0=no offset) ");
Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportOffset),
&ScreenChangeExportOffset, 0);
Printf(false, "");
@ -726,8 +754,8 @@ void TextWindow::ShowConfiguration(void) {
(!SS.exportShadedTriangles ? "" : "no"),
(!SS.exportShadedTriangles ? "no" : ""));
if(fabs(SS.exportOffset) > LENGTH_EPS) {
Printf(false, "%Ft curves as piecewise linear:%E %Fsyes");
Printf(false, " (always pwl if cutter radius offset isn't 0)");
Printf(false, "%Ft curves as piecewise linear:%E %Fsyes%Ft "
"(since cutter radius is not zero)");
} else {
Printf(false, "%Ft curves as piecewise linear: "
"%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
@ -739,6 +767,37 @@ void TextWindow::ShowConfiguration(void) {
(!SS.exportPwlCurves ? "no" : ""));
}
Printf(false, "");
Printf(false, "%Ft export canvas size: "
"%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
&ScreenChangeCanvasSizeAuto,
(!SS.exportCanvasSizeAuto ? "" : "fixed"),
(!SS.exportCanvasSizeAuto ? "fixed" : ""),
&ScreenChangeCanvasSizeAuto,
(SS.exportCanvasSizeAuto ? "" : "auto"),
(SS.exportCanvasSizeAuto ? "auto" : ""));
if(SS.exportCanvasSizeAuto) {
Printf(false, "%Ft (by margins around exported geometry)");
Printf(false, "%Ba%Ft left: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.left), &ScreenChangeCanvasSize, 0);
Printf(false, "%Bd%Ft right: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.right), &ScreenChangeCanvasSize, 1);
Printf(false, "%Ba%Ft bottom: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.bottom), &ScreenChangeCanvasSize, 2);
Printf(false, "%Bd%Ft top: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.top), &ScreenChangeCanvasSize, 3);
} else {
Printf(false, "%Ft (by absolute dimensions and offsets)");
Printf(false, "%Ba%Ft width: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.width), &ScreenChangeCanvasSize, 10);
Printf(false, "%Bd%Ft height: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.height), &ScreenChangeCanvasSize, 11);
Printf(false, "%Ba%Ft offset x: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.dx), &ScreenChangeCanvasSize, 12);
Printf(false, "%Bd%Ft offset y: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.dy), &ScreenChangeCanvasSize, 13);
}
Printf(false, "");
Printf(false, "%Ft draw back faces: "
"%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
@ -965,7 +1024,7 @@ void TextWindow::EditControlDone(char *s) {
case EDIT_EXPORT_OFFSET: {
Expr *e = Expr::From(s);
if(e) {
double ev = e->Eval();
double ev = SS.ExprToMm(e);
if(isnan(ev) || ev < 0) {
Error("Cutter radius offset must not be negative!");
} else {
@ -1025,6 +1084,27 @@ void TextWindow::EditControlDone(char *s) {
case EDIT_STEP_DIM_STEPS:
shown.dimSteps = min(300, max(1, atoi(s)));
break;
case EDIT_CANVAS_SIZE: {
Expr *e = Expr::From(s);
if(!e) {
Error("Not a valid number or expression: '%s'", s);
break;
}
float d = (float)SS.ExprToMm(e);
switch(edit.i) {
case 0: SS.exportMargin.left = d; break;
case 1: SS.exportMargin.right = d; break;
case 2: SS.exportMargin.bottom = d; break;
case 3: SS.exportMargin.top = d; break;
case 10: SS.exportCanvas.width = d; break;
case 11: SS.exportCanvas.height = d; break;
case 12: SS.exportCanvas.dx = d; break;
case 13: SS.exportCanvas.dy = d; break;
}
break;
}
}
InvalidateGraphics();
SS.later.showTW = true;

3
ui.h
View File

@ -80,6 +80,7 @@ public:
static const int EDIT_EDGE_COLOR = 16;
static const int EDIT_EXPORT_SCALE = 17;
static const int EDIT_EXPORT_OFFSET = 18;
static const int EDIT_CANVAS_SIZE = 19;
// For the helical sweep
static const int EDIT_HELIX_TURNS = 20;
static const int EDIT_HELIX_PITCH = 21;
@ -147,6 +148,8 @@ public:
static void ScreenChangeBackFaces(int link, DWORD v);
static void ScreenChangePwlCurves(int link, DWORD v);
static void ScreenChangeCanvasSizeAuto(int link, DWORD v);
static void ScreenChangeCanvasSize(int link, DWORD v);
static void ScreenChangeShadedTriangles(int link, DWORD v);
static void ScreenStepDimSteps(int link, DWORD v);

View File

@ -1,11 +1,10 @@
grid
line styles (color, thickness)
margins in exported vector art
background color setting
better text
better drawing of dimensions
faster triangulation
interpolating splines
-----
copy and paste