diff --git a/constraint.cpp b/constraint.cpp index b5f76fb..f812d55 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -2,6 +2,8 @@ hConstraint Constraint::AddConstraint(Constraint *c) { SS.constraint.AddAndAssignId(c); + + if(SS.GW.solving == GraphicsWindow::SOLVE_ALWAYS) SS.Solve(); return c->h; } @@ -9,22 +11,24 @@ void Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { Constraint c; memset(&c, 0, sizeof(c)); c.group = SS.GW.activeGroup; + c.workplane = SS.GW.activeWorkplane; c.type = Constraint::POINTS_COINCIDENT; c.ptA = ptA; c.ptB = ptB; - SS.constraint.AddAndAssignId(&c); + AddConstraint(&c); } void Constraint::MenuConstrain(int id) { Constraint c; memset(&c, 0, sizeof(c)); c.group = SS.GW.activeGroup; + c.workplane = SS.GW.activeWorkplane; SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { - case GraphicsWindow::MNU_DISTANCE_DIA: + case GraphicsWindow::MNU_DISTANCE_DIA: { if(gs.points == 2 && gs.n == 2) { c.type = PT_PT_DISTANCE; c.ptA = gs.point[0]; @@ -38,11 +42,16 @@ void Constraint::MenuConstrain(int id) { Error("Bad selection for distance / diameter constraint."); return; } - c.disp.offset = Vector::MakeFrom(50, 50, 50); + Vector n = SS.GW.projRight.Cross(SS.GW.projUp); + Vector a = SS.GetEntity(c.ptA)->PointGetCoords(); + Vector b = SS.GetEntity(c.ptB)->PointGetCoords(); + + c.disp.offset = n.Cross(a.Minus(b)).WithMagnitude(50); c.exprA = Expr::FromString("0")->DeepCopyKeep(); c.ModifyToSatisfy(); AddConstraint(&c); break; + } case GraphicsWindow::MNU_ON_ENTITY: if(gs.points == 2 && gs.n == 2) { @@ -75,6 +84,10 @@ void Constraint::MenuConstrain(int id) { case GraphicsWindow::MNU_VERTICAL: case GraphicsWindow::MNU_HORIZONTAL: { hEntity ha, hb; + if(c.workplane.v == Entity::FREE_IN_3D.v) { + Error("Select workplane before constraining horiz/vert."); + return; + } if(gs.lineSegments == 1 && gs.n == 1) { c.entityA = gs.entity[0]; Entity *e = SS.GetEntity(c.entityA); @@ -87,24 +100,6 @@ void Constraint::MenuConstrain(int id) { Error("Bad selection for horizontal / vertical constraint."); return; } - Entity *ea = SS.GetEntity(ha); - Entity *eb = SS.GetEntity(hb); - if(ea->workplane.v == Entity::FREE_IN_3D.v && - eb->workplane.v == Entity::FREE_IN_3D.v) - { - Error("Horizontal/vertical constraint applies only to " - "entities drawn in a 2d coordinate system."); - return; - } - if(eb->workplane.v == SS.GW.activeWorkplane.v) { - // We are constraining two points in two different wrkpls; so - // we have two choices for the definitons of the coordinate - // directions. ptA's gets chosen, so make sure that's the - // active workplane. - hEntity t = c.ptA; - c.ptA = c.ptB; - c.ptB = t; - } if(id == GraphicsWindow::MNU_HORIZONTAL) { c.type = HORIZONTAL; } else { @@ -118,6 +113,15 @@ void Constraint::MenuConstrain(int id) { SS.Solve(); return; + case GraphicsWindow::MNU_SOLVE_AUTO: + if(SS.GW.solving == GraphicsWindow::SOLVE_ALWAYS) { + SS.GW.solving = GraphicsWindow::DONT_SOLVE; + } else { + SS.GW.solving = GraphicsWindow::SOLVE_ALWAYS; + } + SS.GW.EnsureValidActives(); + break; + default: oops(); } @@ -125,33 +129,31 @@ void Constraint::MenuConstrain(int id) { InvalidateGraphics(); } -Expr *Constraint::Distance(hEntity hpa, hEntity hpb) { +Expr *Constraint::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) { Entity *pa = SS.GetEntity(hpa); Entity *pb = SS.GetEntity(hpb); - if(!(pa->IsPoint() && pb->IsPoint())) oops(); - if(pa->type == Entity::POINT_IN_2D && - pb->type == Entity::POINT_IN_2D && - pa->workplane.v == pb->workplane.v) - { - // A nice case; they are both in the same workplane, so I can write - // the equation in terms of the basis vectors in that csys. - Expr *du = Expr::FromParam(pa->param[0])->Minus( - Expr::FromParam(pb->param[0])); - Expr *dv = Expr::FromParam(pa->param[1])->Minus( - Expr::FromParam(pb->param[1])); + if(wrkpl.v == Entity::FREE_IN_3D.v) { + // This is true distance + ExprVector ea, eb, eab; + ea = pa->PointGetExprs(); + eb = pb->PointGetExprs(); + eab = ea.Minus(eb); + + return eab.Magnitude(); + } else { + // This is projected distance, in the given workplane. + Expr *au, *av, *bu, *bv; + + pa->PointGetExprsInWorkplane(wrkpl, &au, &av); + pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv); + + Expr *du = au->Minus(bu); + Expr *dv = av->Minus(bv); return ((du->Square())->Plus(dv->Square()))->Sqrt(); } - - // The usual case, just write it in terms of the coordinates - ExprVector ea, eb, eab; - ea = pa->PointGetExprs(); - eb = pb->PointGetExprs(); - eab = ea.Minus(eb); - - return eab.Magnitude(); } void Constraint::ModifyToSatisfy(void) { @@ -182,59 +184,39 @@ void Constraint::AddEq(IdList *l, Expr *expr, int index) { void Constraint::Generate(IdList *l) { switch(type) { case PT_PT_DISTANCE: - AddEq(l, Distance(ptA, ptB)->Minus(exprA), 0); + AddEq(l, Distance(workplane, ptA, ptB)->Minus(exprA), 0); break; case EQUAL_LENGTH_LINES: { Entity *a = SS.GetEntity(entityA); Entity *b = SS.GetEntity(entityB); - AddEq(l, Distance(a->point[0], a->point[1])->Minus( - Distance(b->point[0], b->point[1])), 0); + AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus( + Distance(workplane, b->point[0], b->point[1])), 0); break; } case POINTS_COINCIDENT: { Entity *a = SS.GetEntity(ptA); Entity *b = SS.GetEntity(ptB); - if(!a->IsPointIn3d() && b->IsPointIn3d()) { - Entity *t = a; - a = b; b = t; - } - if(a->IsPointIn3d() && b->IsPointIn3d()) { - // Easy case: both points have 3 DOF, so write three eqs. - ExprVector ea, eb, eab; - ea = a->PointGetExprs(); - eb = b->PointGetExprs(); - eab = ea.Minus(eb); - AddEq(l, eab.x, 0); - AddEq(l, eab.y, 1); - AddEq(l, eab.z, 2); - } else if(!(a->IsPointIn3d() || b->IsPointIn3d()) && - (a->workplane.v == b->workplane.v)) - { - // Both in same workplane, nice. - AddEq(l, Expr::FromParam(a->param[0])->Minus( - Expr::FromParam(b->param[0])), 0); - AddEq(l, Expr::FromParam(a->param[1])->Minus( - Expr::FromParam(b->param[1])), 1); + if(workplane.v == Entity::FREE_IN_3D.v) { + ExprVector pa = a->PointGetExprs(); + ExprVector pb = b->PointGetExprs(); + AddEq(l, pa.x->Minus(pb.x), 0); + AddEq(l, pa.y->Minus(pb.y), 1); + AddEq(l, pa.z->Minus(pb.z), 2); } else { - // Either two 2 DOF points in different planes, or one - // 3 DOF point and one 2 DOF point. Either way, write two - // equations on the projection of a into b's plane. - ExprVector p3; - p3 = a->PointGetExprs(); - Entity *w = SS.GetEntity(b->workplane); - ExprVector offset = w->WorkplaneGetOffsetExprs(); - p3 = p3.Minus(offset); - ExprVector u, v; - w->WorkplaneGetBasisExprs(&u, &v); - AddEq(l, Expr::FromParam(b->param[0])->Minus(p3.Dot(u)), 0); - AddEq(l, Expr::FromParam(b->param[1])->Minus(p3.Dot(v)), 1); + Expr *au, *av; + Expr *bu, *bv; + a->PointGetExprsInWorkplane(workplane, &au, &av); + b->PointGetExprsInWorkplane(workplane, &bu, &bv); + AddEq(l, au->Minus(bu), 0); + AddEq(l, av->Minus(bv), 1); } break; } case PT_IN_PLANE: { + // This one works the same, whether projected or not. ExprVector p, n; Expr *d; p = SS.GetEntity(ptA)->PointGetExprs(); @@ -256,25 +238,12 @@ void Constraint::Generate(IdList *l) { } Entity *a = SS.GetEntity(ha); Entity *b = SS.GetEntity(hb); - if(a->workplane.v == Entity::FREE_IN_3D.v) { - Entity *t = a; - a = b; - b = t; - } - - if(a->workplane.v == b->workplane.v) { - int i = (type == HORIZONTAL) ? 1 : 0; - AddEq(l, Expr::FromParam(a->param[i])->Minus( - Expr::FromParam(b->param[i])), 0); - } else { - Entity *w = SS.GetEntity(a->workplane); - ExprVector u, v; - w->WorkplaneGetBasisExprs(&u, &v); - ExprVector norm = (type == HORIZONTAL) ? v : u; - ExprVector pa = a->PointGetExprs(); - ExprVector pb = b->PointGetExprs(); - AddEq(l, (pa.Minus(pb)).Dot(norm), 0); - } + + Expr *au, *av, *bu, *bv; + a->PointGetExprsInWorkplane(workplane, &au, &av); + b->PointGetExprsInWorkplane(workplane, &bu, &bv); + + AddEq(l, (type == HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0); break; } diff --git a/entity.cpp b/entity.cpp index 288df38..e94c4d4 100644 --- a/entity.cpp +++ b/entity.cpp @@ -196,6 +196,43 @@ ExprVector Entity::PointGetExprs(void) { return r; } +void Entity::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) { + if(type == POINT_IN_2D && workplane.v == wrkpl.v) { + // They want our coordinates in the form that we've written them, + // very nice. + *u = Expr::FromParam(param[0]); + *v = Expr::FromParam(param[1]); + } else { + // Get the offset and basis vectors for this weird exotic csys. + Entity *w = SS.GetEntity(wrkpl); + ExprVector wp = w->WorkplaneGetOffsetExprs(); + ExprVector wu, wv; + w->WorkplaneGetBasisExprs(&wu, &wv); + + // Get our coordinates in three-space, and project them into that + // coordinate system. + ExprVector ev = PointGetExprs(); + ev = ev.Minus(wp); + *u = ev.Dot(wu); + *v = ev.Dot(wv); + } +} + +bool Entity::PointIsLocked(void) { + // A point is locked if none of its coordinates get assumed. + if(type == POINT_IN_3D) { + if(SS.GetParam(param[0])->assumed) return false; + if(SS.GetParam(param[1])->assumed) return false; + if(SS.GetParam(param[2])->assumed) return false; + } else if(type == POINT_IN_2D) { + if(SS.GetParam(param[0])->assumed) return false; + if(SS.GetParam(param[1])->assumed) return false; + } else { + oops(); + } + return true; +} + void Entity::LineDrawOrGetDistance(Vector a, Vector b) { if(dogd.drawing) { glBegin(GL_LINE_STRIP); @@ -272,7 +309,9 @@ void Entity::DrawOrGetDistance(int order) { glEnd(); } else { Point2d pp = SS.GW.ProjectPoint(v); - dogd.dmin = pp.DistanceTo(dogd.mp) - 5; + // Make a free point slightly easier to select, so that with + // coincident points, we select the free one. + dogd.dmin = pp.DistanceTo(dogd.mp) - (PointIsLocked() ? 3 : 4); } break; } diff --git a/file.cpp b/file.cpp index 819e252..9b0d9ec 100644 --- a/file.cpp +++ b/file.cpp @@ -75,6 +75,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'c', "Constraint.h.v", 'x', &(SS.sv.c.h.v) }, { 'c', "Constraint.type", 'd', &(SS.sv.c.type) }, { 'c', "Constraint.group.v", 'x', &(SS.sv.c.group.v) }, + { 'c', "Constraint.workplane.v", 'x', &(SS.sv.c.workplane.v) }, { 'c', "Constraint.exprA", 'E', &(SS.sv.c.exprA) }, { 'c', "Constraint.exprB", 'E', &(SS.sv.c.exprB) }, { 'c', "Constraint.ptA.v", 'x', &(SS.sv.c.ptA.v) }, diff --git a/graphicswin.cpp b/graphicswin.cpp index f2c6e38..57ac95d 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -31,6 +31,8 @@ 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 Text &Window\tTab", MNU_SHOW_TEXT_WND, '\t', mView }, +{ 1, NULL, 0, NULL }, { 1, "Dimensions in &Inches", MNU_UNITS_INCHES, 0, mView }, { 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView }, @@ -80,6 +82,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, NULL, 0, NULL }, { 1, "Sym&bolic Equation\tShift+B", 0, 'B'|S, NULL }, { 1, NULL, 0, NULL }, +{ 1, "Sol&ve Automatically\tShift+Tab", MNU_SOLVE_AUTO, '\t'|S, mCon }, { 1, "Solve Once Now\tSpace", MNU_SOLVE_NOW, ' ', mCon }, { 0, "&Help", 0, NULL }, @@ -106,6 +109,11 @@ void GraphicsWindow::Init(void) { showPoints = true; showAllGroups = true; showConstraints = true; + + solving = SOLVE_ALWAYS; + + showTextWindow = true; + ShowTextWindow(showTextWindow); } void GraphicsWindow::NormalizeProjectionVectors(void) { @@ -174,6 +182,11 @@ void GraphicsWindow::MenuView(int id) { case MNU_ZOOM_TO_FIT: break; + case MNU_SHOW_TEXT_WND: + SS.GW.showTextWindow = !SS.GW.showTextWindow; + SS.GW.EnsureValidActives(); + break; + case MNU_UNITS_MM: SS.GW.viewUnits = UNIT_MM; SS.GW.EnsureValidActives(); @@ -228,6 +241,11 @@ void GraphicsWindow::EnsureValidActives(void) { } CheckMenuById(MNU_UNITS_MM, viewUnits == UNIT_MM); CheckMenuById(MNU_UNITS_INCHES, viewUnits == UNIT_INCHES); + + ShowTextWindow(SS.GW.showTextWindow); + CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow); + + CheckMenuById(MNU_SOLVE_AUTO, (SS.GW.solving == SOLVE_ALWAYS)); } void GraphicsWindow::MenuEdit(int id) { @@ -415,7 +433,9 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, UpdateDraggedPoint(&(c->disp.offset), x, y); } else if(leftDown && pendingOperation == DRAGGING_POINT) { UpdateDraggedEntity(pendingPoint, x, y); - HitTestMakeSelection(mp); + if(solving == SOLVE_ALWAYS) { + SS.Solve(); + } } // No buttons pressed. @@ -543,6 +563,9 @@ hRequest GraphicsWindow::AddRequest(int type) { r.type = type; SS.request.AddAndAssignId(&r); SS.GenerateAll(); + + if(solving == SOLVE_ALWAYS) SS.Solve(); + return r.h; } @@ -707,6 +730,7 @@ void GraphicsWindow::EditControlDone(char *s) { Expr::FreeKeep(&(c->exprA)); c->exprA = e->DeepCopyKeep(); HideGraphicsEditControl(); + if(SS.GW.solving == SOLVE_ALWAYS) SS.Solve(); } else { Error("Not a valid number or expression: '%s'", s); } diff --git a/sketch.h b/sketch.h index 246dc28..860dabc 100644 --- a/sketch.h +++ b/sketch.h @@ -167,8 +167,10 @@ public: bool IsPoint(void); bool IsPointIn3d(void); // Applies for any of the point types + bool PointIsLocked(void); Vector PointGetCoords(void); ExprVector PointGetExprs(void); + void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v); void PointForceTo(Vector v); bool PointIsFromReferences(void); bool PointIsKnown(void); @@ -204,6 +206,7 @@ public: double val; bool known; + bool assumed; }; @@ -287,7 +290,7 @@ public: // Some helpers when generating symbolic constraint equations void ModifyToSatisfy(void); void AddEq(IdList *l, Expr *expr, int index); - static Expr *Distance(hEntity pa, hEntity pb); + static Expr *Distance(hEntity workplane, hEntity pa, hEntity pb); static void ConstrainCoincident(hEntity ptA, hEntity ptB); }; diff --git a/solvespace.cpp b/solvespace.cpp index 60b8d71..b054275 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -20,6 +20,7 @@ void SolveSpace::Init(char *cmdLine) { void SolveSpace::GenerateAll(void) { int i; + // Don't lose our numerical guesses when we regenerate. IdList prev; param.MoveSelfInto(&prev); @@ -28,10 +29,12 @@ void SolveSpace::GenerateAll(void) { request.elem[i].Generate(&entity, ¶m); } + // Restore the numerical guesses. for(i = 0; i < param.n; i++) { Param *p = prev.FindByIdNoOops(param.elem[i].h); if(p) { param.elem[i].val = p->val; + param.elem[i].assumed = p->assumed; } } diff --git a/solvespace.h b/solvespace.h index 3be68d8..729b7e4 100644 --- a/solvespace.h +++ b/solvespace.h @@ -40,10 +40,11 @@ void ShowGraphicsEditControl(int x, int y, char *s); void HideGraphicsEditControl(void); BOOL GraphicsEditControlIsVisible(void); -void InvalidateGraphics(void); +void ShowTextWindow(BOOL visible); void InvalidateText(void); -SDWORD GetMilliseconds(void); +void InvalidateGraphics(void); void PaintGraphics(void); +SDWORD GetMilliseconds(void); void dbp(char *str, ...); void Error(char *str, ...); diff --git a/system.cpp b/system.cpp index 1279d49..206e310 100644 --- a/system.cpp +++ b/system.cpp @@ -181,9 +181,6 @@ bool System::NewtonSolve(int tag) { // Take the Newton step; // J(x_n) (x_{n+1} - x_n) = 0 - F(x_n) for(i = 0; i < mat.m; i++) { - dbp("mat.X[%d] = %.3f", i, mat.X[i]); - dbp("modifying param %08x, now %.3f", mat.param[i], - param.FindById(mat.param[i])->val); (param.FindById(mat.param[i]))->val -= mat.X[i]; } @@ -210,10 +207,10 @@ bool System::NewtonSolve(int tag) { bool System::Solve(void) { int i, j; - dbp("%d equations", eq.n); +/* dbp("%d equations", eq.n); for(i = 0; i < eq.n; i++) { dbp(" %s = 0", eq.elem[i].e->Print()); - } + } */ param.ClearTags(); eq.ClearTags(); @@ -221,23 +218,30 @@ bool System::Solve(void) { WriteJacobian(0, 0); EvalJacobian(); +/* for(i = 0; i < mat.m; i++) { for(j = 0; j < mat.n; j++) { dbp("A[%d][%d] = %.3f", i, j, mat.A.num[i][j]); } - } + } */ GaussJordan(); - dbp("bound states:"); +/* dbp("bound states:"); for(j = 0; j < mat.n; j++) { dbp(" param %08x: %d", mat.param[j], mat.bound[j]); - } + } */ // Fix any still-free variables wherever they are now. for(j = 0; j < mat.n; j++) { if(mat.bound[j]) continue; - param.FindById(mat.param[j])->tag = ASSUMED; + Param *p = param.FindByIdNoOops(mat.param[j]); + if(!p) { + // This is parameter does not occur in this group, so it's + // not available to assume. + continue; + } + p->tag = ASSUMED; } bool ok = NewtonSolve(0); @@ -250,6 +254,9 @@ bool System::Solve(void) { Param *pp = SS.GetParam(p->h); pp->val = p->val; pp->known = true; + // The main param table keeps track of what was assumed, to + // choose which point to drag so that it actually moves. + pp->assumed = (p->tag == ASSUMED); } } diff --git a/ui.h b/ui.h index fb4bf26..08ab3b5 100644 --- a/ui.h +++ b/ui.h @@ -4,8 +4,8 @@ class TextWindow { public: - static const int MAX_COLS = 150; - static const int MAX_ROWS = 300; + static const int MAX_COLS = 100; + static const int MAX_ROWS = 200; #ifndef RGB #define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) @@ -87,11 +87,12 @@ public: MNU_ZOOM_IN, MNU_ZOOM_OUT, MNU_ZOOM_TO_FIT, - MNU_UNSELECT_ALL, + MNU_SHOW_TEXT_WND, MNU_UNITS_INCHES, MNU_UNITS_MM, // Edit MNU_DELETE, + MNU_UNSELECT_ALL, // Request MNU_SEL_WORKPLANE, MNU_FREE_IN_3D, @@ -105,6 +106,7 @@ public: MNU_ON_ENTITY, MNU_HORIZONTAL, MNU_VERTICAL, + MNU_SOLVE_AUTO, MNU_SOLVE_NOW, } MenuId; typedef void MenuHandler(int id); @@ -201,9 +203,14 @@ public: bool showPoints; bool showAllGroups; bool showConstraints; + bool showTextWindow; static void ToggleBool(int link, DWORD v); static void ToggleAnyDatumShown(int link, DWORD v); + static const int DONT_SOLVE = 0; + static const int SOLVE_ALWAYS = 1; + int solving; + void UpdateDraggedPoint(Vector *pos, double mx, double my); void UpdateDraggedEntity(hEntity hp, double mx, double my); diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 80c6bba..dbe6a82 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -332,6 +332,7 @@ static BOOL ProcessKeyDown(WPARAM wParam) case VK_OEM_5: c = '\\'; break; case VK_SPACE: c = ' '; break; case VK_DELETE: c = 127; break; + case VK_TAB: c = '\t'; break; default: c = wParam; @@ -351,6 +352,11 @@ static BOOL ProcessKeyDown(WPARAM wParam) return FALSE; } +void ShowTextWindow(BOOL visible) +{ + ShowWindow(TextWnd, visible ? SW_SHOWNOACTIVATE : SW_HIDE); +} + static HGLRC CreateGlContext(HDC hdc) { PIXELFORMATDESCRIPTOR pfd;