From ea6db67c7d9bb5dea693d74f92ab1825ace4e4ea Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 3 Jun 2008 10:28:41 -0800 Subject: [PATCH] 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] --- constraint.cpp | 34 +++++++++++++++++------ graphicswin.cpp | 24 ++++++++-------- sketch.cpp | 10 ++++--- sketch.h | 1 + solvespace.cpp | 68 +++++++++++++++++++++++++++++++++++---------- solvespace.h | 12 ++++++-- system.cpp | 11 ++++++-- textwin.cpp | 70 +++++++++++++++++++++++++++++++---------------- win32/w32main.cpp | 13 +++++++-- wishlist.txt | 17 ++++++++++++ 10 files changed, 190 insertions(+), 70 deletions(-) create mode 100644 wishlist.txt diff --git a/constraint.cpp b/constraint.cpp index 33310f2..45d4229 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -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 *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 *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 *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); diff --git a/graphicswin.cpp b/graphicswin.cpp index e197ae7..b6d98db 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -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) { diff --git a/sketch.cpp b/sketch.cpp index 82472a5..c590427 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -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, @@ -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(); } diff --git a/sketch.h b/sketch.h index a456bd3..4527c31 100644 --- a/sketch.h +++ b/sketch.h @@ -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 remap; diff --git a/solvespace.cpp b/solvespace.cpp index dae6d47..bb960f0 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -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(); diff --git a/solvespace.h b/solvespace.h index e3d3abd..121801d 100644 --- a/solvespace.h +++ b/solvespace.h @@ -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; diff --git a/system.cpp b/system.cpp index ee8b6f7..9f97360 100644 --- a/system.cpp +++ b/system.cpp @@ -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; diff --git a/textwin.cpp b/textwin.cpp index 12eedcf..b93fff5 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -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; } } diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 36a6218..024e076 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -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 diff --git a/wishlist.txt b/wishlist.txt new file mode 100644 index 0000000..65cf718 --- /dev/null +++ b/wishlist.txt @@ -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 + +