diff --git a/Makefile b/Makefile index 8bfc6e1..9502b34 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ W32OBJS = $(OBJDIR)\w32main.obj \ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\textwin.obj \ $(OBJDIR)\textscreens.obj \ + $(OBJDIR)\confscreen.obj \ $(OBJDIR)\graphicswin.obj \ $(OBJDIR)\modify.obj \ $(OBJDIR)\util.obj \ @@ -28,6 +29,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\expr.obj \ $(OBJDIR)\constraint.obj \ $(OBJDIR)\constrainteq.obj \ + $(OBJDIR)\mouse.obj \ $(OBJDIR)\draw.obj \ $(OBJDIR)\toolbar.obj \ $(OBJDIR)\drawconstraint.obj \ diff --git a/confscreen.cpp b/confscreen.cpp new file mode 100644 index 0000000..5fb4303 --- /dev/null +++ b/confscreen.cpp @@ -0,0 +1,359 @@ +//----------------------------------------------------------------------------- +// For the configuration screen, setup items that are not specific to the +// file being edited right now. +//----------------------------------------------------------------------------- +#include "solvespace.h" + +void TextWindow::ScreenChangeLightDirection(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f, %.2f, %.2f", CO(SS.lightDir[v])); + ShowTextEditControl(29+2*v, 8, str); + SS.TW.edit.meaning = EDIT_LIGHT_DIRECTION; + SS.TW.edit.i = v; +} + +void TextWindow::ScreenChangeLightIntensity(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f", SS.lightIntensity[v]); + ShowTextEditControl(29+2*v, 30, str); + SS.TW.edit.meaning = EDIT_LIGHT_INTENSITY; + SS.TW.edit.i = v; +} + +void TextWindow::ScreenChangeColor(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f, %.2f, %.2f", + REDf(SS.modelColor[v]), + GREENf(SS.modelColor[v]), + BLUEf(SS.modelColor[v])); + ShowTextEditControl(9+2*v, 12, str); + SS.TW.edit.meaning = EDIT_COLOR; + SS.TW.edit.i = v; +} + +void TextWindow::ScreenChangeChordTolerance(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f", SS.chordTol); + ShowTextEditControl(37, 3, str); + SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE; +} + +void TextWindow::ScreenChangeMaxSegments(int link, DWORD v) { + char str[1024]; + sprintf(str, "%d", SS.maxSegments); + ShowTextEditControl(41, 3, str); + SS.TW.edit.meaning = EDIT_MAX_SEGMENTS; +} + +void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.3f", 1000*SS.cameraTangent); + 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(57, 3, str); + SS.TW.edit.meaning = EDIT_EXPORT_SCALE; +} + +void TextWindow::ScreenChangeExportOffset(int link, DWORD v) { + ShowTextEditControl(61, 3, SS.MmToString(SS.exportOffset)); + SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; +} + +void TextWindow::ScreenChangeFixExportColors(int link, DWORD v) { + SS.fixExportColors = !SS.fixExportColors; +} + +void TextWindow::ScreenChangeBackFaces(int link, DWORD v) { + SS.drawBackFaces = !SS.drawBackFaces; + InvalidateGraphics(); +} + +void TextWindow::ScreenChangeShadedTriangles(int link, DWORD v) { + SS.exportShadedTriangles = !SS.exportShadedTriangles; + InvalidateGraphics(); +} + +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 = 75, 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)"); + + for(i = 0; i < SS.MODEL_COLORS; i++) { + Printf(false, "%Bp #%d: %Bp %Bp (%@, %@, %@) %f%D%Ll%Fl[change]%E", + (i & 1) ? 'd' : 'a', + i, 0x80000000 | SS.modelColor[i], + (i & 1) ? 'd' : 'a', + REDf(SS.modelColor[i]), + GREENf(SS.modelColor[i]), + BLUEf(SS.modelColor[i]), + &ScreenChangeColor, i); + } + + Printf(false, ""); + Printf(false, "%Ft light direction intensity"); + for(i = 0; i < 2; i++) { + Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E " + "%2 %Fl%D%f%Ll[c]%E", + (i & 1) ? 'd' : 'a', i, + CO(SS.lightDir[i]), i, &ScreenChangeLightDirection, + SS.lightIntensity[i], i, &ScreenChangeLightIntensity); + } + + Printf(false, ""); + Printf(false, "%Ft chord tolerance (in screen pixels)%E"); + Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles", + SS.chordTol, + &ScreenChangeChordTolerance, 0, + SK.GetGroup(SS.GW.activeGroup)->displayMesh.l.n); + Printf(false, "%Ft max piecewise linear segments%E"); + Printf(false, "%Ba %d %Fl%Ll%f[change]%E", + SS.maxSegments, + &ScreenChangeMaxSegments); + + Printf(false, ""); + Printf(false, "%Ft perspective factor (0 for parallel)%E"); + 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)"); + Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", + (double)SS.exportScale, + &ScreenChangeExportScale, 0); + 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, ""); + Printf(false, "%Ft export shaded 2d triangles: " + "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &ScreenChangeShadedTriangles, + (SS.exportShadedTriangles ? "" : "yes"), + (SS.exportShadedTriangles ? "yes" : ""), + &ScreenChangeShadedTriangles, + (!SS.exportShadedTriangles ? "" : "no"), + (!SS.exportShadedTriangles ? "no" : "")); + if(fabs(SS.exportOffset) > LENGTH_EPS) { + 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", + &ScreenChangePwlCurves, + (SS.exportPwlCurves ? "" : "yes"), + (SS.exportPwlCurves ? "yes" : ""), + &ScreenChangePwlCurves, + (!SS.exportPwlCurves ? "" : "no"), + (!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 fix white exported lines: " + "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &ScreenChangeFixExportColors, + ( SS.fixExportColors ? "" : "yes"), ( SS.fixExportColors ? "yes" : ""), + &ScreenChangeFixExportColors, + (!SS.fixExportColors ? "" : "no"), (!SS.fixExportColors ? "no" : "")); + + Printf(false, "%Ft draw triangle back faces: " + "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &ScreenChangeBackFaces, + (SS.drawBackFaces ? "" : "yes"), (SS.drawBackFaces ? "yes" : ""), + &ScreenChangeBackFaces, + (!SS.drawBackFaces ? "" : "no"), (!SS.drawBackFaces ? "no" : "")); + + Printf(false, ""); + Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR)); + Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER)); + Printf(false, " %Ft version %E%s", glGetString(GL_VERSION)); +} + +bool TextWindow::EditControlDoneForConfiguration(char *s) { + switch(edit.meaning) { + case EDIT_LIGHT_INTENSITY: + SS.lightIntensity[edit.i] = min(1, max(0, atof(s))); + InvalidateGraphics(); + break; + + case EDIT_LIGHT_DIRECTION: { + double x, y, z; + if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) { + SS.lightDir[edit.i] = Vector::From(x, y, z); + } else { + Error("Bad format: specify coordinates as x, y, z"); + } + InvalidateGraphics(); + break; + } + case EDIT_COLOR: { + double r, g, b; + if(sscanf(s, "%lf, %lf, %lf", &r, &g, &b)==3) { + SS.modelColor[edit.i] = RGB(r*255, g*255, b*255); + } else { + Error("Bad format: specify color as r, g, b"); + } + break; + } + case EDIT_CHORD_TOLERANCE: { + SS.chordTol = min(10, max(0.1, atof(s))); + SS.GenerateAll(0, INT_MAX); + break; + } + case EDIT_MAX_SEGMENTS: { + SS.maxSegments = min(1000, max(7, atoi(s))); + SS.GenerateAll(0, INT_MAX); + break; + } + 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; + } + case EDIT_EXPORT_SCALE: { + Expr *e = Expr::From(s); + if(e) { + double ev = e->Eval(); + if(fabs(ev) < 0.001 || isnan(ev)) { + Error("Export scale must not be zero!"); + } else { + SS.exportScale = (float)ev; + } + } else { + Error("Not a valid number or expression: '%s'", s); + } + break; + } + case EDIT_EXPORT_OFFSET: { + Expr *e = Expr::From(s); + if(e) { + double ev = SS.ExprToMm(e); + 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_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; + } + + default: return false; + } + return true; +} + diff --git a/draw.cpp b/draw.cpp index 5b5c22f..baeb702 100644 --- a/draw.cpp +++ b/draw.cpp @@ -1,972 +1,10 @@ +//----------------------------------------------------------------------------- +// The root function to paint our graphics window, after setting up all the +// views and such appropriately. Also contains all the stuff to manage the +// selection. +//----------------------------------------------------------------------------- #include "solvespace.h" -void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { - Entity *p = SK.GetEntity(hp); - Vector pos = p->PointGetNum(); - UpdateDraggedNum(&pos, mx, my); - p->PointForceTo(pos); -} - -void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { - *pos = pos->Plus(projRight.ScaledBy((mx - orig.mouse.x)/scale)); - *pos = pos->Plus(projUp.ScaledBy((my - orig.mouse.y)/scale)); - - orig.mouse.x = mx; - orig.mouse.y = my; - InvalidateGraphics(); -} - -void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, - bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) -{ - if(GraphicsEditControlIsVisible()) return; - if(context.active) return; - - if(rightDown) { - middleDown = true; - shiftDown = !shiftDown; - } - - if(SS.showToolbar) { - if(ToolbarMouseMoved((int)x, (int)y)) { - hover.Clear(); - return; - } - } - - Point2d mp = { x, y }; - - if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { - // Avoid accidentally panning (or rotating if shift is down) if the - // user wants a context menu. - return; - } - orig.startedMoving = true; - - // If the middle button is down, then mouse movement is used to pan and - // rotate our view. This wins over everything else. - if(middleDown) { - hover.Clear(); - - double dx = (x - orig.mouse.x) / scale; - double dy = (y - orig.mouse.y) / scale; - - // When the view is locked, permit only translation (pan). - if(!(shiftDown || ctrlDown)) { - double s = 0.3*(PI/180)*scale; // degrees per pixel - projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); - projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); - - NormalizeProjectionVectors(); - } else if(ctrlDown) { - double theta = atan2(orig.mouse.y, orig.mouse.x); - theta -= atan2(y, x); - - Vector normal = orig.projRight.Cross(orig.projUp); - projRight = orig.projRight.RotatedAbout(normal, theta); - projUp = orig.projUp.RotatedAbout(normal, theta); - - NormalizeProjectionVectors(); - } else { - offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; - offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; - offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; - } - - orig.projRight = projRight; - orig.projUp = projUp; - orig.offset = offset; - orig.mouse.x = x; - orig.mouse.y = y; - - InvalidateGraphics(); - return; - } - - if(pending.operation == 0) { - double dm = orig.mouse.DistanceTo(mp); - // If we're currently not doing anything, then see if we should - // start dragging something. - if(leftDown && dm > 3) { - if(hover.entity.v) { - Entity *e = SK.GetEntity(hover.entity); - if(e->IsPoint()) { - // Start dragging this point. - ClearSelection(); - pending.point = hover.entity; - pending.operation = DRAGGING_POINT; - } else if(e->type == Entity::CIRCLE) { - // Drag the radius. - ClearSelection(); - pending.circle = hover.entity; - pending.operation = DRAGGING_RADIUS; - } else if(e->IsNormal()) { - ClearSelection(); - pending.normal = hover.entity; - pending.operation = DRAGGING_NORMAL; - } - } else if(hover.constraint.v && - SK.GetConstraint(hover.constraint)->HasLabel()) - { - ClearSelection(); - pending.constraint = hover.constraint; - pending.operation = DRAGGING_CONSTRAINT; - } - if(pending.operation != 0) { - // We just started a drag, so remember for the undo before - // the drag changes anything. - SS.UndoRemember(); - } - } else { - // Otherwise, just hit test and give up; but don't hit test - // if the mouse is down, because then the user could hover - // a point, mouse down (thus selecting it), and drag, in an - // effort to drag the point, but instead hover a different - // entity before we move far enough to start the drag. - if(!leftDown) HitTestMakeSelection(mp); - } - return; - } - - // If the user has started an operation from the menu, but not - // completed it, then just do the selection. - if(pending.operation < FIRST_PENDING) { - HitTestMakeSelection(mp); - return; - } - - // We're currently dragging something; so do that. But if we haven't - // painted since the last time we solved, do nothing, because there's - // no sense solving a frame and not displaying it. - if(!havePainted) return; - switch(pending.operation) { - case DRAGGING_CONSTRAINT: { - Constraint *c = SK.constraint.FindById(pending.constraint); - UpdateDraggedNum(&(c->disp.offset), x, y); - break; - } - case DRAGGING_NEW_LINE_POINT: - HitTestMakeSelection(mp); - // and fall through - case DRAGGING_NEW_POINT: - case DRAGGING_POINT: { - Entity *p = SK.GetEntity(pending.point); - if((p->type == Entity::POINT_N_ROT_TRANS) && - (shiftDown || ctrlDown)) - { - // These points also come with a rotation, which the user can - // edit by pressing shift or control. - Quaternion q = p->PointGetQuaternion(); - Vector p3 = p->PointGetNum(); - Point2d p2 = ProjectPoint(p3); - - Vector u = q.RotationU(), v = q.RotationV(); - if(ctrlDown) { - double d = mp.DistanceTo(p2); - if(d < 25) { - // Don't start dragging the position about the normal - // until we're a little ways out, to get a reasonable - // reference pos - orig.mouse = mp; - break; - } - double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); - theta -= atan2(y-p2.y, x-p2.x); - - Vector gn = projRight.Cross(projUp); - u = u.RotatedAbout(gn, -theta); - v = v.RotatedAbout(gn, -theta); - } else { - double dx = -(x - orig.mouse.x); - double dy = -(y - orig.mouse.y); - double s = 0.3*(PI/180); // degrees per pixel - u = u.RotatedAbout(projUp, -s*dx); - u = u.RotatedAbout(projRight, s*dy); - v = v.RotatedAbout(projUp, -s*dx); - v = v.RotatedAbout(projRight, s*dy); - } - q = Quaternion::From(u, v); - p->PointForceQuaternionTo(q); - // Let's rotate about the selected point; so fix up the - // translation so that that point didn't move. - p->PointForceTo(p3); - orig.mouse = mp; - } else { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); - } - SS.MarkGroupDirtyByEntity(pending.point); - break; - } - case DRAGGING_NEW_CUBIC_POINT: { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); - - hRequest hr = pending.point.request(); - Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(); - Vector p3 = SK.GetEntity(hr.entity(4))->PointGetNum(); - Vector p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)); - SK.GetEntity(hr.entity(2))->PointForceTo(p1); - Vector p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); - SK.GetEntity(hr.entity(3))->PointForceTo(p2); - - SS.MarkGroupDirtyByEntity(pending.point); - break; - } - case DRAGGING_NEW_ARC_POINT: { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); - - hRequest hr = pending.point.request(); - Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum(); - Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum(); - Vector center = (ona.Plus(onb)).ScaledBy(0.5); - - SK.GetEntity(hr.entity(1))->PointForceTo(center); - - SS.MarkGroupDirtyByEntity(pending.point); - break; - } - case DRAGGING_NEW_RADIUS: - case DRAGGING_RADIUS: { - Entity *circle = SK.GetEntity(pending.circle); - Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); - Point2d c2 = ProjectPoint(center); - double r = c2.DistanceTo(mp)/scale; - SK.GetEntity(circle->distance)->DistanceForceTo(r); - - SS.MarkGroupDirtyByEntity(pending.circle); - break; - } - - case DRAGGING_NORMAL: { - Entity *normal = SK.GetEntity(pending.normal); - Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); - Point2d p2 = ProjectPoint(p); - - Quaternion q = normal->NormalGetNum(); - Vector u = q.RotationU(), v = q.RotationV(); - - if(ctrlDown) { - double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); - theta -= atan2(y-p2.y, x-p2.x); - - Vector normal = projRight.Cross(projUp); - u = u.RotatedAbout(normal, -theta); - v = v.RotatedAbout(normal, -theta); - } else { - double dx = -(x - orig.mouse.x); - double dy = -(y - orig.mouse.y); - double s = 0.3*(PI/180); // degrees per pixel - u = u.RotatedAbout(projUp, -s*dx); - u = u.RotatedAbout(projRight, s*dy); - v = v.RotatedAbout(projUp, -s*dx); - v = v.RotatedAbout(projRight, s*dy); - } - orig.mouse = mp; - normal->NormalForceTo(Quaternion::From(u, v)); - - SS.MarkGroupDirtyByEntity(pending.normal); - break; - } - - default: oops(); - } - if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) { - SS.GenerateAll(); - } - havePainted = false; -} - -void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, - double rx, double ry, double rz, - bool shiftDown) -{ - if(!havePainted) return; - Vector out = projRight.Cross(projUp); - - // rotation vector is axis of rotation, and its magnitude is angle - Vector aa = Vector::From(rx, ry, rz); - // but it's given with respect to screen projection frame - aa = aa.ScaleOutOfCsys(projRight, projUp, out); - double aam = aa.Magnitude(); - if(aam != 0.0) aa = aa.WithMagnitude(1); - - // This can either transform our view, or transform an imported part. - GroupSelection(); - Entity *e = NULL; - Group *g = NULL; - if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]); - if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]); - if(e) g = SK.GetGroup(e->group); - if(g && g->type == Group::IMPORTED && !shiftDown) { - // Apply the transformation to an imported part. Gain down the Z - // axis, since it's hard to see what you're doing on that one since - // it's normal to the screen. - Vector t = projRight.ScaledBy(tx/scale).Plus( - projUp .ScaledBy(ty/scale).Plus( - out .ScaledBy(0.1*tz/scale))); - Quaternion q = Quaternion::From(aa, aam); - - // If we go five seconds without SpaceNavigator input, or if we've - // switched groups, then consider that a new action and save an undo - // point. - SDWORD now = GetMilliseconds(); - if(now - lastSpaceNavigatorTime > 5000 || - lastSpaceNavigatorGroup.v != g->h.v) - { - SS.UndoRemember(); - } - - g->TransformImportedBy(t, q); - - lastSpaceNavigatorTime = now; - lastSpaceNavigatorGroup = g->h; - SS.MarkGroupDirty(g->h); - SS.later.generateAll = true; - } else { - // Apply the transformation to the view of the everything. The - // x and y components are translation; but z component is scale, - // not translation, or else it would do nothing in a parallel - // projection - offset = offset.Plus(projRight.ScaledBy(tx/scale)); - offset = offset.Plus(projUp.ScaledBy(ty/scale)); - scale *= exp(0.001*tz); - - if(aam != 0.0) { - projRight = projRight.RotatedAbout(aa, -aam); - projUp = projUp. RotatedAbout(aa, -aam); - NormalizeProjectionVectors(); - } - } - - havePainted = false; - InvalidateGraphics(); -} - -void GraphicsWindow::SpaceNavigatorButtonUp(void) { - ZoomToFit(false); - InvalidateGraphics(); -} - -void GraphicsWindow::ClearPending(void) { - memset(&pending, 0, sizeof(pending)); -} - -void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { - if(GraphicsEditControlIsVisible()) return; - - orig.offset = offset; - orig.projUp = projUp; - orig.projRight = projRight; - orig.mouse.x = x; - orig.mouse.y = y; - orig.startedMoving = false; -} - -void GraphicsWindow::ContextMenuListStyles(void) { - CreateContextSubmenu(); - Style *s; - bool empty = true; - for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) { - if(s->h.v < Style::FIRST_CUSTOM) continue; - - AddContextMenuItem(s->DescriptionString(), CMNU_FIRST_STYLE + s->h.v); - empty = false; - } - - if(!empty) AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - - AddContextMenuItem("No Style", CMNU_NO_STYLE); - AddContextMenuItem("Newly Created Custom Style...", CMNU_NEW_CUSTOM_STYLE); -} - -void GraphicsWindow::MouseRightUp(double x, double y) { - // Don't show a context menu if the user is right-clicking the toolbar, - // or if they are finishing a pan. - if(ToolbarMouseMoved((int)x, (int)y)) return; - if(orig.startedMoving) return; - - if(context.active) return; - context.active = true; - - if(pending.operation == DRAGGING_NEW_LINE_POINT) { - // Special case; use a right click to stop drawing lines, since - // a left click would draw another one. This is quicker and more - // intuitive than hitting escape. - ClearPending(); - } - - GroupSelection(); - if(hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { - // No reason to display a context menu. - goto done; - } - - // We can either work on the selection (like the functions are designed to) - // or on the hovered item. In the latter case we can fudge things by just - // selecting the hovered item, and then applying our operation to the - // selection. - bool toggleForStyles = false, - toggleForGroupInfo = false, - toggleForDelete = false, - toggleForStyleInfo = false; - - if(!hover.IsEmpty()) { - AddContextMenuItem("Toggle Hovered Item Selection", - CMNU_TOGGLE_SELECTION); - } - - if(gs.stylables > 0) { - ContextMenuListStyles(); - AddContextMenuItem("Assign Selection to Style", CONTEXT_SUBMENU); - } else if(gs.n == 0 && gs.constraints == 0 && hover.IsStylable()) { - ContextMenuListStyles(); - AddContextMenuItem("Assign Hovered Item to Style", CONTEXT_SUBMENU); - toggleForStyles = true; - } - - if(gs.n + gs.constraints == 1) { - AddContextMenuItem("Group Info for Selected Item", CMNU_GROUP_INFO); - } else if(!hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { - AddContextMenuItem("Group Info for Hovered Item", CMNU_GROUP_INFO); - toggleForGroupInfo = true; - } - - if(gs.n + gs.constraints == 1 && gs.stylables == 1) { - AddContextMenuItem("Style Info for Selected Item", CMNU_STYLE_INFO); - } else if(hover.IsStylable() && gs.n == 0 && gs.constraints == 0) { - AddContextMenuItem("Style Info for Hovered Item", CMNU_STYLE_INFO); - toggleForStyleInfo = true; - } - - if(hover.constraint.v && gs.n == 0 && gs.constraints == 0) { - Constraint *c = SK.GetConstraint(hover.constraint); - if(c->HasLabel() && c->type != Constraint::COMMENT) { - AddContextMenuItem("Toggle Reference Dimension", - CMNU_REFERENCE_DIM); - } - if(c->type == Constraint::ANGLE || - c->type == Constraint::EQUAL_ANGLE) - { - AddContextMenuItem("Other Supplementary Angle", - CMNU_OTHER_ANGLE); - } - } - - if(gs.n > 0 || gs.constraints > 0) { - AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); - AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); - } else if(!hover.IsEmpty()) { - AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL); - toggleForDelete = true; - } - - int ret = ShowContextMenu(); - switch(ret) { - case CMNU_TOGGLE_SELECTION: - ToggleSelectionStateOfHovered(); - break; - - case CMNU_UNSELECT_ALL: - MenuEdit(MNU_UNSELECT_ALL); - break; - - case CMNU_DELETE_SEL: - if(toggleForDelete) ToggleSelectionStateOfHovered(); - MenuEdit(MNU_DELETE); - break; - - case CMNU_REFERENCE_DIM: - ToggleSelectionStateOfHovered(); - Constraint::MenuConstrain(MNU_REFERENCE); - break; - - case CMNU_OTHER_ANGLE: - ToggleSelectionStateOfHovered(); - Constraint::MenuConstrain(MNU_OTHER_ANGLE); - break; - - case CMNU_GROUP_INFO: { - if(toggleForGroupInfo) ToggleSelectionStateOfHovered(); - - hGroup hg; - GroupSelection(); - if(gs.entities == 1) { - hg = SK.GetEntity(gs.entity[0])->group; - } else if(gs.points == 1) { - hg = SK.GetEntity(gs.point[0])->group; - } else if(gs.constraints == 1) { - hg = SK.GetConstraint(gs.constraint[0])->group; - } else { - break; - } - ClearSelection(); - - SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); - SS.TW.shown.group = hg; - SS.later.showTW = true; - break; - } - - case CMNU_STYLE_INFO: { - if(toggleForStyleInfo) ToggleSelectionStateOfHovered(); - - hStyle hs; - GroupSelection(); - if(gs.entities == 1) { - hs = Style::ForEntity(gs.entity[0]); - } else if(gs.points == 1) { - hs = Style::ForEntity(gs.point[0]); - } else if(gs.constraints == 1) { - hs = SK.GetConstraint(gs.constraint[0])->disp.style; - if(!hs.v) hs.v = Style::CONSTRAINT; - } else { - break; - } - ClearSelection(); - - SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); - SS.TW.shown.style = hs; - SS.later.showTW = true; - break; - } - - case CMNU_NEW_CUSTOM_STYLE: { - if(toggleForStyles) ToggleSelectionStateOfHovered(); - DWORD v = Style::CreateCustomStyle(); - Style::AssignSelectionToStyle(v); - break; - } - - case CMNU_NO_STYLE: - if(toggleForStyles) ToggleSelectionStateOfHovered(); - Style::AssignSelectionToStyle(0); - break; - - default: - if(ret >= CMNU_FIRST_STYLE) { - if(toggleForStyles) ToggleSelectionStateOfHovered(); - Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); - } - // otherwise it was probably cancelled, so do nothing - break; - } - -done: - context.active = false; -} - -hRequest GraphicsWindow::AddRequest(int type) { - return AddRequest(type, true); -} -hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { - if(rememberForUndo) SS.UndoRemember(); - - Request r; - memset(&r, 0, sizeof(r)); - r.group = activeGroup; - Group *g = SK.GetGroup(activeGroup); - if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { - r.construction = false; - } else { - r.construction = true; - } - r.workplane = ActiveWorkplane(); - r.type = type; - SK.request.AddAndAssignId(&r); - - // We must regenerate the parameters, so that the code that tries to - // place this request's entities where the mouse is can do so. But - // we mustn't try to solve until reasonable values have been supplied - // for these new parameters, or else we'll get a numerical blowup. - SS.GenerateAll(-1, -1); - SS.MarkGroupDirty(r.group); - return r.h; -} - -bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { - if(!hover.entity.v) return false; - - Entity *e = SK.GetEntity(hover.entity); - if(e->IsPoint()) { - Constraint::ConstrainCoincident(e->h, pt); - return true; - } - if(e->IsCircle()) { - Constraint::Constrain(Constraint::PT_ON_CIRCLE, - pt, Entity::NO_ENTITY, e->h); - return true; - } - if(e->type == Entity::LINE_SEGMENT) { - Constraint::Constrain(Constraint::PT_ON_LINE, - pt, Entity::NO_ENTITY, e->h); - return true; - } - - return false; -} - -void GraphicsWindow::MouseLeftDown(double mx, double my) { - if(GraphicsEditControlIsVisible()) return; - HideTextEditControl(); - - if(SS.showToolbar) { - if(ToolbarMouseDown((int)mx, (int)my)) return; - } - - // Make sure the hover is up to date. - MouseMoved(mx, my, false, false, false, false, false); - orig.mouse.x = mx; - orig.mouse.y = my; - - // The current mouse location - Vector v = offset.ScaledBy(-1); - v = v.Plus(projRight.ScaledBy(mx/scale)); - v = v.Plus(projUp.ScaledBy(my/scale)); - - hRequest hr; - switch(pending.operation) { - case MNU_DATUM_POINT: - hr = AddRequest(Request::DATUM_POINT); - SK.GetEntity(hr.entity(0))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(0)); - - ClearSuper(); - break; - - case MNU_LINE_SEGMENT: - hr = AddRequest(Request::LINE_SEGMENT); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_LINE_POINT; - pending.point = hr.entity(2); - pending.description = "click to place next point of line"; - SK.GetEntity(pending.point)->PointForceTo(v); - break; - - case MNU_RECTANGLE: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw rectangle in 3d; select a workplane first."); - ClearSuper(); - break; - } - hRequest lns[4]; - int i; - SS.UndoRemember(); - for(i = 0; i < 4; i++) { - lns[i] = AddRequest(Request::LINE_SEGMENT, false); - } - for(i = 0; i < 4; i++) { - Constraint::ConstrainCoincident( - lns[i].entity(1), lns[(i+1)%4].entity(2)); - SK.GetEntity(lns[i].entity(1))->PointForceTo(v); - SK.GetEntity(lns[i].entity(2))->PointForceTo(v); - } - for(i = 0; i < 4; i++) { - Constraint::Constrain( - (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, - Entity::NO_ENTITY, Entity::NO_ENTITY, - lns[i].entity(0)); - } - ConstrainPointByHovered(lns[2].entity(1)); - - pending.operation = DRAGGING_NEW_POINT; - pending.point = lns[1].entity(2); - pending.description = "click to place other corner of rectangle"; - break; - } - case MNU_CIRCLE: - hr = AddRequest(Request::CIRCLE); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_RADIUS; - pending.circle = hr.entity(0); - pending.description = "click to set radius"; - SK.GetParam(hr.param(0))->val = 0; - break; - - case MNU_ARC: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw arc in 3d; select a workplane first."); - ClearPending(); - break; - } - hr = AddRequest(Request::ARC_OF_CIRCLE); - // This fudge factor stops us from immediately failing to solve - // because of the arc's implicit (equal radius) tangent. - Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale); - SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj)); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - SK.GetEntity(hr.entity(3))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(2)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_ARC_POINT; - pending.point = hr.entity(3); - pending.description = "click to place point"; - break; - } - case MNU_CUBIC: - hr = AddRequest(Request::CUBIC); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - SK.GetEntity(hr.entity(3))->PointForceTo(v); - SK.GetEntity(hr.entity(4))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_CUBIC_POINT; - pending.point = hr.entity(4); - pending.description = "click to place next point of cubic"; - break; - - case MNU_WORKPLANE: - if(LockedInWorkplane()) { - Error("Sketching in a workplane already; sketch in 3d before " - "creating new workplane."); - ClearSuper(); - break; - } - hr = AddRequest(Request::WORKPLANE); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - break; - - case MNU_TTF_TEXT: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw text in 3d; select a workplane first."); - ClearSuper(); - break; - } - hr = AddRequest(Request::TTF_TEXT); - Request *r = SK.GetRequest(hr); - r->str.strcpy("Abc"); - r->font.strcpy("arial.ttf"); - - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - - pending.operation = DRAGGING_NEW_POINT; - pending.point = hr.entity(2); - pending.description = "click to place bottom left of text"; - break; - } - - case MNU_COMMENT: { - ClearSuper(); - Constraint c; - ZERO(&c); - c.group = SS.GW.activeGroup; - c.workplane = SS.GW.ActiveWorkplane(); - c.type = Constraint::COMMENT; - c.disp.offset = v; - c.comment.strcpy("NEW COMMENT -- DOUBLE-CLICK TO EDIT"); - Constraint::AddConstraint(&c); - break; - } - - case DRAGGING_RADIUS: - case DRAGGING_NEW_POINT: - // The MouseMoved event has already dragged it as desired. - ClearPending(); - break; - - case DRAGGING_NEW_ARC_POINT: - case DRAGGING_NEW_CUBIC_POINT: - ConstrainPointByHovered(pending.point); - ClearPending(); - break; - - case DRAGGING_NEW_LINE_POINT: { - if(ConstrainPointByHovered(pending.point)) { - ClearPending(); - break; - } - // Create a new line segment, so that we continue drawing. - hRequest hr = AddRequest(Request::LINE_SEGMENT); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - - // Constrain the line segments to share an endpoint - Constraint::ConstrainCoincident(pending.point, hr.entity(1)); - - // And drag an endpoint of the new line segment - pending.operation = DRAGGING_NEW_LINE_POINT; - pending.point = hr.entity(2); - pending.description = "click to place next point of next line"; - - break; - } - - case 0: - default: - ClearPending(); - ToggleSelectionStateOfHovered(); - break; - } - - SS.later.showTW = true; - InvalidateGraphics(); -} - -void GraphicsWindow::MouseLeftUp(double mx, double my) { - switch(pending.operation) { - case DRAGGING_POINT: - case DRAGGING_CONSTRAINT: - case DRAGGING_NORMAL: - case DRAGGING_RADIUS: - ClearPending(); - break; - - default: - break; // do nothing - } -} - -void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { - if(GraphicsEditControlIsVisible()) return; - HideTextEditControl(); - - if(hover.constraint.v) { - constraintBeingEdited = hover.constraint; - ClearSuper(); - - Constraint *c = SK.GetConstraint(constraintBeingEdited); - if(c->reference) { - // Not meaningful to edit a reference dimension - return; - } - - Vector p3 = c->GetLabelPos(); - Point2d p2 = ProjectPoint(p3); - - char s[1024]; - switch(c->type) { - case Constraint::COMMENT: - strcpy(s, c->comment.str); - break; - - case Constraint::ANGLE: - case Constraint::LENGTH_RATIO: - sprintf(s, "%.3f", c->valA); - break; - - default: - strcpy(s, SS.MmToString(fabs(c->valA))); - break; - } - ShowGraphicsEditControl((int)p2.x, (int)p2.y-4, s); - } -} - -void GraphicsWindow::EditControlDone(char *s) { - HideGraphicsEditControl(); - Constraint *c = SK.GetConstraint(constraintBeingEdited); - - if(c->type == Constraint::COMMENT) { - SS.UndoRemember(); - c->comment.strcpy(s); - return; - } - - Expr *e = Expr::From(s); - if(e) { - SS.UndoRemember(); - - switch(c->type) { - case Constraint::PT_LINE_DISTANCE: - case Constraint::PT_FACE_DISTANCE: - case Constraint::PT_PLANE_DISTANCE: { - // The sign is not displayed to the user, but this is a signed - // distance internally. To flip the sign, the user enters a - // negative distance. - bool wasNeg = (c->valA < 0); - if(wasNeg) { - c->valA = -SS.ExprToMm(e); - } else { - c->valA = SS.ExprToMm(e); - } - break; - } - case Constraint::ANGLE: - case Constraint::LENGTH_RATIO: - // These don't get the units conversion for distance, and - // they're always positive - c->valA = fabs(e->Eval()); - break; - - default: - // These are always positive, and they get the units conversion. - c->valA = fabs(SS.ExprToMm(e)); - break; - } - SS.MarkGroupDirty(c->group); - SS.GenerateAll(); - } else { - Error("Not a valid number or expression: '%s'", s); - } -} - -bool GraphicsWindow::KeyDown(int c) { - if(c == ('h' - 'a') + 1) { - // Treat backspace identically to escape. - MenuEdit(MNU_UNSELECT_ALL); - return true; - } - - return false; -} - -void GraphicsWindow::MouseScroll(double x, double y, int delta) { - double offsetRight = offset.Dot(projRight); - double offsetUp = offset.Dot(projUp); - - double righti = x/scale - offsetRight; - double upi = y/scale - offsetUp; - - if(delta > 0) { - scale *= 1.2; - } else { - scale /= 1.2; - } - - double rightf = x/scale - offsetRight; - double upf = y/scale - offsetUp; - - offset = offset.Plus(projRight.ScaledBy(rightf - righti)); - offset = offset.Plus(projUp.ScaledBy(upf - upi)); - - InvalidateGraphics(); -} - -void GraphicsWindow::MouseLeave(void) { - // Un-hover everything when the mouse leaves our window, unless there's - // currently a context menu shown. - if(!context.active) { - hover.Clear(); - toolbarTooltipped = 0; - toolbarHovered = 0; - PaintGraphics(); - } -} - bool GraphicsWindow::Selection::Equals(Selection *b) { if(entity.v != b->entity.v) return false; if(constraint.v != b->constraint.v) return false; @@ -1154,7 +192,6 @@ void GraphicsWindow::GroupSelection(void) { } } - void GraphicsWindow::HitTestMakeSelection(Point2d mp) { int i; double d, dmin = 1e12; @@ -1204,6 +241,49 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { } } +//----------------------------------------------------------------------------- +// Project a point in model space to screen space, exactly as gl would; return +// units are pixels. +//----------------------------------------------------------------------------- +Point2d GraphicsWindow::ProjectPoint(Vector p) { + Vector p3 = ProjectPoint3(p); + Point2d p2 = { p3.x, p3.y }; + return p2; +} +//----------------------------------------------------------------------------- +// Project a point in model space to screen space, exactly as gl would; return +// units are pixels. The z coordinate is also returned, also in pixels. +//----------------------------------------------------------------------------- +Vector GraphicsWindow::ProjectPoint3(Vector p) { + double w; + Vector r = ProjectPoint4(p, &w); + return r.ScaledBy(scale/w); +} +//----------------------------------------------------------------------------- +// Project a point in model space halfway into screen space. The scale is +// not applied, and the perspective divide isn't applied; instead the w +// coordinate is returned separately. +//----------------------------------------------------------------------------- +Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) { + p = p.Plus(offset); + + Vector r; + r.x = p.Dot(projRight); + r.y = p.Dot(projUp); + r.z = p.Dot(projUp.Cross(projRight)); + + *w = 1 + r.z*SS.CameraTangent()*scale; + return r; +} + +void GraphicsWindow::NormalizeProjectionVectors(void) { + Vector norm = projRight.Cross(projUp); + projUp = norm.Cross(projRight); + + projUp = projUp.ScaledBy(1/projUp.Magnitude()); + projRight = projRight.ScaledBy(1/projRight.Magnitude()); +} + Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { Vector n = projRight.Cross(projUp); diff --git a/graphicswin.cpp b/graphicswin.cpp index 98cc001..ec85ebb 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -164,49 +164,6 @@ void GraphicsWindow::Init(void) { EnsureValidActives(); } -void GraphicsWindow::NormalizeProjectionVectors(void) { - Vector norm = projRight.Cross(projUp); - projUp = norm.Cross(projRight); - - projUp = projUp.ScaledBy(1/projUp.Magnitude()); - projRight = projRight.ScaledBy(1/projRight.Magnitude()); -} - -//----------------------------------------------------------------------------- -// Project a point in model space to screen space, exactly as gl would; return -// units are pixels. -//----------------------------------------------------------------------------- -Point2d GraphicsWindow::ProjectPoint(Vector p) { - Vector p3 = ProjectPoint3(p); - Point2d p2 = { p3.x, p3.y }; - return p2; -} -//----------------------------------------------------------------------------- -// Project a point in model space to screen space, exactly as gl would; return -// units are pixels. The z coordinate is also returned, also in pixels. -//----------------------------------------------------------------------------- -Vector GraphicsWindow::ProjectPoint3(Vector p) { - double w; - Vector r = ProjectPoint4(p, &w); - return r.ScaledBy(scale/w); -} -//----------------------------------------------------------------------------- -// Project a point in model space halfway into screen space. The scale is -// not applied, and the perspective divide isn't applied; instead the w -// coordinate is returned separately. -//----------------------------------------------------------------------------- -Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) { - p = p.Plus(offset); - - Vector r; - r.x = p.Dot(projRight); - r.y = p.Dot(projUp); - r.z = p.Dot(projUp.Cross(projRight)); - - *w = 1 + r.z*SS.CameraTangent()*scale; - return r; -} - void GraphicsWindow::AnimateOntoWorkplane(void) { if(!LockedInWorkplane()) return; diff --git a/mouse.cpp b/mouse.cpp new file mode 100644 index 0000000..bdefbe3 --- /dev/null +++ b/mouse.cpp @@ -0,0 +1,971 @@ +//----------------------------------------------------------------------------- +// Anything relating to mouse, keyboard, or 6-DOF mouse input. +//----------------------------------------------------------------------------- +#include "solvespace.h" + +void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { + Entity *p = SK.GetEntity(hp); + Vector pos = p->PointGetNum(); + UpdateDraggedNum(&pos, mx, my); + p->PointForceTo(pos); +} + +void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { + *pos = pos->Plus(projRight.ScaledBy((mx - orig.mouse.x)/scale)); + *pos = pos->Plus(projUp.ScaledBy((my - orig.mouse.y)/scale)); + + orig.mouse.x = mx; + orig.mouse.y = my; + InvalidateGraphics(); +} + +void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, + bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) +{ + if(GraphicsEditControlIsVisible()) return; + if(context.active) return; + + if(rightDown) { + middleDown = true; + shiftDown = !shiftDown; + } + + if(SS.showToolbar) { + if(ToolbarMouseMoved((int)x, (int)y)) { + hover.Clear(); + return; + } + } + + Point2d mp = { x, y }; + + if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { + // Avoid accidentally panning (or rotating if shift is down) if the + // user wants a context menu. + return; + } + orig.startedMoving = true; + + // If the middle button is down, then mouse movement is used to pan and + // rotate our view. This wins over everything else. + if(middleDown) { + hover.Clear(); + + double dx = (x - orig.mouse.x) / scale; + double dy = (y - orig.mouse.y) / scale; + + if(!(shiftDown || ctrlDown)) { + double s = 0.3*(PI/180)*scale; // degrees per pixel + projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); + projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); + + NormalizeProjectionVectors(); + } else if(ctrlDown) { + double theta = atan2(orig.mouse.y, orig.mouse.x); + theta -= atan2(y, x); + + Vector normal = orig.projRight.Cross(orig.projUp); + projRight = orig.projRight.RotatedAbout(normal, theta); + projUp = orig.projUp.RotatedAbout(normal, theta); + + NormalizeProjectionVectors(); + } else { + offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; + offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; + offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; + } + + orig.projRight = projRight; + orig.projUp = projUp; + orig.offset = offset; + orig.mouse.x = x; + orig.mouse.y = y; + + InvalidateGraphics(); + return; + } + + if(pending.operation == 0) { + double dm = orig.mouse.DistanceTo(mp); + // If we're currently not doing anything, then see if we should + // start dragging something. + if(leftDown && dm > 3) { + if(hover.entity.v) { + Entity *e = SK.GetEntity(hover.entity); + if(e->IsPoint()) { + // Start dragging this point. + ClearSelection(); + pending.point = hover.entity; + pending.operation = DRAGGING_POINT; + } else if(e->type == Entity::CIRCLE) { + // Drag the radius. + ClearSelection(); + pending.circle = hover.entity; + pending.operation = DRAGGING_RADIUS; + } else if(e->IsNormal()) { + ClearSelection(); + pending.normal = hover.entity; + pending.operation = DRAGGING_NORMAL; + } + } else if(hover.constraint.v && + SK.GetConstraint(hover.constraint)->HasLabel()) + { + ClearSelection(); + pending.constraint = hover.constraint; + pending.operation = DRAGGING_CONSTRAINT; + } + if(pending.operation != 0) { + // We just started a drag, so remember for the undo before + // the drag changes anything. + SS.UndoRemember(); + } + } else { + // Otherwise, just hit test and give up; but don't hit test + // if the mouse is down, because then the user could hover + // a point, mouse down (thus selecting it), and drag, in an + // effort to drag the point, but instead hover a different + // entity before we move far enough to start the drag. + if(!leftDown) HitTestMakeSelection(mp); + } + return; + } + + // If the user has started an operation from the menu, but not + // completed it, then just do the selection. + if(pending.operation < FIRST_PENDING) { + HitTestMakeSelection(mp); + return; + } + + // We're currently dragging something; so do that. But if we haven't + // painted since the last time we solved, do nothing, because there's + // no sense solving a frame and not displaying it. + if(!havePainted) return; + switch(pending.operation) { + case DRAGGING_CONSTRAINT: { + Constraint *c = SK.constraint.FindById(pending.constraint); + UpdateDraggedNum(&(c->disp.offset), x, y); + break; + } + case DRAGGING_NEW_LINE_POINT: + HitTestMakeSelection(mp); + // and fall through + case DRAGGING_NEW_POINT: + case DRAGGING_POINT: { + Entity *p = SK.GetEntity(pending.point); + if((p->type == Entity::POINT_N_ROT_TRANS) && + (shiftDown || ctrlDown)) + { + // These points also come with a rotation, which the user can + // edit by pressing shift or control. + Quaternion q = p->PointGetQuaternion(); + Vector p3 = p->PointGetNum(); + Point2d p2 = ProjectPoint(p3); + + Vector u = q.RotationU(), v = q.RotationV(); + if(ctrlDown) { + double d = mp.DistanceTo(p2); + if(d < 25) { + // Don't start dragging the position about the normal + // until we're a little ways out, to get a reasonable + // reference pos + orig.mouse = mp; + break; + } + double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); + theta -= atan2(y-p2.y, x-p2.x); + + Vector gn = projRight.Cross(projUp); + u = u.RotatedAbout(gn, -theta); + v = v.RotatedAbout(gn, -theta); + } else { + double dx = -(x - orig.mouse.x); + double dy = -(y - orig.mouse.y); + double s = 0.3*(PI/180); // degrees per pixel + u = u.RotatedAbout(projUp, -s*dx); + u = u.RotatedAbout(projRight, s*dy); + v = v.RotatedAbout(projUp, -s*dx); + v = v.RotatedAbout(projRight, s*dy); + } + q = Quaternion::From(u, v); + p->PointForceQuaternionTo(q); + // Let's rotate about the selected point; so fix up the + // translation so that that point didn't move. + p->PointForceTo(p3); + orig.mouse = mp; + } else { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + } + SS.MarkGroupDirtyByEntity(pending.point); + break; + } + case DRAGGING_NEW_CUBIC_POINT: { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + + hRequest hr = pending.point.request(); + Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(); + Vector p3 = SK.GetEntity(hr.entity(4))->PointGetNum(); + Vector p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)); + SK.GetEntity(hr.entity(2))->PointForceTo(p1); + Vector p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); + SK.GetEntity(hr.entity(3))->PointForceTo(p2); + + SS.MarkGroupDirtyByEntity(pending.point); + break; + } + case DRAGGING_NEW_ARC_POINT: { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + + hRequest hr = pending.point.request(); + Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum(); + Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum(); + Vector center = (ona.Plus(onb)).ScaledBy(0.5); + + SK.GetEntity(hr.entity(1))->PointForceTo(center); + + SS.MarkGroupDirtyByEntity(pending.point); + break; + } + case DRAGGING_NEW_RADIUS: + case DRAGGING_RADIUS: { + Entity *circle = SK.GetEntity(pending.circle); + Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); + Point2d c2 = ProjectPoint(center); + double r = c2.DistanceTo(mp)/scale; + SK.GetEntity(circle->distance)->DistanceForceTo(r); + + SS.MarkGroupDirtyByEntity(pending.circle); + break; + } + + case DRAGGING_NORMAL: { + Entity *normal = SK.GetEntity(pending.normal); + Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); + Point2d p2 = ProjectPoint(p); + + Quaternion q = normal->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + if(ctrlDown) { + double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); + theta -= atan2(y-p2.y, x-p2.x); + + Vector normal = projRight.Cross(projUp); + u = u.RotatedAbout(normal, -theta); + v = v.RotatedAbout(normal, -theta); + } else { + double dx = -(x - orig.mouse.x); + double dy = -(y - orig.mouse.y); + double s = 0.3*(PI/180); // degrees per pixel + u = u.RotatedAbout(projUp, -s*dx); + u = u.RotatedAbout(projRight, s*dy); + v = v.RotatedAbout(projUp, -s*dx); + v = v.RotatedAbout(projRight, s*dy); + } + orig.mouse = mp; + normal->NormalForceTo(Quaternion::From(u, v)); + + SS.MarkGroupDirtyByEntity(pending.normal); + break; + } + + default: oops(); + } + if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) { + SS.GenerateAll(); + } + havePainted = false; +} + +void GraphicsWindow::ClearPending(void) { + memset(&pending, 0, sizeof(pending)); +} + +void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { + if(GraphicsEditControlIsVisible()) return; + + orig.offset = offset; + orig.projUp = projUp; + orig.projRight = projRight; + orig.mouse.x = x; + orig.mouse.y = y; + orig.startedMoving = false; +} + +void GraphicsWindow::ContextMenuListStyles(void) { + CreateContextSubmenu(); + Style *s; + bool empty = true; + for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) { + if(s->h.v < Style::FIRST_CUSTOM) continue; + + AddContextMenuItem(s->DescriptionString(), CMNU_FIRST_STYLE + s->h.v); + empty = false; + } + + if(!empty) AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + + AddContextMenuItem("No Style", CMNU_NO_STYLE); + AddContextMenuItem("Newly Created Custom Style...", CMNU_NEW_CUSTOM_STYLE); +} + +void GraphicsWindow::MouseRightUp(double x, double y) { + // Don't show a context menu if the user is right-clicking the toolbar, + // or if they are finishing a pan. + if(ToolbarMouseMoved((int)x, (int)y)) return; + if(orig.startedMoving) return; + + if(context.active) return; + context.active = true; + + if(pending.operation == DRAGGING_NEW_LINE_POINT) { + // Special case; use a right click to stop drawing lines, since + // a left click would draw another one. This is quicker and more + // intuitive than hitting escape. + ClearPending(); + } + + GroupSelection(); + if(hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { + // No reason to display a context menu. + goto done; + } + + // We can either work on the selection (like the functions are designed to) + // or on the hovered item. In the latter case we can fudge things by just + // selecting the hovered item, and then applying our operation to the + // selection. + bool toggleForStyles = false, + toggleForGroupInfo = false, + toggleForDelete = false, + toggleForStyleInfo = false; + + if(!hover.IsEmpty()) { + AddContextMenuItem("Toggle Hovered Item Selection", + CMNU_TOGGLE_SELECTION); + } + + if(gs.stylables > 0) { + ContextMenuListStyles(); + AddContextMenuItem("Assign Selection to Style", CONTEXT_SUBMENU); + } else if(gs.n == 0 && gs.constraints == 0 && hover.IsStylable()) { + ContextMenuListStyles(); + AddContextMenuItem("Assign Hovered Item to Style", CONTEXT_SUBMENU); + toggleForStyles = true; + } + + if(gs.n + gs.constraints == 1) { + AddContextMenuItem("Group Info for Selected Item", CMNU_GROUP_INFO); + } else if(!hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { + AddContextMenuItem("Group Info for Hovered Item", CMNU_GROUP_INFO); + toggleForGroupInfo = true; + } + + if(gs.n + gs.constraints == 1 && gs.stylables == 1) { + AddContextMenuItem("Style Info for Selected Item", CMNU_STYLE_INFO); + } else if(hover.IsStylable() && gs.n == 0 && gs.constraints == 0) { + AddContextMenuItem("Style Info for Hovered Item", CMNU_STYLE_INFO); + toggleForStyleInfo = true; + } + + if(hover.constraint.v && gs.n == 0 && gs.constraints == 0) { + Constraint *c = SK.GetConstraint(hover.constraint); + if(c->HasLabel() && c->type != Constraint::COMMENT) { + AddContextMenuItem("Toggle Reference Dimension", + CMNU_REFERENCE_DIM); + } + if(c->type == Constraint::ANGLE || + c->type == Constraint::EQUAL_ANGLE) + { + AddContextMenuItem("Other Supplementary Angle", + CMNU_OTHER_ANGLE); + } + } + + if(gs.n > 0 || gs.constraints > 0) { + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); + AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); + } else if(!hover.IsEmpty()) { + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL); + toggleForDelete = true; + } + + int ret = ShowContextMenu(); + switch(ret) { + case CMNU_TOGGLE_SELECTION: + ToggleSelectionStateOfHovered(); + break; + + case CMNU_UNSELECT_ALL: + MenuEdit(MNU_UNSELECT_ALL); + break; + + case CMNU_DELETE_SEL: + if(toggleForDelete) ToggleSelectionStateOfHovered(); + MenuEdit(MNU_DELETE); + break; + + case CMNU_REFERENCE_DIM: + ToggleSelectionStateOfHovered(); + Constraint::MenuConstrain(MNU_REFERENCE); + break; + + case CMNU_OTHER_ANGLE: + ToggleSelectionStateOfHovered(); + Constraint::MenuConstrain(MNU_OTHER_ANGLE); + break; + + case CMNU_GROUP_INFO: { + if(toggleForGroupInfo) ToggleSelectionStateOfHovered(); + + hGroup hg; + GroupSelection(); + if(gs.entities == 1) { + hg = SK.GetEntity(gs.entity[0])->group; + } else if(gs.points == 1) { + hg = SK.GetEntity(gs.point[0])->group; + } else if(gs.constraints == 1) { + hg = SK.GetConstraint(gs.constraint[0])->group; + } else { + break; + } + ClearSelection(); + + SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); + SS.TW.shown.group = hg; + SS.later.showTW = true; + break; + } + + case CMNU_STYLE_INFO: { + if(toggleForStyleInfo) ToggleSelectionStateOfHovered(); + + hStyle hs; + GroupSelection(); + if(gs.entities == 1) { + hs = Style::ForEntity(gs.entity[0]); + } else if(gs.points == 1) { + hs = Style::ForEntity(gs.point[0]); + } else if(gs.constraints == 1) { + hs = SK.GetConstraint(gs.constraint[0])->disp.style; + if(!hs.v) hs.v = Style::CONSTRAINT; + } else { + break; + } + ClearSelection(); + + SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); + SS.TW.shown.style = hs; + SS.later.showTW = true; + break; + } + + case CMNU_NEW_CUSTOM_STYLE: { + if(toggleForStyles) ToggleSelectionStateOfHovered(); + DWORD v = Style::CreateCustomStyle(); + Style::AssignSelectionToStyle(v); + break; + } + + case CMNU_NO_STYLE: + if(toggleForStyles) ToggleSelectionStateOfHovered(); + Style::AssignSelectionToStyle(0); + break; + + default: + if(ret >= CMNU_FIRST_STYLE) { + if(toggleForStyles) ToggleSelectionStateOfHovered(); + Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); + } + // otherwise it was probably cancelled, so do nothing + break; + } + +done: + context.active = false; +} + +hRequest GraphicsWindow::AddRequest(int type) { + return AddRequest(type, true); +} +hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { + if(rememberForUndo) SS.UndoRemember(); + + Request r; + memset(&r, 0, sizeof(r)); + r.group = activeGroup; + Group *g = SK.GetGroup(activeGroup); + if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { + r.construction = false; + } else { + r.construction = true; + } + r.workplane = ActiveWorkplane(); + r.type = type; + SK.request.AddAndAssignId(&r); + + // We must regenerate the parameters, so that the code that tries to + // place this request's entities where the mouse is can do so. But + // we mustn't try to solve until reasonable values have been supplied + // for these new parameters, or else we'll get a numerical blowup. + SS.GenerateAll(-1, -1); + SS.MarkGroupDirty(r.group); + return r.h; +} + +bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { + if(!hover.entity.v) return false; + + Entity *e = SK.GetEntity(hover.entity); + if(e->IsPoint()) { + Constraint::ConstrainCoincident(e->h, pt); + return true; + } + if(e->IsCircle()) { + Constraint::Constrain(Constraint::PT_ON_CIRCLE, + pt, Entity::NO_ENTITY, e->h); + return true; + } + if(e->type == Entity::LINE_SEGMENT) { + Constraint::Constrain(Constraint::PT_ON_LINE, + pt, Entity::NO_ENTITY, e->h); + return true; + } + + return false; +} + +void GraphicsWindow::MouseLeftDown(double mx, double my) { + if(GraphicsEditControlIsVisible()) return; + HideTextEditControl(); + + if(SS.showToolbar) { + if(ToolbarMouseDown((int)mx, (int)my)) return; + } + + // Make sure the hover is up to date. + MouseMoved(mx, my, false, false, false, false, false); + orig.mouse.x = mx; + orig.mouse.y = my; + + // The current mouse location + Vector v = offset.ScaledBy(-1); + v = v.Plus(projRight.ScaledBy(mx/scale)); + v = v.Plus(projUp.ScaledBy(my/scale)); + + hRequest hr; + switch(pending.operation) { + case MNU_DATUM_POINT: + hr = AddRequest(Request::DATUM_POINT); + SK.GetEntity(hr.entity(0))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(0)); + + ClearSuper(); + break; + + case MNU_LINE_SEGMENT: + hr = AddRequest(Request::LINE_SEGMENT); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_LINE_POINT; + pending.point = hr.entity(2); + pending.description = "click to place next point of line"; + SK.GetEntity(pending.point)->PointForceTo(v); + break; + + case MNU_RECTANGLE: { + if(!SS.GW.LockedInWorkplane()) { + Error("Can't draw rectangle in 3d; select a workplane first."); + ClearSuper(); + break; + } + hRequest lns[4]; + int i; + SS.UndoRemember(); + for(i = 0; i < 4; i++) { + lns[i] = AddRequest(Request::LINE_SEGMENT, false); + } + for(i = 0; i < 4; i++) { + Constraint::ConstrainCoincident( + lns[i].entity(1), lns[(i+1)%4].entity(2)); + SK.GetEntity(lns[i].entity(1))->PointForceTo(v); + SK.GetEntity(lns[i].entity(2))->PointForceTo(v); + } + for(i = 0; i < 4; i++) { + Constraint::Constrain( + (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, + Entity::NO_ENTITY, Entity::NO_ENTITY, + lns[i].entity(0)); + } + ConstrainPointByHovered(lns[2].entity(1)); + + pending.operation = DRAGGING_NEW_POINT; + pending.point = lns[1].entity(2); + pending.description = "click to place other corner of rectangle"; + break; + } + case MNU_CIRCLE: + hr = AddRequest(Request::CIRCLE); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_RADIUS; + pending.circle = hr.entity(0); + pending.description = "click to set radius"; + SK.GetParam(hr.param(0))->val = 0; + break; + + case MNU_ARC: { + if(!SS.GW.LockedInWorkplane()) { + Error("Can't draw arc in 3d; select a workplane first."); + ClearPending(); + break; + } + hr = AddRequest(Request::ARC_OF_CIRCLE); + // This fudge factor stops us from immediately failing to solve + // because of the arc's implicit (equal radius) tangent. + Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale); + SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj)); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + SK.GetEntity(hr.entity(3))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(2)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_ARC_POINT; + pending.point = hr.entity(3); + pending.description = "click to place point"; + break; + } + case MNU_CUBIC: + hr = AddRequest(Request::CUBIC); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + SK.GetEntity(hr.entity(3))->PointForceTo(v); + SK.GetEntity(hr.entity(4))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_CUBIC_POINT; + pending.point = hr.entity(4); + pending.description = "click to place next point of cubic"; + break; + + case MNU_WORKPLANE: + if(LockedInWorkplane()) { + Error("Sketching in a workplane already; sketch in 3d before " + "creating new workplane."); + ClearSuper(); + break; + } + hr = AddRequest(Request::WORKPLANE); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + break; + + case MNU_TTF_TEXT: { + if(!SS.GW.LockedInWorkplane()) { + Error("Can't draw text in 3d; select a workplane first."); + ClearSuper(); + break; + } + hr = AddRequest(Request::TTF_TEXT); + Request *r = SK.GetRequest(hr); + r->str.strcpy("Abc"); + r->font.strcpy("arial.ttf"); + + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + + pending.operation = DRAGGING_NEW_POINT; + pending.point = hr.entity(2); + pending.description = "click to place bottom left of text"; + break; + } + + case MNU_COMMENT: { + ClearSuper(); + Constraint c; + ZERO(&c); + c.group = SS.GW.activeGroup; + c.workplane = SS.GW.ActiveWorkplane(); + c.type = Constraint::COMMENT; + c.disp.offset = v; + c.comment.strcpy("NEW COMMENT -- DOUBLE-CLICK TO EDIT"); + Constraint::AddConstraint(&c); + break; + } + + case DRAGGING_RADIUS: + case DRAGGING_NEW_POINT: + // The MouseMoved event has already dragged it as desired. + ClearPending(); + break; + + case DRAGGING_NEW_ARC_POINT: + case DRAGGING_NEW_CUBIC_POINT: + ConstrainPointByHovered(pending.point); + ClearPending(); + break; + + case DRAGGING_NEW_LINE_POINT: { + if(ConstrainPointByHovered(pending.point)) { + ClearPending(); + break; + } + // Create a new line segment, so that we continue drawing. + hRequest hr = AddRequest(Request::LINE_SEGMENT); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + + // Constrain the line segments to share an endpoint + Constraint::ConstrainCoincident(pending.point, hr.entity(1)); + + // And drag an endpoint of the new line segment + pending.operation = DRAGGING_NEW_LINE_POINT; + pending.point = hr.entity(2); + pending.description = "click to place next point of next line"; + + break; + } + + case 0: + default: + ClearPending(); + ToggleSelectionStateOfHovered(); + break; + } + + SS.later.showTW = true; + InvalidateGraphics(); +} + +void GraphicsWindow::MouseLeftUp(double mx, double my) { + switch(pending.operation) { + case DRAGGING_POINT: + case DRAGGING_CONSTRAINT: + case DRAGGING_NORMAL: + case DRAGGING_RADIUS: + ClearPending(); + break; + + default: + break; // do nothing + } +} + +void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { + if(GraphicsEditControlIsVisible()) return; + HideTextEditControl(); + + if(hover.constraint.v) { + constraintBeingEdited = hover.constraint; + ClearSuper(); + + Constraint *c = SK.GetConstraint(constraintBeingEdited); + if(c->reference) { + // Not meaningful to edit a reference dimension + return; + } + + Vector p3 = c->GetLabelPos(); + Point2d p2 = ProjectPoint(p3); + + char s[1024]; + switch(c->type) { + case Constraint::COMMENT: + strcpy(s, c->comment.str); + break; + + case Constraint::ANGLE: + case Constraint::LENGTH_RATIO: + sprintf(s, "%.3f", c->valA); + break; + + default: + strcpy(s, SS.MmToString(fabs(c->valA))); + break; + } + ShowGraphicsEditControl((int)p2.x, (int)p2.y-4, s); + } +} + +void GraphicsWindow::EditControlDone(char *s) { + HideGraphicsEditControl(); + Constraint *c = SK.GetConstraint(constraintBeingEdited); + + if(c->type == Constraint::COMMENT) { + SS.UndoRemember(); + c->comment.strcpy(s); + return; + } + + Expr *e = Expr::From(s); + if(e) { + SS.UndoRemember(); + + switch(c->type) { + case Constraint::PT_LINE_DISTANCE: + case Constraint::PT_FACE_DISTANCE: + case Constraint::PT_PLANE_DISTANCE: { + // The sign is not displayed to the user, but this is a signed + // distance internally. To flip the sign, the user enters a + // negative distance. + bool wasNeg = (c->valA < 0); + if(wasNeg) { + c->valA = -SS.ExprToMm(e); + } else { + c->valA = SS.ExprToMm(e); + } + break; + } + case Constraint::ANGLE: + case Constraint::LENGTH_RATIO: + // These don't get the units conversion for distance, and + // they're always positive + c->valA = fabs(e->Eval()); + break; + + default: + // These are always positive, and they get the units conversion. + c->valA = fabs(SS.ExprToMm(e)); + break; + } + SS.MarkGroupDirty(c->group); + SS.GenerateAll(); + } else { + Error("Not a valid number or expression: '%s'", s); + } +} + +bool GraphicsWindow::KeyDown(int c) { + if(c == ('h' - 'a') + 1) { + // Treat backspace identically to escape. + MenuEdit(MNU_UNSELECT_ALL); + return true; + } + + return false; +} + +void GraphicsWindow::MouseScroll(double x, double y, int delta) { + double offsetRight = offset.Dot(projRight); + double offsetUp = offset.Dot(projUp); + + double righti = x/scale - offsetRight; + double upi = y/scale - offsetUp; + + if(delta > 0) { + scale *= 1.2; + } else { + scale /= 1.2; + } + + double rightf = x/scale - offsetRight; + double upf = y/scale - offsetUp; + + offset = offset.Plus(projRight.ScaledBy(rightf - righti)); + offset = offset.Plus(projUp.ScaledBy(upf - upi)); + + InvalidateGraphics(); +} + +void GraphicsWindow::MouseLeave(void) { + // Un-hover everything when the mouse leaves our window, unless there's + // currently a context menu shown. + if(!context.active) { + hover.Clear(); + toolbarTooltipped = 0; + toolbarHovered = 0; + PaintGraphics(); + } +} + +void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, + double rx, double ry, double rz, + bool shiftDown) +{ + if(!havePainted) return; + Vector out = projRight.Cross(projUp); + + // rotation vector is axis of rotation, and its magnitude is angle + Vector aa = Vector::From(rx, ry, rz); + // but it's given with respect to screen projection frame + aa = aa.ScaleOutOfCsys(projRight, projUp, out); + double aam = aa.Magnitude(); + if(aam != 0.0) aa = aa.WithMagnitude(1); + + // This can either transform our view, or transform an imported part. + GroupSelection(); + Entity *e = NULL; + Group *g = NULL; + if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]); + if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]); + if(e) g = SK.GetGroup(e->group); + if(g && g->type == Group::IMPORTED && !shiftDown) { + // Apply the transformation to an imported part. Gain down the Z + // axis, since it's hard to see what you're doing on that one since + // it's normal to the screen. + Vector t = projRight.ScaledBy(tx/scale).Plus( + projUp .ScaledBy(ty/scale).Plus( + out .ScaledBy(0.1*tz/scale))); + Quaternion q = Quaternion::From(aa, aam); + + // If we go five seconds without SpaceNavigator input, or if we've + // switched groups, then consider that a new action and save an undo + // point. + SDWORD now = GetMilliseconds(); + if(now - lastSpaceNavigatorTime > 5000 || + lastSpaceNavigatorGroup.v != g->h.v) + { + SS.UndoRemember(); + } + + g->TransformImportedBy(t, q); + + lastSpaceNavigatorTime = now; + lastSpaceNavigatorGroup = g->h; + SS.MarkGroupDirty(g->h); + SS.later.generateAll = true; + } else { + // Apply the transformation to the view of the everything. The + // x and y components are translation; but z component is scale, + // not translation, or else it would do nothing in a parallel + // projection + offset = offset.Plus(projRight.ScaledBy(tx/scale)); + offset = offset.Plus(projUp.ScaledBy(ty/scale)); + scale *= exp(0.001*tz); + + if(aam != 0.0) { + projRight = projRight.RotatedAbout(aa, -aam); + projUp = projUp. RotatedAbout(aa, -aam); + NormalizeProjectionVectors(); + } + } + + havePainted = false; + InvalidateGraphics(); +} + +void GraphicsWindow::SpaceNavigatorButtonUp(void) { + ZoomToFit(false); + InvalidateGraphics(); +} + diff --git a/style.cpp b/style.cpp index 6925451..674c021 100644 --- a/style.cpp +++ b/style.cpp @@ -511,13 +511,13 @@ bool TextWindow::EditControlDoneForStyles(char *str) { } else { s->width = v; } - return true; + break; } case EDIT_STYLE_TEXT_ANGLE: SS.UndoRemember(); s = Style::Get(edit.style); s->textAngle = WRAP_SYMMETRIC(atof(str), 360); - return true; + break; case EDIT_BACKGROUND_COLOR: case EDIT_STYLE_COLOR: { @@ -536,7 +536,7 @@ bool TextWindow::EditControlDoneForStyles(char *str) { } else { Error("Bad format: specify color as r, g, b"); } - return true; + break; } case EDIT_STYLE_NAME: if(!StringAllPrintable(str) || !*str) { @@ -546,10 +546,11 @@ bool TextWindow::EditControlDoneForStyles(char *str) { s = Style::Get(edit.style); s->name.strcpy(str); } - return true; + break; default: return false; } + return true; } void TextWindow::ShowStyleInfo(void) { diff --git a/textscreens.cpp b/textscreens.cpp index 6cb9f93..3e59d36 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -579,244 +579,6 @@ void TextWindow::ShowGroupSolveInfo(void) { Printf(false, "by selecting Edit -> Undo."); } -//----------------------------------------------------------------------------- -// For the configuration screen, setup items that are not specific to the -// file being edited right now. -//----------------------------------------------------------------------------- -void TextWindow::ScreenChangeLightDirection(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.2f, %.2f, %.2f", CO(SS.lightDir[v])); - ShowTextEditControl(29+2*v, 8, str); - SS.TW.edit.meaning = EDIT_LIGHT_DIRECTION; - SS.TW.edit.i = v; -} -void TextWindow::ScreenChangeLightIntensity(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.2f", SS.lightIntensity[v]); - ShowTextEditControl(29+2*v, 30, str); - SS.TW.edit.meaning = EDIT_LIGHT_INTENSITY; - SS.TW.edit.i = v; -} -void TextWindow::ScreenChangeColor(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.2f, %.2f, %.2f", - REDf(SS.modelColor[v]), - GREENf(SS.modelColor[v]), - BLUEf(SS.modelColor[v])); - ShowTextEditControl(9+2*v, 12, str); - SS.TW.edit.meaning = EDIT_COLOR; - SS.TW.edit.i = v; -} -void TextWindow::ScreenChangeChordTolerance(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.2f", SS.chordTol); - ShowTextEditControl(37, 3, str); - SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE; -} -void TextWindow::ScreenChangeMaxSegments(int link, DWORD v) { - char str[1024]; - sprintf(str, "%d", SS.maxSegments); - ShowTextEditControl(41, 3, str); - SS.TW.edit.meaning = EDIT_MAX_SEGMENTS; -} -void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.3f", 1000*SS.cameraTangent); - 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(57, 3, str); - SS.TW.edit.meaning = EDIT_EXPORT_SCALE; -} -void TextWindow::ScreenChangeExportOffset(int link, DWORD v) { - ShowTextEditControl(61, 3, SS.MmToString(SS.exportOffset)); - SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; -} -void TextWindow::ScreenChangeFixExportColors(int link, DWORD v) { - SS.fixExportColors = !SS.fixExportColors; -} -void TextWindow::ScreenChangeBackFaces(int link, DWORD v) { - SS.drawBackFaces = !SS.drawBackFaces; - InvalidateGraphics(); -} -void TextWindow::ScreenChangeShadedTriangles(int link, DWORD v) { - SS.exportShadedTriangles = !SS.exportShadedTriangles; - InvalidateGraphics(); -} -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 = 75, 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)"); - - for(i = 0; i < SS.MODEL_COLORS; i++) { - Printf(false, "%Bp #%d: %Bp %Bp (%@, %@, %@) %f%D%Ll%Fl[change]%E", - (i & 1) ? 'd' : 'a', - i, 0x80000000 | SS.modelColor[i], - (i & 1) ? 'd' : 'a', - REDf(SS.modelColor[i]), - GREENf(SS.modelColor[i]), - BLUEf(SS.modelColor[i]), - &ScreenChangeColor, i); - } - - Printf(false, ""); - Printf(false, "%Ft light direction intensity"); - for(i = 0; i < 2; i++) { - Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E " - "%2 %Fl%D%f%Ll[c]%E", - (i & 1) ? 'd' : 'a', i, - CO(SS.lightDir[i]), i, &ScreenChangeLightDirection, - SS.lightIntensity[i], i, &ScreenChangeLightIntensity); - } - - Printf(false, ""); - Printf(false, "%Ft chord tolerance (in screen pixels)%E"); - Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles", - SS.chordTol, - &ScreenChangeChordTolerance, 0, - SK.GetGroup(SS.GW.activeGroup)->displayMesh.l.n); - Printf(false, "%Ft max piecewise linear segments%E"); - Printf(false, "%Ba %d %Fl%Ll%f[change]%E", - SS.maxSegments, - &ScreenChangeMaxSegments); - - Printf(false, ""); - Printf(false, "%Ft perspective factor (0 for parallel)%E"); - 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)"); - Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", - (double)SS.exportScale, - &ScreenChangeExportScale, 0); - 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, ""); - Printf(false, "%Ft export shaded 2d triangles: " - "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", - &ScreenChangeShadedTriangles, - (SS.exportShadedTriangles ? "" : "yes"), - (SS.exportShadedTriangles ? "yes" : ""), - &ScreenChangeShadedTriangles, - (!SS.exportShadedTriangles ? "" : "no"), - (!SS.exportShadedTriangles ? "no" : "")); - if(fabs(SS.exportOffset) > LENGTH_EPS) { - 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", - &ScreenChangePwlCurves, - (SS.exportPwlCurves ? "" : "yes"), - (SS.exportPwlCurves ? "yes" : ""), - &ScreenChangePwlCurves, - (!SS.exportPwlCurves ? "" : "no"), - (!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 fix white exported lines: " - "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", - &ScreenChangeFixExportColors, - ( SS.fixExportColors ? "" : "yes"), ( SS.fixExportColors ? "yes" : ""), - &ScreenChangeFixExportColors, - (!SS.fixExportColors ? "" : "no"), (!SS.fixExportColors ? "no" : "")); - - Printf(false, "%Ft draw triangle back faces: " - "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", - &ScreenChangeBackFaces, - (SS.drawBackFaces ? "" : "yes"), (SS.drawBackFaces ? "yes" : ""), - &ScreenChangeBackFaces, - (!SS.drawBackFaces ? "" : "no"), (!SS.drawBackFaces ? "no" : "")); - - Printf(false, ""); - Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR)); - Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER)); - Printf(false, " %Ft version %E%s", glGetString(GL_VERSION)); -} - //----------------------------------------------------------------------------- // When we're stepping a dimension. User specifies the finish value, and // how many steps to take in between current and finish, re-solving each @@ -903,7 +665,6 @@ void TextWindow::ShowMeshVolume(void) { Printf(true, "%Fl%Ll%f(back)%E", &ScreenHome); } - //----------------------------------------------------------------------------- // The edit control is visible, and the user just pressed enter. //----------------------------------------------------------------------------- @@ -957,81 +718,6 @@ void TextWindow::EditControlDone(char *s) { } break; } - case EDIT_LIGHT_INTENSITY: - SS.lightIntensity[edit.i] = min(1, max(0, atof(s))); - InvalidateGraphics(); - break; - case EDIT_LIGHT_DIRECTION: { - double x, y, z; - if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) { - SS.lightDir[edit.i] = Vector::From(x, y, z); - } else { - Error("Bad format: specify coordinates as x, y, z"); - } - InvalidateGraphics(); - break; - } - case EDIT_COLOR: { - double r, g, b; - if(sscanf(s, "%lf, %lf, %lf", &r, &g, &b)==3) { - SS.modelColor[edit.i] = RGB(r*255, g*255, b*255); - } else { - Error("Bad format: specify color as r, g, b"); - } - break; - } - case EDIT_CHORD_TOLERANCE: { - SS.chordTol = min(10, max(0.1, atof(s))); - SS.GenerateAll(0, INT_MAX); - break; - } - case EDIT_MAX_SEGMENTS: { - SS.maxSegments = min(1000, max(7, atoi(s))); - SS.GenerateAll(0, INT_MAX); - break; - } - 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; - } - case EDIT_EXPORT_SCALE: { - Expr *e = Expr::From(s); - if(e) { - double ev = e->Eval(); - if(fabs(ev) < 0.001 || isnan(ev)) { - Error("Export scale must not be zero!"); - } else { - SS.exportScale = (float)ev; - } - } else { - Error("Not a valid number or expression: '%s'", s); - } - break; - } - case EDIT_EXPORT_OFFSET: { - Expr *e = Expr::From(s); - if(e) { - double ev = SS.ExprToMm(e); - 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: { @@ -1063,7 +749,6 @@ void TextWindow::EditControlDone(char *s) { } break; } - case EDIT_STEP_DIM_FINISH: { Expr *e = Expr::From(s); if(!e) { @@ -1077,35 +762,19 @@ void TextWindow::EditControlDone(char *s) { } break; } - 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; + default: { + bool st = EditControlDoneForStyles(s), + cf = EditControlDoneForConfiguration(s); + if(st && cf) { + // The identifiers were somehow assigned not uniquely? + oops(); } break; } - - default: - EditControlDoneForStyles(s); - break; } InvalidateGraphics(); SS.later.showTW = true; diff --git a/ui.h b/ui.h index 90f5f2b..0c94947 100644 --- a/ui.h +++ b/ui.h @@ -199,6 +199,7 @@ public: static void ScreenChangeBackgroundColor(int link, DWORD v); bool EditControlDoneForStyles(char *s); + bool EditControlDoneForConfiguration(char *s); void EditControlDone(char *s); }; diff --git a/wishlist.txt b/wishlist.txt index f71cd03..b3ec76c 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,5 +1,4 @@ -split draw.cpp multi-drag -----