From db565438e3456aea5708db9afd43c638c59b64e0 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 29 Sep 2009 03:35:19 -0800 Subject: [PATCH] Add text angle for styles. Add ability to quickly change between perspective and parallel projections. Add a snap grid, for points and for text comments. Draw text comments in the plane of their workplane if they have one, otherwise always facing forward. And fix a few nasty bugs: the possibility of an extremely long animation onto a workplane, accidental use of the wrong style line width for constraints, misplaced text box in style screen for default styles, other little stuff. [git-p4: depot-paths = "//depot/solvespace/": change = 2037] --- draw.cpp | 76 +++++++++++++++++++++++++++++++- drawconstraint.cpp | 45 +++++++++++++++---- export.cpp | 2 +- file.cpp | 1 + graphicswin.cpp | 105 ++++++++++++++++++++++++++++++++++++++++++--- sketch.h | 1 + solvespace.cpp | 30 +++++-------- solvespace.h | 4 +- style.cpp | 57 +++++++++++++++++++----- textscreens.cpp | 27 +++++++++--- textwin.cpp | 6 ++- ui.h | 23 +++++++--- win32/w32main.cpp | 2 + wishlist.txt | 2 +- 14 files changed, 321 insertions(+), 60 deletions(-) diff --git a/draw.cpp b/draw.cpp index b87a53e8..5b5c22f6 100644 --- a/draw.cpp +++ b/draw.cpp @@ -1148,6 +1148,7 @@ void GraphicsWindow::GroupSelection(void) { Constraint *c = SK.GetConstraint(s->constraint); if(c->type == Constraint::COMMENT) { (gs.stylables)++; + (gs.comments)++; } } } @@ -1226,7 +1227,7 @@ void GraphicsWindow::Paint(int w, int h) { double mat[16]; // Last thing before display is to apply the perspective - double clp = SS.cameraTangent*scale; + double clp = SS.CameraTangent()*scale; MakeMatrix(mat, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, @@ -1323,6 +1324,79 @@ void GraphicsWindow::Paint(int w, int h) { glxUnlockColor(); + if(showSnapGrid && LockedInWorkplane()) { + hEntity he = ActiveWorkplane(); + EntityBase *wrkpl = SK.GetEntity(he), + *norm = wrkpl->Normal(); + Vector wu, wv, wn, wp; + wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + wu = norm->NormalU(); + wv = norm->NormalV(); + wn = norm->NormalN(); + + double g = SS.gridSpacing; + + double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, + vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; + int a; + for(a = 0; a < 4; a++) { + // Ideally, we would just do +/- half the width and height; but + // allow some extra slop for rounding. + Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), + vert = projUp. ScaledBy((0.6*height)/scale + 2*g); + if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); + if(a == 1 || a == 3) vert = vert. ScaledBy(-1); + Vector tp = horiz.Plus(vert).Minus(offset); + + // Project the point into our grid plane, normal to the screen + // (not to the grid plane). If the plane is on edge then this is + // impossible so don't try to draw the grid. + bool parallel; + Vector tpp = Vector::AtIntersectionOfPlaneAndLine( + wn, wn.Dot(wp), + tp, tp.Plus(n), + ¶llel); + if(parallel) goto nogrid; + + tpp = tpp.Minus(wp); + double uu = tpp.Dot(wu), + vv = tpp.Dot(wv); + + umin = min(uu, umin); + umax = max(uu, umax); + vmin = min(vv, vmin); + vmax = max(vv, vmax); + } + + int i, j, i0, i1, j0, j1; + + i0 = (int)(umin / g); + i1 = (int)(umax / g); + j0 = (int)(vmin / g); + j1 = (int)(vmax / g); + + if(i0 > i1 || i1 - i0 > 400) goto nogrid; + if(j0 > j1 || j1 - j0 > 400) goto nogrid; + + glLineWidth(1); + glxColorRGBa(Style::Color(Style::DATUM), 0.3); + glBegin(GL_LINES); + for(i = i0 + 1; i < i1; i++) { + glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g))); + glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g))); + } + for(j = j0 + 1; j < j1; j++) { + glxVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g))); + glxVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g))); + } + glEnd(); + + // Clear the depth buffer, so that the grid is at the very back of + // the Z order. + glClear(GL_DEPTH_BUFFER_BIT); +nogrid:; + } + // Draw the active group; this fills the polygons in a drawing group, and // draws the solid mesh. (SK.GetGroup(activeGroup))->Draw(); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index bda1af52..9381c7fc 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -31,8 +31,10 @@ void Constraint::LineDrawOrGetDistance(Vector a, Vector b) { if(dogd.sel) { dogd.sel->AddEdge(a, b, hs.v); } else { - if(hs.v && Style::Width(disp.style) >= 3.0) { - glxFatLine(a, b, Style::Width(disp.style) / SS.GW.scale); + // The only constraints with styles should be comments, so don't + // check otherwise, save looking up the styles constantly. + if(type == COMMENT && Style::Width(hs) >= 3.0) { + glxFatLine(a, b, Style::Width(hs) / SS.GW.scale); } else { glBegin(GL_LINE_STRIP); glxVertex3v(a); @@ -95,10 +97,18 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { sheight = glxStrHeight(th); // By default, the reference is from the center; but the style could - // specify otherwise if one is present. + // specify otherwise if one is present, and it could also specify a + // rotation. if(type == COMMENT && disp.style.v) { - Style *s = Style::Get(disp.style); - int o = s->textOrigin; + Style *st = Style::Get(disp.style); + // rotation first + double rads = st->textAngle*PI/180; + double c = cos(rads), s = sin(rads); + Vector pr = gr, pu = gu; + gr = pr.ScaledBy( c).Plus(pu.ScaledBy(s)); + gu = pr.ScaledBy(-s).Plus(pu.ScaledBy(c)); + // then origin + int o = st->textOrigin; if(o & Style::ORIGIN_LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2)); if(o & Style::ORIGIN_RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2)); if(o & Style::ORIGIN_BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2)); @@ -378,8 +388,15 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { // If the group is hidden, then the constraints are hidden and not // able to be selected. if(!(g->visible)) return; - // And likewise if the group is not the active group. - if(g->h.v != SS.GW.activeGroup.v) return; + // And likewise if the group is not the active group; except for comments + // with an assigned style. + if(g->h.v != SS.GW.activeGroup.v && !(type == COMMENT && disp.style.v)) { + return; + } + if(disp.style.v) { + Style *s = Style::Get(disp.style); + if(!s->visible) return; + } // Unit vectors that describe our current view of the scene. One pixel // long, not one actual unit. @@ -917,13 +934,23 @@ s: } break; - case COMMENT: + case COMMENT: { if(disp.style.v) { glLineWidth(Style::Width(disp.style)); glxColorRGB(Style::Color(disp.style)); } - DoLabel(disp.offset, labelPos, gr, gu); + Vector u, v; + if(workplane.v == Entity::FREE_IN_3D.v) { + u = gr; + v = gu; + } else { + EntityBase *norm = SK.GetEntity(workplane)->Normal(); + u = norm->NormalU(); + v = norm->NormalV(); + } + DoLabel(disp.offset, labelPos, u, v); break; + } default: oops(); } diff --git a/export.cpp b/export.cpp index 4958e311..998ff990 100644 --- a/export.cpp +++ b/export.cpp @@ -166,7 +166,7 @@ void SolveSpace::ExportViewTo(char *filename) { VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { ExportLinesAndMesh(&edges, &beziers, sm, - u, v, n, origin, SS.cameraTangent*SS.GW.scale, + u, v, n, origin, SS.CameraTangent()*SS.GW.scale, out); } edges.Clear(); diff --git a/file.cpp b/file.cpp index b7d89b94..701e0210 100644 --- a/file.cpp +++ b/file.cpp @@ -157,6 +157,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 's', "Style.widthAs", 'd', &(SS.sv.s.widthAs) }, { 's', "Style.textHeight", 'f', &(SS.sv.s.textHeight) }, { 's', "Style.textHeightAs", 'd', &(SS.sv.s.textHeightAs) }, + { 's', "Style.textAngle", 'f', &(SS.sv.s.textAngle) }, { 's', "Style.textOrigin", 'x', &(SS.sv.s.textOrigin) }, { 's', "Style.color", 'x', &(SS.sv.s.color) }, { 's', "Style.visible", 'b', &(SS.sv.s.visible) }, diff --git a/graphicswin.cpp b/graphicswin.cpp index 96cd2bbc..98cc001e 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -32,9 +32,9 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "&Redo\tCtrl+Y", MNU_REDO, 'Y'|C, mEdit }, { 1, "Re&generate All\tSpace", MNU_REGEN_ALL, ' ', mEdit }, { 1, NULL, 0, NULL }, +{ 1, "Snap Selection to &Grid\t.", MNU_SNAP_TO_GRID, '.', mEdit }, { 1, "Rotate Imported &90°\t9", MNU_ROTATE_90, '9', mEdit }, { 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, - { 1, NULL, 0, NULL }, { 1, "&Unselect All\tEsc", MNU_UNSELECT_ALL, 27, mEdit }, @@ -43,6 +43,9 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Zoom &Out\t-", MNU_ZOOM_OUT, '-', mView }, { 1, "Zoom To &Fit\tF", MNU_ZOOM_TO_FIT, 'F', mView }, { 1, NULL, 0, NULL }, +{ 1, "Show Snap &Grid\t>", MNU_SHOW_GRID, '.'|S, mView }, +{ 1, "Force &Parallel Projection\t`", MNU_PARALLEL_PROJ, '`', mView }, +{ 1, NULL, 0, NULL }, { 1, "Nearest &Ortho View\tF2", MNU_NEAREST_ORTHO, F(2), mView }, { 1, "Nearest &Isometric View\tF3", MNU_NEAREST_ISO, F(3), mView }, { 1, "&Center View At Point\tF4", MNU_CENTER_VIEW, F(4), mView }, @@ -154,6 +157,7 @@ void GraphicsWindow::Init(void) { showTextWindow = true; ShowTextWindow(showTextWindow); + showSnapGrid = false; context.active = false; // Do this last, so that all the menus get updated correctly. @@ -199,7 +203,7 @@ Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) { r.y = p.Dot(projUp); r.z = p.Dot(projUp.Cross(projRight)); - *w = 1 + r.z*SS.cameraTangent*scale; + *w = 1 + r.z*SS.CameraTangent()*scale; return r; } @@ -230,6 +234,10 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) { // Animate transition, unless it's a tiny move. SDWORD dt = (mp < 0.01 && mo < 10) ? (-20) : (SDWORD)(100 + 1000*mp + 0.4*mo); + // Don't ever animate for longer than 2000 ms; we can get absurdly + // long translations (as measured in pixels) if the user zooms out, moves, + // and then zooms in again. + if(dt > 2000) dt = 2000; SDWORD tn, t0 = GetMilliseconds(); double s = 0; Quaternion dq = quatf.Times(quat0.Inverse()); @@ -347,7 +355,7 @@ void GraphicsWindow::ZoomToFit(bool includingInvisibles) { // Adjust the scale so that no points are behind the camera if(wmin < 0.1) { - double k = SS.cameraTangent; + double k = SS.CameraTangent(); // w = 1+k*scale*z double zmin = (wmin - 1)/(k*scale); // 0.1 = 1 + k*scale*zmin @@ -370,6 +378,29 @@ void GraphicsWindow::MenuView(int id) { SS.GW.ZoomToFit(false); break; + case MNU_SHOW_GRID: + SS.GW.showSnapGrid = !SS.GW.showSnapGrid; + if(SS.GW.showSnapGrid && !SS.GW.LockedInWorkplane()) { + Message("No workplane is active, so the grid will not " + "appear."); + } + SS.GW.EnsureValidActives(); + InvalidateGraphics(); + break; + + case MNU_PARALLEL_PROJ: + SS.forceParallelProj = !SS.forceParallelProj; + if(SS.cameraTangent < 1e-6) { + Error("The perspective factor is set to zero, so the view will " + "always be a parallel projection.\r\n\r\n" + "For a perspective projection, modify the camera tangent " + "in the configuration screen. A value around 0.3 is " + "typical."); + } + SS.GW.EnsureValidActives(); + InvalidateGraphics(); + break; + case MNU_NEAREST_ORTHO: case MNU_NEAREST_ISO: { static const Vector ortho[3] = { @@ -448,7 +479,7 @@ void GraphicsWindow::MenuView(int id) { case MNU_SHOW_TOOLBAR: SS.showToolbar = !SS.showToolbar; SS.GW.EnsureValidActives(); - PaintGraphics(); + InvalidateGraphics(); break; case MNU_UNITS_MM: @@ -532,6 +563,8 @@ void GraphicsWindow::EnsureValidActives(void) { CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow); CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar); + CheckMenuById(MNU_PARALLEL_PROJ, SS.forceParallelProj); + CheckMenuById(MNU_SHOW_GRID, SS.GW.showSnapGrid); if(change) SS.later.showTW = true; } @@ -583,6 +616,24 @@ void GraphicsWindow::DeleteTaggedRequests(void) { SS.later.showTW = true; } +Vector GraphicsWindow::SnapToGrid(Vector p) { + if(!LockedInWorkplane()) return p; + + EntityBase *wrkpl = SK.GetEntity(ActiveWorkplane()), + *norm = wrkpl->Normal(); + Vector wo = SK.GetEntity(wrkpl->point[0])->PointGetNum(), + wu = norm->NormalU(), + wv = norm->NormalV(), + wn = norm->NormalN(); + + Vector pp = (p.Minus(wo)).DotInToCsys(wu, wv, wn); + pp.x = floor((pp.x / SS.gridSpacing) + 0.5)*SS.gridSpacing; + pp.y = floor((pp.y / SS.gridSpacing) + 0.5)*SS.gridSpacing; + pp.z = 0; + + return pp.ScaleOutOfCsys(wu, wv, wn).Plus(wo); +} + void GraphicsWindow::MenuEdit(int id) { switch(id) { case MNU_UNSELECT_ALL: @@ -590,7 +641,10 @@ void GraphicsWindow::MenuEdit(int id) { // If there's nothing selected to de-select, and no operation // to cancel, then perhaps they want to return to the home // screen in the text window. - if(SS.GW.gs.n == 0 && SS.GW.pending.operation == 0) { + if(SS.GW.gs.n == 0 && + SS.GW.gs.constraints == 0 && + SS.GW.pending.operation == 0) + { if(!(TextEditControlIsVisible() || GraphicsEditControlIsVisible())) { @@ -667,6 +721,46 @@ void GraphicsWindow::MenuEdit(int id) { break; } + case MNU_SNAP_TO_GRID: { + if(!SS.GW.LockedInWorkplane()) { + Error("No workplane is active. Select a workplane to define " + "the plane for the snap grid."); + break; + } + SS.GW.GroupSelection(); + if(SS.GW.gs.n != SS.GW.gs.points || + SS.GW.gs.constraints != SS.GW.gs.comments || + (SS.GW.gs.n == 0 && SS.GW.gs.constraints == 0)) + { + Error("Can't snap these items to grid; select only points or " + "text comments. To snap a line, select its endpoints."); + break; + } + SS.UndoRemember(); + int i; + for(i = 0; i < SS.GW.gs.points; i++) { + hEntity hp = SS.GW.gs.point[i]; + Entity *ep = SK.GetEntity(hp); + Vector p = ep->PointGetNum(); + + ep->PointForceTo(SS.GW.SnapToGrid(p)); + + // Regenerate, with this point marked as dragged so that it + // gets placed as close as possible to our snap + SS.GW.pending.point = hp; + SS.MarkGroupDirty(ep->group); + SS.GenerateAll(); + SS.GW.pending.point = Entity::NO_ENTITY; + } + for(i = 0; i < SS.GW.gs.constraints; i++) { + Constraint *c = SK.GetConstraint(SS.GW.gs.constraint[i]); + c->disp.offset = SS.GW.SnapToGrid(c->disp.offset); + } + SS.GW.ClearSelection(); + InvalidateGraphics(); + break; + } + case MNU_UNDO: SS.UndoUndo(); break; @@ -715,6 +809,7 @@ void GraphicsWindow::MenuRequest(int id) { SS.GW.SetWorkplaneFreeIn3d(); SS.GW.EnsureValidActives(); SS.later.showTW = true; + InvalidateGraphics(); break; case MNU_ARC: { diff --git a/sketch.h b/sketch.h index 18f024ca..4ac9e6a9 100644 --- a/sketch.h +++ b/sketch.h @@ -635,6 +635,7 @@ public: static const int ORIGIN_BOT = 0x04; static const int ORIGIN_TOP = 0x08; int textOrigin; + double textAngle; DWORD color; bool visible; bool exportable; diff --git a/solvespace.cpp b/solvespace.cpp index 98e21a52..6ebd9c3d 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -64,6 +64,8 @@ void SolveSpace::Init(char *cmdLine) { viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits"); // Camera tangent (determines perspective) cameraTangent = CnfThawFloat(0.0f, "CameraTangent"); + // Grid spacing + gridSpacing = CnfThawFloat(5.0f, "GridSpacing"); // Export scale factor exportScale = CnfThawFloat(1.0f, "ExportScale"); // Export offset (cutter radius comp) @@ -150,6 +152,8 @@ void SolveSpace::Exit(void) { CnfFreezeDWORD((DWORD)viewUnits, "ViewUnits"); // Camera tangent (determines perspective) CnfFreezeFloat((float)cameraTangent, "CameraTangent"); + // Grid spacing + CnfFreezeFloat(gridSpacing, "GridSpacing"); // Export scale (a float, stored as a DWORD) CnfFreezeFloat(exportScale, "ExportScale"); // Export offset (cutter radius comp) @@ -191,25 +195,6 @@ void SolveSpace::DoLater(void) { ZERO(&later); } -int SolveSpace::CircleSides(double r) { - // Let the pwl segment be symmetric about the x axis; then the curve - // goes out to r, and if there's n segments, then the endpoints are - // at +/- (2pi/n)/2 = +/- pi/n. So the chord goes to x = r cos pi/n, - // from x = r, so it's - // tol = r - r cos pi/n - // tol = r(1 - cos pi/n) - // tol ~ r(1 - (1 - (pi/n)^2/2)) (Taylor expansion) - // tol = r((pi/n)^2/2) - // 2*tol/r = (pi/n)^2 - // sqrt(2*tol/r) = pi/n - // n = pi/sqrt(2*tol/r); - - double tol = chordTol/GW.scale; - int n = 3 + (int)(PI/sqrt(2*tol/r)); - - return max(7, min(n, maxSegments)); -} - char *SolveSpace::MmToString(double v) { static int WhichBuf; static char Bufs[8][128]; @@ -243,6 +228,13 @@ double SolveSpace::ChordTolMm(void) { return SS.chordTol / SS.GW.scale; } +double SolveSpace::CameraTangent(void) { + if(forceParallelProj) { + return 0; + } else { + return cameraTangent; + } +} void SolveSpace::AfterNewFile(void) { // Clear out the traced point, which is no longer valid diff --git a/solvespace.h b/solvespace.h index 3798c7bc..27df7bc0 100644 --- a/solvespace.h +++ b/solvespace.h @@ -547,6 +547,7 @@ public: double chordTol; int maxSegments; double cameraTangent; + float gridSpacing; float exportScale; float exportOffset; int fixExportColors; @@ -569,7 +570,6 @@ public: float dy; } exportCanvas; - int CircleSides(double r); typedef enum { UNIT_MM = 0, UNIT_INCHES, @@ -579,6 +579,8 @@ public: double ExprToMm(Expr *e); double StringToMm(char *s); double ChordTolMm(void); + bool forceParallelProj; + double CameraTangent(void); // The platform-dependent code calls this before entering the msg loop void Init(char *cmdLine); diff --git a/style.cpp b/style.cpp index 26ed1f27..6925451e 100644 --- a/style.cpp +++ b/style.cpp @@ -73,6 +73,8 @@ void Style::CreateDefaultStyle(hStyle h) { ns.widthAs = UNITS_AS_PIXELS; ns.textHeight = DEFAULT_TEXT_HEIGHT; ns.textHeightAs = UNITS_AS_PIXELS; + ns.textOrigin = 0; + ns.textAngle = 0; ns.visible = true; ns.exportable = true; ns.h = h; @@ -95,6 +97,8 @@ void Style::LoadFactoryDefaults(void) { s->widthAs = UNITS_AS_PIXELS; s->textHeight = DEFAULT_TEXT_HEIGHT; s->textHeightAs = UNITS_AS_PIXELS; + s->textOrigin = 0; + s->textAngle = 0; s->visible = true; s->exportable = true; s->name.strcpy(CnfPrefixToName(d->cnfPrefix)); @@ -375,8 +379,8 @@ void TextWindow::ScreenDeleteStyle(int link, DWORD v) { void TextWindow::ScreenChangeStyleWidthOrTextHeight(int link, DWORD v) { hStyle hs = { v }; Style *s = Style::Get(hs); - double val = (link == 'w') ? s->width : s->textHeight; - int units = (link == 'w') ? s->widthAs : s->textHeightAs; + double val = (link == 't') ? s->textHeight : s->width; + int units = (link == 't') ? s->textHeightAs : s->widthAs; char str[300]; if(units == Style::UNITS_AS_PIXELS) { @@ -384,10 +388,28 @@ void TextWindow::ScreenChangeStyleWidthOrTextHeight(int link, DWORD v) { } else { strcpy(str, SS.MmToString(val)); } - ShowTextEditControl((link == 'w') ? 21 : 26, 13, str); + int row = 0; + if(link == 'w') { + row = 16; // width for a default style + } else if(link == 'W') { + row = 21; // width for a custom style + } else if(link == 't') { + row = 27; // text height (for custom styles only) + } + ShowTextEditControl(row, 13, str); SS.TW.edit.style = hs; - SS.TW.edit.meaning = (link == 'w') ? EDIT_STYLE_WIDTH : - EDIT_STYLE_TEXT_HEIGHT; + SS.TW.edit.meaning = (link == 't') ? EDIT_STYLE_TEXT_HEIGHT : + EDIT_STYLE_WIDTH; +} + +void TextWindow::ScreenChangeStyleTextAngle(int link, DWORD v) { + hStyle hs = { v }; + Style *s = Style::Get(hs); + char str[300]; + sprintf(str, "%.2f", s->textAngle); + ShowTextEditControl(32, 13, str); + SS.TW.edit.style = hs; + SS.TW.edit.meaning = EDIT_STYLE_TEXT_ANGLE; } void TextWindow::ScreenChangeStyleColor(int link, DWORD v) { @@ -491,6 +513,12 @@ bool TextWindow::EditControlDoneForStyles(char *str) { } return true; } + case EDIT_STYLE_TEXT_ANGLE: + SS.UndoRemember(); + s = Style::Get(edit.style); + s->textAngle = WRAP_SYMMETRIC(atof(str), 360); + return true; + case EDIT_BACKGROUND_COLOR: case EDIT_STYLE_COLOR: { double r, g, b; @@ -565,13 +593,15 @@ void TextWindow::ShowStyleInfo(void) { // The line width, and its units if(s->widthAs == Style::UNITS_AS_PIXELS) { - Printf(true, "%FtLINE WIDTH %E%@ %D%f%Lw%Fl[change]%E", + Printf(true, "%FtLINE WIDTH %E%@ %D%f%Lp%Fl[change]%E", s->width, - s->h.v, &ScreenChangeStyleWidthOrTextHeight); + s->h.v, &ScreenChangeStyleWidthOrTextHeight, + (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } else { - Printf(true, "%FtLINE WIDTH %E%s %D%f%Lw%Fl[change]%E", + Printf(true, "%FtLINE WIDTH %E%s %D%f%Lp%Fl[change]%E", SS.MmToString(s->width), - s->h.v, &ScreenChangeStyleWidthOrTextHeight); + s->h.v, &ScreenChangeStyleWidthOrTextHeight, + (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } bool widthpx = (s->widthAs == Style::UNITS_AS_PIXELS); @@ -589,14 +619,15 @@ void TextWindow::ShowStyleInfo(void) { } // The text height, and its units + Printf(false, ""); char *chng = (s->h.v < Style::FIRST_CUSTOM) ? "" : "[change]"; if(s->textHeightAs == Style::UNITS_AS_PIXELS) { - Printf(true, "%FtTEXT HEIGHT %E%@ %D%f%Lt%Fl%s%E", + Printf(false, "%FtTEXT HEIGHT %E%@ %D%f%Lt%Fl%s%E", s->textHeight, s->h.v, &ScreenChangeStyleWidthOrTextHeight, chng); } else { - Printf(true, "%FtTEXT HEIGHT %E%s %D%f%Lt%Fl%s%E", + Printf(false, "%FtTEXT HEIGHT %E%s %D%f%Lt%Fl%s%E", SS.MmToString(s->textHeight), s->h.v, &ScreenChangeStyleWidthOrTextHeight, chng); @@ -619,6 +650,10 @@ void TextWindow::ShowStyleInfo(void) { if(s->h.v >= Style::FIRST_CUSTOM) { bool neither; + Printf(true, "%FtTEXT ANGLE %E%@ %D%f%Ll%Fl[change]%E", + s->textAngle, + s->h.v, &ScreenChangeStyleTextAngle); + neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT)); Printf(true, "%FtALIGN TEXT " "%Fh%D%f%LL%s%E%Fs%s%E / " diff --git a/textscreens.cpp b/textscreens.cpp index bd1898d8..6cb9f933 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -273,7 +273,7 @@ void TextWindow::ScreenChangeHelixParameter(int link, DWORD v) { sprintf(str, "%.3f", g->valA); SS.TW.edit.meaning = EDIT_HELIX_TURNS; r = 12; - } else if(link == 'p') { + } else if(link == 'i') { strcpy(str, SS.MmToString(g->valB)); SS.TW.edit.meaning = EDIT_HELIX_PITCH; r = 14; @@ -383,7 +383,7 @@ void TextWindow::ShowGroupInfo(void) { (!rh ? "" : "left-hand"), (!rh ? "left-hand" : "")); Printf(false, "%FtTHROUGH%E %@ turns %Fl%Lt%D%f[change]%E", g->valA, g->h.v, &ScreenChangeHelixParameter); - Printf(false, "%FtPITCH%E %s axially per turn %Fl%Lp%D%f[change]%E", + Printf(false, "%FtPITCH%E %s axially per turn %Fl%Li%D%f[change]%E", SS.MmToString(g->valB), g->h.v, &ScreenChangeHelixParameter); Printf(false, "%FtdRADIUS%E %s radially per turn %Fl%Lr%D%f[change]%E", SS.MmToString(g->valC), g->h.v, &ScreenChangeHelixParameter); @@ -625,15 +625,19 @@ void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) { ShowTextEditControl(47, 3, str); SS.TW.edit.meaning = EDIT_CAMERA_TANGENT; } +void TextWindow::ScreenChangeGridSpacing(int link, DWORD v) { + ShowTextEditControl(51, 3, SS.MmToString(SS.gridSpacing)); + SS.TW.edit.meaning = EDIT_GRID_SPACING; +} void TextWindow::ScreenChangeExportScale(int link, DWORD v) { char str[1024]; sprintf(str, "%.3f", (double)SS.exportScale); - ShowTextEditControl(53, 3, str); + ShowTextEditControl(57, 3, str); SS.TW.edit.meaning = EDIT_EXPORT_SCALE; } void TextWindow::ScreenChangeExportOffset(int link, DWORD v) { - ShowTextEditControl(57, 3, SS.MmToString(SS.exportOffset)); + ShowTextEditControl(61, 3, SS.MmToString(SS.exportOffset)); SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; } void TextWindow::ScreenChangeFixExportColors(int link, DWORD v) { @@ -670,7 +674,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, DWORD v) { default: return; } - int row = 71, col; + int row = 75, col; if(v < 10) { row += v*2; col = 11; @@ -723,6 +727,10 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", SS.cameraTangent*1000, &ScreenChangeCameraTangent, 0); + Printf(false, "%Ft snap grid spacing%E"); + Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E", + SS.MmToString(SS.gridSpacing), + &ScreenChangeGridSpacing, 0); Printf(false, ""); Printf(false, "%Ft export scale factor (1.0=mm, 25.4=inch)"); @@ -984,6 +992,15 @@ void TextWindow::EditControlDone(char *s) { } case EDIT_CAMERA_TANGENT: { SS.cameraTangent = (min(2, max(0, atof(s))))/1000.0; + if(SS.forceParallelProj) { + Message("The perspective factor will have no effect until you " + "disable View -> Force Parallel Projection."); + } + InvalidateGraphics(); + break; + } + case EDIT_GRID_SPACING: { + SS.gridSpacing = (float)min(1e4, max(1e-3, SS.StringToMm(s))); InvalidateGraphics(); break; } diff --git a/textwin.cpp b/textwin.cpp index 00fa1e51..c7209fd0 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -144,7 +144,11 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) { case 'L': if(fmt[1] == '\0') goto done; fmt++; - link = *fmt; + if(*fmt == 'p') { + link = va_arg(vl, int); + } else { + link = *fmt; + } break; case 'f': diff --git a/ui.h b/ui.h index bd324dfd..90f5f2b1 100644 --- a/ui.h +++ b/ui.h @@ -80,9 +80,10 @@ public: static const int EDIT_CHORD_TOLERANCE = 13; static const int EDIT_MAX_SEGMENTS = 14; static const int EDIT_CAMERA_TANGENT = 15; - static const int EDIT_EXPORT_SCALE = 16; - static const int EDIT_EXPORT_OFFSET = 17; - static const int EDIT_CANVAS_SIZE = 18; + static const int EDIT_GRID_SPACING = 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; @@ -95,9 +96,10 @@ public: // For the styles stuff static const int EDIT_STYLE_WIDTH = 50; static const int EDIT_STYLE_TEXT_HEIGHT = 51; - static const int EDIT_STYLE_COLOR = 52; - static const int EDIT_STYLE_NAME = 53; - static const int EDIT_BACKGROUND_COLOR = 54; + static const int EDIT_STYLE_TEXT_ANGLE = 52; + static const int EDIT_STYLE_COLOR = 53; + static const int EDIT_STYLE_NAME = 54; + static const int EDIT_BACKGROUND_COLOR = 55; struct { int meaning; int i; @@ -187,10 +189,12 @@ public: static void ScreenChangeChordTolerance(int link, DWORD v); static void ScreenChangeMaxSegments(int link, DWORD v); static void ScreenChangeCameraTangent(int link, DWORD v); + static void ScreenChangeGridSpacing(int link, DWORD v); static void ScreenChangeExportScale(int link, DWORD v); static void ScreenChangeExportOffset(int link, DWORD v); static void ScreenChangeStyleName(int link, DWORD v); static void ScreenChangeStyleWidthOrTextHeight(int link, DWORD v); + static void ScreenChangeStyleTextAngle(int link, DWORD v); static void ScreenChangeStyleColor(int link, DWORD v); static void ScreenChangeBackgroundColor(int link, DWORD v); @@ -220,6 +224,8 @@ public: MNU_ZOOM_IN, MNU_ZOOM_OUT, MNU_ZOOM_TO_FIT, + MNU_SHOW_GRID, + MNU_PARALLEL_PROJ, MNU_NEAREST_ORTHO, MNU_NEAREST_ISO, MNU_CENTER_VIEW, @@ -231,6 +237,7 @@ public: MNU_UNDO, MNU_REDO, MNU_DELETE, + MNU_SNAP_TO_GRID, MNU_ROTATE_90, MNU_UNSELECT_ALL, MNU_REGEN_ALL, @@ -373,6 +380,7 @@ public: // The constraint that is being edited with the on-screen textbox. hConstraint constraintBeingEdited; + Vector SnapToGrid(Vector p); bool ConstrainPointByHovered(hEntity pt); void DeleteTaggedRequests(void); hRequest AddRequest(int type, bool rememberForUndo); @@ -428,6 +436,7 @@ public: int vectors; int constraints; int stylables; + int comments; int n; } gs; void GroupSelection(void); @@ -469,6 +478,8 @@ public: bool showHdnLines; static void ToggleBool(int link, DWORD v); + bool showSnapGrid; + void UpdateDraggedNum(Vector *pos, double mx, double my); void UpdateDraggedPoint(hEntity hp, double mx, double my); diff --git a/win32/w32main.cpp b/win32/w32main.cpp index b0c9fe43..304ea089 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -539,9 +539,11 @@ static BOOL ProcessKeyDown(WPARAM wParam) case VK_OEM_MINUS: c = '-'; break; case VK_ESCAPE: c = 27; break; case VK_OEM_1: c = ';'; break; + case VK_OEM_3: c = '`'; break; case VK_OEM_4: c = '['; break; case VK_OEM_6: c = ']'; break; case VK_OEM_5: c = '\\'; break; + case VK_OEM_PERIOD: c = '.'; break; case VK_SPACE: c = ' '; break; case VK_DELETE: c = 127; break; case VK_TAB: c = '\t'; break; diff --git a/wishlist.txt b/wishlist.txt index 9a86d8d7..f71cd03b 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,5 +1,5 @@ -grid +split draw.cpp multi-drag -----