Improve convergence of constraints involving parallel vectors, by

using the initial numerical guess of whichever vector is already
known to choose our projection planes.

And add a mechanism to defer showing the text window or
regenerating, in order to simplify dependencies on valid actives.
Also yes/no/cancel when about to abandon an unsaved file, and a
bugfix when rotating a rot/trans point.

[git-p4: depot-paths = "//depot/solvespace/": change = 1772]
solver
Jonathan Westhues 2008-06-03 10:28:41 -08:00
parent 7d4a4fbb76
commit ea6db67c7d
10 changed files with 190 additions and 70 deletions

View File

@ -39,7 +39,7 @@ void Constraint::AddConstraint(Constraint *c) {
SS.constraint.AddAndAssignId(c);
SS.MarkGroupDirty(c->group);
SS.GenerateAll();
SS.later.generateAll = true;
}
void Constraint::Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA)
@ -323,10 +323,13 @@ void Constraint::MenuConstrain(int id) {
Expr *Constraint::VectorsParallel(int eq, ExprVector a, ExprVector b) {
ExprVector r = a.Cross(b);
// Hairy ball theorem screws me here. There's no clean solution that I
// know, so let's pivot on the initial numerical guess.
double mx = fabs((a.x)->Eval()) + fabs((b.x)->Eval());
double my = fabs((a.y)->Eval()) + fabs((b.y)->Eval());
double mz = fabs((a.z)->Eval()) + fabs((b.z)->Eval());
// know, so let's pivot on the initial numerical guess. Our caller
// has ensured that if one of our input vectors is already known (e.g.
// it's from a previous group), then that one's in a; so that one's
// not going to move, and we should pivot on that one.
double mx = fabs((a.x)->Eval());
double my = fabs((a.y)->Eval());
double mz = fabs((a.z)->Eval());
// The basis vector in which the vectors have the LEAST energy is the
// one that we should look at most (e.g. if both vectors lie in the xy
// plane, then the z component of the cross product is most important).
@ -566,8 +569,13 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
ExprVector eab = ea.Minus(eb);
ExprVector eap = ea.Minus(ep);
AddEq(l, VectorsParallel(0, eab, eap), 0);
AddEq(l, VectorsParallel(1, eab, eap), 1);
if(p->group.v == group.v) {
AddEq(l, VectorsParallel(0, eab, eap), 0);
AddEq(l, VectorsParallel(1, eab, eap), 1);
} else {
AddEq(l, VectorsParallel(0, eap, eab), 0);
AddEq(l, VectorsParallel(1, eap, eab), 1);
}
} else {
AddEq(l, PointLineDistance(workplane, ptA, entityA), 0);
}
@ -715,6 +723,10 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
case SAME_ORIENTATION: {
Entity *a = SS.GetEntity(entityA);
Entity *b = SS.GetEntity(entityB);
if(b->group.v != group.v) {
SWAP(Entity *, a, b);
}
ExprVector au = a->NormalExprsU(),
av = a->NormalExprsV(),
an = a->NormalExprsN();
@ -765,8 +777,12 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
}
case PARALLEL: {
ExprVector a = SS.GetEntity(entityA)->VectorGetExprs();
ExprVector b = SS.GetEntity(entityB)->VectorGetExprs();
Entity *ea = SS.GetEntity(entityA), *eb = SS.GetEntity(entityB);
if(eb->group.v != group.v) {
SWAP(Entity *, ea, eb);
}
ExprVector a = ea->VectorGetExprs();
ExprVector b = eb->VectorGetExprs();
if(workplane.v == Entity::FREE_IN_3D.v) {
AddEq(l, VectorsParallel(0, a, b), 0);

View File

@ -295,7 +295,7 @@ void GraphicsWindow::EnsureValidActives(void) {
ShowTextWindow(SS.GW.showTextWindow);
CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow);
if(change) SS.TW.Show();
if(change) SS.later.showTW = true;
}
void GraphicsWindow::SetWorkplaneFreeIn3d(void) {
@ -356,7 +356,7 @@ void GraphicsWindow::MenuEdit(int id) {
// that references it (since the regen code checks for that).
SS.GenerateAll(0, INT_MAX);
SS.GW.EnsureValidActives();
SS.TW.Show();
SS.later.showTW = true;
break;
}
@ -382,13 +382,13 @@ void GraphicsWindow::MenuRequest(int id) {
// Align the view with the selected workplane
SS.GW.AnimateOntoWorkplane();
SS.GW.ClearSuper();
SS.TW.Show();
SS.later.showTW = true;
break;
}
case MNU_FREE_IN_3D:
SS.GW.SetWorkplaneFreeIn3d();
SS.GW.EnsureValidActives();
SS.TW.Show();
SS.later.showTW = true;
break;
case MNU_DATUM_POINT: s = "click to place datum point"; goto c;
@ -401,7 +401,7 @@ void GraphicsWindow::MenuRequest(int id) {
c:
SS.GW.pending.operation = id;
SS.GW.pending.description = s;
SS.TW.Show();
SS.later.showTW = true;
break;
case MNU_CONSTRUCTION: {
@ -578,10 +578,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
double dx = -(x - orig.mouse.x);
double dy = -(y - orig.mouse.y);
double s = 0.3*(PI/180); // degrees per pixel
u = u.RotatedAbout(orig.projUp, -s*dx);
u = u.RotatedAbout(orig.projRight, s*dy);
v = v.RotatedAbout(orig.projUp, -s*dx);
v = v.RotatedAbout(orig.projRight, s*dy);
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);
@ -779,7 +779,7 @@ void GraphicsWindow::ClearSelection(void) {
for(int i = 0; i < MAX_SELECTED; i++) {
selection[i].Clear();
}
SS.TW.Show();
SS.later.showTW = true;
InvalidateGraphics();
}
@ -1101,7 +1101,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
}
}
SS.TW.Show();
SS.later.showTW = true;
InvalidateGraphics();
}
@ -1180,7 +1180,7 @@ void GraphicsWindow::ToggleBool(int link, DWORD v) {
SS.GenerateAll();
InvalidateGraphics();
SS.TW.Show();
SS.later.showTW = true;
}
Vector GraphicsWindow::VectorFromProjs(double right, double up, double fwd) {

View File

@ -144,7 +144,7 @@ void Group::MenuGroup(int id) {
SS.GetGroup(g.h)->Activate();
SS.GW.AnimateOntoWorkplane();
TextWindow::ScreenSelectGroup(0, g.h.v);
SS.TW.Show();
SS.later.showTW = true;
}
char *Group::DescriptionString(void) {
@ -164,8 +164,8 @@ void Group::Activate(void) {
SS.GW.showFaces = false;
}
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
SS.GenerateAll();
SS.TW.Show();
SS.later.generateAll = true;
SS.later.showTW = true;
}
void Group::Generate(IdList<Entity,hEntity> *entity,
@ -740,8 +740,10 @@ void Group::GenerateMesh(void) {
SMesh *a = PreviousGroupMesh();
if(meshCombine == COMBINE_AS_UNION) {
mesh.MakeFromUnion(a, &outm);
} else {
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
mesh.MakeFromDifference(a, &outm);
} else {
}
outm.Clear();
}

View File

@ -127,6 +127,7 @@ public:
static const int COMBINE_AS_UNION = 0;
static const int COMBINE_AS_DIFFERENCE = 1;
static const int COMBINE_AS_ASSEMBLE = 2;
int meshCombine;
IdList<EntityMap,EntityId> remap;

View File

@ -17,6 +17,12 @@ void SolveSpace::Init(char *cmdLine) {
AfterNewFile();
}
void SolveSpace::DoLater(void) {
if(later.generateAll) GenerateAll();
if(later.showTW) TW.Show();
ZERO(&later);
}
void SolveSpace::AfterNewFile(void) {
ReloadAllImported();
GenerateAll(-1, -1);
@ -24,8 +30,9 @@ void SolveSpace::AfterNewFile(void) {
TW.Init();
GW.Init();
unsaved = false;
GenerateAll(0, INT_MAX);
TW.Show();
later.showTW = true;
}
void SolveSpace::MarkGroupDirtyByEntity(hEntity he) {
@ -256,7 +263,7 @@ void SolveSpace::GenerateAll(int first, int last) {
if(deleted.groups > 0) {
SS.TW.ClearSuper();
}
TW.Show();
later.showTW = true;
GW.ClearSuper();
// Don't display any errors until we've regenerated fully. The
// sketch is not necessarily in a consistent state until we've
@ -365,6 +372,40 @@ void SolveSpace::AddToRecentList(char *file) {
RefreshRecentMenus();
}
bool SolveSpace::GetFilenameAndSave(bool saveAs) {
char newFile[MAX_PATH];
strcpy(newFile, saveFile);
if(saveAs || strlen(newFile)==0) {
if(!GetSaveFile(newFile, SLVS_EXT, SLVS_PATTERN)) return false;
}
if(SaveToFile(newFile)) {
AddToRecentList(newFile);
strcpy(saveFile, newFile);
unsaved = false;
return true;
} else {
return false;
}
}
bool SolveSpace::OkayToStartNewFile(void) {
if(!unsaved) return true;
switch(SaveFileYesNoCancel()) {
case IDYES:
return GetFilenameAndSave(false);
case IDNO:
return true;
case IDCANCEL:
return false;
default: oops();
}
}
void SolveSpace::MenuFile(int id) {
if(id >= RECENT_OPEN && id < (RECENT_OPEN+MAX_RECENT)) {
@ -384,12 +425,16 @@ void SolveSpace::MenuFile(int id) {
switch(id) {
case GraphicsWindow::MNU_NEW:
if(!SS.OkayToStartNewFile()) break;
strcpy(SS.saveFile, "");
SS.NewFile();
SS.AfterNewFile();
break;
case GraphicsWindow::MNU_OPEN: {
if(!SS.OkayToStartNewFile()) break;
char newFile[MAX_PATH] = "";
if(GetOpenFile(newFile, SLVS_EXT, SLVS_PATTERN)) {
if(SS.LoadFromFile(newFile)) {
@ -405,21 +450,16 @@ void SolveSpace::MenuFile(int id) {
}
case GraphicsWindow::MNU_SAVE:
case GraphicsWindow::MNU_SAVE_AS: {
char newFile[MAX_PATH];
strcpy(newFile, SS.saveFile);
if(id == GraphicsWindow::MNU_SAVE_AS || strlen(newFile)==0) {
if(!GetSaveFile(newFile, SLVS_EXT, SLVS_PATTERN)) break;
}
if(SS.SaveToFile(newFile)) {
AddToRecentList(newFile);
strcpy(SS.saveFile, newFile);
}
SS.GetFilenameAndSave(false);
break;
case GraphicsWindow::MNU_SAVE_AS:
SS.GetFilenameAndSave(true);
break;
}
case GraphicsWindow::MNU_EXIT:
if(!SS.OkayToStartNewFile()) break;
exit(0);
break;
default: oops();

View File

@ -12,6 +12,8 @@
#define max(x, y) ((x) > (y) ? (x) : (y))
#endif
#define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11))
inline int WRAP(int v, int n) {
while(v >= n) v -= n;
while(v < 0) v += n;
@ -215,8 +217,6 @@ public:
inline Param *GetParam (hParam h) { return param. FindById(h); }
inline Group *GetGroup (hGroup h) { return group. FindById(h); }
hGroup activeGroup;
FILE *fh;
void Init(char *cmdLine);
@ -243,6 +243,8 @@ public:
Constraint c;
} sv;
static void MenuFile(int id);
bool GetFilenameAndSave(bool saveAs);
bool OkayToStartNewFile(void);
hGroup CreateDefaultDrawingGroup(void);
void NewFile(void);
bool SaveToFile(char *filename);
@ -273,6 +275,12 @@ public:
// The system to be solved.
System sys;
struct {
bool showTW;
bool generateAll;
} later;
void DoLater(void);
};
extern SolveSpace SS;

View File

@ -332,7 +332,12 @@ 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.n; i++) {
(param.FindById(mat.param[i]))->val -= mat.X[i];
Param *p = param.FindById(mat.param[i]);
p->val -= mat.X[i];
if(isnan(p->val)) {
// Very bad, and clearly not convergent
return false;
}
}
// Re-evalute the functions, since the params have just changed.
@ -342,6 +347,9 @@ bool System::NewtonSolve(int tag) {
// Check for convergence
converged = true;
for(i = 0; i < mat.m; i++) {
if(isnan(mat.B.num[i])) {
return false;
}
if(fabs(mat.B.num[i]) > 1e-10) {
converged = false;
break;
@ -459,7 +467,6 @@ void System::Solve(Group *g) {
} else {
val = p->val;
}
Param *pp = SS.GetParam(p->h);
pp->val = val;
pp->known = true;

View File

@ -458,7 +458,7 @@ void TextWindow::ReportHowGroupSolved(hGroup hg) {
SS.GW.ClearSuper();
SS.TW.OneScreenForwardTo(SCREEN_GROUP_SOLVE_INFO);
SS.TW.shown->group.v = hg.v;
SS.TW.Show();
SS.later.showTW = true;
}
void TextWindow::ScreenHowGroupSolved(int link, DWORD v) {
if(SS.GW.activeGroup.v != v) {
@ -470,6 +470,7 @@ void TextWindow::ScreenHowGroupSolved(int link, DWORD v) {
void TextWindow::ShowListOfGroups(void) {
Printf(true, "%Ftactv show ok group-name%E");
int i;
bool afterActive = false;
for(i = 0; i < SS.group.n; i++) {
Group *g = &(SS.group.elem[i]);
char *s = g->DescriptionString();
@ -479,7 +480,7 @@ void TextWindow::ShowListOfGroups(void) {
bool ref = (g->h.v == Group::HGROUP_REFERENCES.v);
Printf(false, "%Bp%Fd "
"%Fp%D%f%s%Ll%s%E%s "
"%Fp%D%f%Ll%s%E%s "
"%Fp%D%f%Ll%s%E%Fh%s%E "
"%Fp%D%f%s%Ll%s%E "
"%Fl%Ll%D%f%s",
// Alternate between light and dark backgrounds, for readability
@ -491,14 +492,16 @@ void TextWindow::ShowListOfGroups(void) {
active ? "" : " ",
// Link that hides or shows the group
shown ? 's' : 'h', g->h.v, (&TextWindow::ScreenToggleGroupShown),
shown ? "yes" : "no",
shown ? "" : " ",
afterActive ? "" : (shown ? "yes" : "no"),
afterActive ? " - " : (shown ? "" : " "),
// Link to the errors, if a problem occured while solving
ok ? 's' : 'x', g->h.v, (&TextWindow::ScreenHowGroupSolved),
ok ? "ok" : "",
ok ? "" : "NO",
// Link to a screen that gives more details on the group
g->h.v, (&TextWindow::ScreenSelectGroup), s);
if(active) afterActive = true;
}
Printf(true, " %Fl%Ls%fshow all groups before active%E",
@ -509,8 +512,16 @@ void TextWindow::ShowListOfGroups(void) {
void TextWindow::ScreenHoverConstraint(int link, DWORD v) {
if(!SS.GW.showConstraints) return;
hConstraint hc = { v };
Constraint *c = SS.GetConstraint(hc);
if(c->group.v != SS.GW.activeGroup.v) {
// Only constraints in the active group are visible
return;
}
SS.GW.hover.Clear();
SS.GW.hover.constraint.v = v;
SS.GW.hover.constraint = hc;
SS.GW.hover.emphasized = true;
}
void TextWindow::ScreenHoverRequest(int link, DWORD v) {
@ -541,11 +552,7 @@ void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) {
}
void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) {
Group *g = SS.GetGroup(SS.TW.shown->group);
if(g->meshCombine == Group::COMBINE_AS_DIFFERENCE) {
g->meshCombine = Group::COMBINE_AS_UNION;
} else if(g->meshCombine == Group::COMBINE_AS_UNION) {
g->meshCombine = Group::COMBINE_AS_DIFFERENCE;
} else oops();
g->meshCombine = v;
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
@ -566,7 +573,7 @@ void TextWindow::ScreenChangeExprA(int link, DWORD v) {
}
void TextWindow::ScreenChangeGroupName(int link, DWORD v) {
Group *g = SS.GetGroup(SS.TW.shown->group);
ShowTextEditControl(7, 13, g->DescriptionString()+5);
ShowTextEditControl(7, 14, g->DescriptionString()+5);
SS.TW.edit.meaning = EDIT_GROUP_NAME;
SS.TW.edit.group.v = v;
}
@ -588,9 +595,9 @@ void TextWindow::ShowGroupInfo(void) {
char *s, *s2;
if(shown->group.v == Group::HGROUP_REFERENCES.v) {
Printf(true, "%FtGROUP %E%s", g->DescriptionString());
Printf(true, "%FtGROUP %E%s", g->DescriptionString());
} else {
Printf(true, "%FtGROUP %E%s "
Printf(true, "%FtGROUP %E%s "
"(%Fl%Ll%D%frename%E / %Fl%Ll%D%fdel%E)",
g->DescriptionString(),
g->h.v, &TextWindow::ScreenChangeGroupName,
@ -598,17 +605,17 @@ void TextWindow::ShowGroupInfo(void) {
}
if(g->type == Group::IMPORTED) {
Printf(true, "%FtIMPORT %E '%s'", g->impFile);
Printf(true, "%FtIMPORT%E '%s'", g->impFile);
}
if(g->type == Group::EXTRUDE) {
s = "EXTRUDE";
s = "EXTRUDE ";
} else if(g->type == Group::TRANSLATE) {
s = "TRANSLATE";
s2 ="REPEAT ";
} else if(g->type == Group::ROTATE) {
s = "ROTATE";
s2 ="REPEAT ";
s2 ="REPEAT";
}
if(g->type == Group::EXTRUDE || g->type == Group::ROTATE ||
@ -621,24 +628,38 @@ void TextWindow::ShowGroupInfo(void) {
&TextWindow::ScreenChangeOneOrTwoSides,
(!one ? "" : "two sides"), (!one ? "two sides" : ""));
}
if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) {
int times = (int)(g->exprA->Eval());
Printf(true, "%Ft%s%E %d time%s %Fl%Ll%D%f(change)%E",
s2, times, times == 1 ? "" : "s",
g->h.v, &TextWindow::ScreenChangeExprA);
}
if(g->type == Group::EXTRUDE) {
if(g->type == Group::EXTRUDE || g->type == Group::IMPORTED) {
bool un = (g->meshCombine == Group::COMBINE_AS_UNION);
bool diff = (g->meshCombine == Group::COMBINE_AS_DIFFERENCE);
Printf(false, "%FtCOMBINE%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
bool asy = (g->meshCombine == Group::COMBINE_AS_ASSEMBLE);
bool asa = (g->type == Group::IMPORTED);
Printf(false,
"%FtMERGE AS%E %Fh%f%D%Ll%s%E%Fs%s%E / %Fh%f%D%Ll%s%E%Fs%s%E %s "
"%Fh%f%D%Ll%s%E%Fs%s%E",
&TextWindow::ScreenChangeMeshCombine,
(!diff ? "" : "as union"), (!diff ? "as union" : ""),
Group::COMBINE_AS_UNION,
(un ? "" : "union"), (un ? "union" : ""),
&TextWindow::ScreenChangeMeshCombine,
(diff ? "" : "as difference"), (diff ? "as difference" : ""));
Group::COMBINE_AS_DIFFERENCE,
(diff ? "" : "difference"), (diff ? "difference" : ""),
asa ? "/" : "",
&TextWindow::ScreenChangeMeshCombine,
Group::COMBINE_AS_ASSEMBLE,
(asy || !asa ? "" : "assemble"), (asy && asa ? "assemble" : ""));
}
if(g->type == Group::EXTRUDE) {
#define TWOX(v) v v
Printf(true, "%FtMCOLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))),
Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))),
0x80000000 | modelColor[0], 0, &TextWindow::ScreenColor,
0x80000000 | modelColor[1], 1, &TextWindow::ScreenColor,
0x80000000 | modelColor[2], 2, &TextWindow::ScreenColor,
@ -744,8 +765,8 @@ void TextWindow::EditControlDone(char *s) {
g->exprA = e->DeepCopyKeep();
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.TW.Show();
SS.later.generateAll = true;
SS.later.showTW = true;
} else {
Error("Not a valid number or expression: '%s'", s);
}
@ -765,7 +786,8 @@ void TextWindow::EditControlDone(char *s) {
Group *g = SS.GetGroup(edit.group);
g->name.strcpy(s);
}
SS.TW.Show();
SS.later.showTW = true;
SS.unsaved = true;
break;
}
}

View File

@ -263,7 +263,7 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
SolveSpace::MenuFile(GraphicsWindow::MNU_EXIT);
break;
case WM_PAINT: {
@ -642,7 +642,7 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
SolveSpace::MenuFile(GraphicsWindow::MNU_EXIT);
return 1;
default:
@ -916,10 +916,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
DWORD ret;
while(ret = GetMessage(&msg, NULL, 0, 0)) {
if(msg.message == WM_KEYDOWN) {
if(ProcessKeyDown(msg.wParam)) continue;
if(ProcessKeyDown(msg.wParam)) goto done;
}
if(msg.message == WM_SYSKEYDOWN && msg.hwnd == TextWnd) {
// If the user presses the Alt key when the text window has focus,
// then that should probably go to the graphics window instead.
SetForegroundWindow(GraphicsWnd);
}
TranslateMessage(&msg);
DispatchMessage(&msg);
done:
SS.DoLater();
}
// Write everything back to the registry

17
wishlist.txt Normal file
View File

@ -0,0 +1,17 @@
STL check for meshes, and T intersection removal
STL export
better triangle combining (Simplify()) for meshes
solids of revolution
investigate slowdown with large number of entities
DXF export
compress file format (binary?)
partitioned subsystems in the solver
arbitrary color specification
union/difference/interference check option for imports
undo/redo
TTF font text
display with proper formatting/units
more measurements