Standardize the behaviour of constraints, by assigning them a

workplane: a free constraint works in three-space (e.g. true
distance), and a constraint in a workplane works in that plane
(e.g. projected distance). And make the solver go automatically,
though solver itself has lots of pieces missing.

[git-p4: depot-paths = "//depot/solvespace/": change = 1691]
This commit is contained in:
Jonathan Westhues 2008-04-26 21:00:12 -08:00
parent 15476d4732
commit bfc7109e0c
10 changed files with 173 additions and 113 deletions

View File

@ -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<Equation,hEquation> *l, Expr *expr, int index) {
void Constraint::Generate(IdList<Equation,hEquation> *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<Equation,hEquation> *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;
}

View File

@ -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;
}

View File

@ -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) },

View File

@ -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);
}

View File

@ -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<Equation,hEquation> *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);
};

View File

@ -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<Param,hParam> prev;
param.MoveSelfInto(&prev);
@ -28,10 +29,12 @@ void SolveSpace::GenerateAll(void) {
request.elem[i].Generate(&entity, &param);
}
// 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;
}
}

View File

@ -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, ...);

View File

@ -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);
}
}

13
ui.h
View File

@ -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);

View File

@ -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;