diff --git a/Makefile b/Makefile index dedf65ed..d2ee419f 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\constraint.obj \ $(OBJDIR)\drawconstraint.obj \ $(OBJDIR)\file.obj \ + $(OBJDIR)\undoredo.obj \ $(OBJDIR)\system.obj \ $(OBJDIR)\polygon.obj \ $(OBJDIR)\mesh.obj \ diff --git a/constraint.cpp b/constraint.cpp index f09fe45b..80ec12c3 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -36,6 +36,11 @@ char *Constraint::DescriptionString(void) { } void Constraint::AddConstraint(Constraint *c) { + AddConstraint(c, true); +} +void Constraint::AddConstraint(Constraint *c, bool rememberForUndo) { + if(rememberForUndo) SS.UndoRemember(); + SS.constraint.AddAndAssignId(c); SS.MarkGroupDirty(c->group); @@ -52,7 +57,7 @@ void Constraint::Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA) c.ptA = ptA; c.ptB = ptB; c.entityA = entityA; - AddConstraint(&c); + AddConstraint(&c, false); } void Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { @@ -310,6 +315,7 @@ void Constraint::MenuConstrain(int id) { break; case GraphicsWindow::MNU_SOLVE_NOW: + SS.ReloadAllImported(); SS.GenerateAll(0, INT_MAX); return; diff --git a/file.cpp b/file.cpp index 23810134..db584ed6 100644 --- a/file.cpp +++ b/file.cpp @@ -20,6 +20,9 @@ hGroup SolveSpace::CreateDefaultDrawingGroup(void) { } void SolveSpace::NewFile(void) { + UndoClearStack(&redo); + UndoClearStack(&undo); + constraint.Clear(); request.Clear(); group.Clear(); @@ -131,7 +134,6 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { '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) }, { 'c', "Constraint.ptB.v", 'x', &(SS.sv.c.ptB.v) }, { 'c', "Constraint.ptC.v", 'x', &(SS.sv.c.ptC.v) }, @@ -373,7 +375,10 @@ bool SolveSpace::LoadEntitiesFromFile(char *file, EntityList *le, SMesh *m) { char *key = line, *val = e+1; LoadUsingTable(key, val); } else if(strcmp(line, "AddGroup")==0) { - + // Don't leak memory; these get allocated whether we want them + // or not. + if(sv.g.exprA) Expr::FreeKeep(&(sv.g.exprA)); + sv.g.remap.Clear(); } else if(strcmp(line, "AddParam")==0) { } else if(strcmp(line, "AddEntity")==0) { @@ -382,7 +387,7 @@ bool SolveSpace::LoadEntitiesFromFile(char *file, EntityList *le, SMesh *m) { } else if(strcmp(line, "AddRequest")==0) { } else if(strcmp(line, "AddConstraint")==0) { - + if(sv.c.exprA) Expr::FreeKeep(&(sv.c.exprA)); } else if(strcmp(line, VERSION_STRING)==0) { } else if(memcmp(line, "Triangle", 8)==0) { diff --git a/graphicswin.cpp b/graphicswin.cpp index b6644613..8077a5a8 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -21,8 +21,8 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "E&xit", MNU_EXIT, 0, mFile }, { 0, "&Edit", 0, NULL }, -{ 1, "&Undo\tCtrl+Z", 0, NULL }, -{ 1, "&Redo\tCtrl+Y", 0, NULL }, +{ 1, "&Undo\tCtrl+Z", MNU_UNDO, 'Z'|C, mEdit }, +{ 1, "&Redo\tCtrl+Y", MNU_REDO, 'Y'|C, mEdit }, { 1, NULL, 0, NULL }, { 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, { 1, NULL, 0, NULL }, @@ -277,11 +277,13 @@ void GraphicsWindow::EnsureValidActives(void) { } } + // And update the checked state for various menus bool locked = LockedInWorkplane(); CheckMenuById(MNU_FREE_IN_3D, !locked); CheckMenuById(MNU_SEL_WORKPLANE, locked); - // And update the checked state for various menus + SS.UndoEnableMenus(); + switch(viewUnits) { case UNIT_MM: case UNIT_INCHES: @@ -360,6 +362,14 @@ void GraphicsWindow::MenuEdit(int id) { break; } + case MNU_UNDO: + SS.UndoUndo(); + break; + + case MNU_REDO: + SS.UndoRedo(); + break; + default: oops(); } } @@ -515,6 +525,11 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, 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 @@ -859,6 +874,11 @@ void GraphicsWindow::MouseMiddleDown(double x, double y) { } 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; @@ -921,6 +941,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { case MNU_DATUM_POINT: hr = AddRequest(Request::DATUM_POINT); SS.GetEntity(hr.entity(0))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(0)); ClearSuper(); @@ -947,8 +968,9 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } hRequest lns[4]; int i; + SS.UndoRemember(); for(i = 0; i < 4; i++) { - lns[i] = AddRequest(Request::LINE_SEGMENT); + lns[i] = AddRequest(Request::LINE_SEGMENT, false); } for(i = 0; i < 4; i++) { Constraint::ConstrainCoincident( @@ -1136,6 +1158,8 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { void GraphicsWindow::EditControlDone(char *s) { Expr *e = Expr::From(s); if(e) { + SS.UndoRemember(); + Constraint *c = SS.GetConstraint(constraintBeingEdited); Expr::FreeKeep(&(c->exprA)); c->exprA = e->DeepCopyKeep(); @@ -1193,6 +1217,8 @@ Vector GraphicsWindow::VectorFromProjs(double right, double up, double fwd) { } void GraphicsWindow::Paint(int w, int h) { + SDWORD in = GetMilliseconds(); + havePainted = true; width = w; height = h; @@ -1250,6 +1276,7 @@ void GraphicsWindow::Paint(int w, int h) { // Draw the groups; this fills the polygons in a drawing group, and // draws the solid mesh. (SS.GetGroup(activeGroup))->Draw(); + dbp("done group: %d ms", GetMilliseconds() - in); // First, draw the entire scene. We don't necessarily want to draw // things with normal z-buffering behaviour; e.g. we always want to @@ -1263,6 +1290,7 @@ void GraphicsWindow::Paint(int w, int h) { SS.entity.elem[i].Draw(a); } } + dbp("done entity: %d ms", GetMilliseconds() - in); glDisable(GL_DEPTH_TEST); // Draw the constraints @@ -1280,5 +1308,9 @@ void GraphicsWindow::Paint(int w, int h) { for(i = 0; i < MAX_SELECTED; i++) { selection[i].Draw(); } + + dbp("till end: %d ms", GetMilliseconds() - in); + dbp("entity.n: %d", SS.entity.n); + dbp("param.n: %d", SS.param.n); } diff --git a/sketch.cpp b/sketch.cpp index ee221628..e9d0ae69 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -131,7 +131,7 @@ void Group::MenuGroup(int id) { default: oops(); } - + SS.UndoRemember(); SS.group.AddAndAssignId(&g); if(g.type == IMPORTED) { SS.ReloadAllImported(); @@ -624,6 +624,7 @@ void Group::GeneratePolygon(void) { polyError.notClosedAt = error; poly.Clear(); } + edges.Clear(); } } diff --git a/sketch.h b/sketch.h index d14e2f53..409ca918 100644 --- a/sketch.h +++ b/sketch.h @@ -375,7 +375,6 @@ public: double val; bool known; - bool assumed; // Used only in the solver hParam substd; @@ -428,7 +427,6 @@ public: // These are the parameters for the constraint. Expr *exprA; - Expr *exprB; hEntity ptA; hEntity ptB; hEntity ptC; @@ -443,6 +441,7 @@ public: char *DescriptionString(void); + static void AddConstraint(Constraint *c, bool rememberForUndo); static void AddConstraint(Constraint *c); static void MenuConstrain(int id); diff --git a/solvespace.h b/solvespace.h index b93038b9..96def228 100644 --- a/solvespace.h +++ b/solvespace.h @@ -141,8 +141,7 @@ public: // In general, the tag indicates the subsys that a variable/equation // has been assigned to; these are exceptions for variables: - static const int VAR_ASSUMED = 10000; - static const int VAR_SUBSTITUTED = 10001; + static const int VAR_SUBSTITUTED = 10000; // and for equations: static const int EQ_SUBSTITUTED = 20000; @@ -218,13 +217,38 @@ public: inline Param *GetParam (hParam h) { return param. FindById(h); } inline Group *GetGroup (hGroup h) { return group. FindById(h); } - FILE *fh; + // The state for undo/redo + typedef struct { + IdList group; + IdList request; + IdList constraint; + IdList param; + hGroup activeGroup; + } UndoState; + static const int MAX_UNDO = 16; + typedef struct { + UndoState d[MAX_UNDO]; + int cnt; + int write; + } UndoStack; + UndoStack undo; + UndoStack redo; + void UndoEnableMenus(void); + void UndoRemember(void); + void UndoUndo(void); + void UndoRedo(void); + void PushFromCurrentOnto(UndoStack *uk); + void PopOntoCurrentFrom(UndoStack *uk); + void UndoClearState(UndoState *ut); + void UndoClearStack(UndoStack *uk); + // File load/save routines, including the additional files that get + // loaded when we have import groups. + FILE *fh; void Init(char *cmdLine); void AfterNewFile(void); static void RemoveFromRecentList(char *file); static void AddToRecentList(char *file); - char saveFile[MAX_PATH]; bool unsaved; typedef struct { @@ -256,6 +280,8 @@ public: void MarkGroupDirty(hGroup hg); void MarkGroupDirtyByEntity(hEntity he); + // Consistency checking on the sketch: stuff with missing dependencies + // will get deleted automatically. struct { int requests; int groups; diff --git a/system.cpp b/system.cpp index 9f97360a..ea5b1b51 100644 --- a/system.cpp +++ b/system.cpp @@ -470,8 +470,6 @@ void System::Solve(Group *g) { Param *pp = SS.GetParam(p->h); pp->val = val; pp->known = true; - // The main param table keeps track of what was assumed. - pp->assumed = (p->tag == VAR_ASSUMED); } if(g->solved.how != Group::SOLVED_OKAY) { g->solved.how = Group::SOLVED_OKAY; diff --git a/textwin.cpp b/textwin.cpp index 78d906b6..70ec54ad 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -540,6 +540,8 @@ void TextWindow::ScreenSelectRequest(int link, DWORD v) { SS.GW.selection[0].entity = hr.entity(0); } void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) { + SS.UndoRemember(); + Group *g = SS.GetGroup(SS.TW.shown->group); if(g->subtype == Group::ONE_SIDED) { g->subtype = Group::TWO_SIDED; @@ -551,6 +553,8 @@ void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) { SS.GW.ClearSuper(); } void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) { + SS.UndoRemember(); + Group *g = SS.GetGroup(SS.TW.shown->group); g->meshCombine = v; SS.MarkGroupDirty(g->h); @@ -558,6 +562,8 @@ void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) { SS.GW.ClearSuper(); } void TextWindow::ScreenColor(int link, DWORD v) { + SS.UndoRemember(); + Group *g = SS.GetGroup(SS.TW.shown->group); if(v < 0 || v >= MODEL_COLORS) return; g->color = SS.TW.modelColor[v]; @@ -578,6 +584,8 @@ void TextWindow::ScreenChangeGroupName(int link, DWORD v) { SS.TW.edit.group.v = v; } void TextWindow::ScreenDeleteGroup(int link, DWORD v) { + SS.UndoRemember(); + hGroup hg = SS.TW.shown->group; if(hg.v == SS.GW.activeGroup.v) { Error("This group is currently active; activate a different group " @@ -763,6 +771,8 @@ void TextWindow::EditControlDone(char *s) { case EDIT_TIMES_REPEATED: { Expr *e = Expr::From(s); if(e) { + SS.UndoRemember(); + Group *g = SS.GetGroup(edit.group); Expr::FreeKeep(&(g->exprA)); g->exprA = e->DeepCopyKeep(); @@ -786,6 +796,8 @@ void TextWindow::EditControlDone(char *s) { if(invalid || !*s) { Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); } else { + SS.UndoRemember(); + Group *g = SS.GetGroup(edit.group); g->name.strcpy(s); } diff --git a/ui.h b/ui.h index b3c34059..0ad54733 100644 --- a/ui.h +++ b/ui.h @@ -132,6 +132,8 @@ public: MNU_UNITS_INCHES, MNU_UNITS_MM, // Edit + MNU_UNDO, + MNU_REDO, MNU_DELETE, MNU_UNSELECT_ALL, // Request @@ -248,6 +250,7 @@ public: hConstraint constraintBeingEdited; bool ConstrainPointByHovered(hEntity pt); + hRequest AddRequest(int type, bool rememberForUndo); hRequest AddRequest(int type); // The current selection. diff --git a/undoredo.cpp b/undoredo.cpp new file mode 100644 index 00000000..b2cec660 --- /dev/null +++ b/undoredo.cpp @@ -0,0 +1,154 @@ +#include "solvespace.h" + +void SolveSpace::UndoRemember(void) { + PushFromCurrentOnto(&undo); + UndoClearStack(&redo); + UndoEnableMenus(); +} + +void SolveSpace::UndoUndo(void) { + if(undo.cnt <= 0) return; + + PushFromCurrentOnto(&redo); + PopOntoCurrentFrom(&undo); + UndoEnableMenus(); +} + +void SolveSpace::UndoRedo(void) { + if(redo.cnt <= 0) return; + + PushFromCurrentOnto(&undo); + PopOntoCurrentFrom(&redo); + UndoEnableMenus(); +} + +void SolveSpace::UndoEnableMenus(void) { + EnableMenuById(GraphicsWindow::MNU_UNDO, undo.cnt > 0); + EnableMenuById(GraphicsWindow::MNU_REDO, redo.cnt > 0); +} + +void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { + int i; + + if(uk->cnt == MAX_UNDO) { + UndoClearState(&(uk->d[uk->write])); + // And then write in to this one again + } else { + (uk->cnt)++; + } + + UndoState *ut = &(uk->d[uk->write]); + ZERO(ut); + for(i = 0; i < group.n; i++) { + Group *src = &(group.elem[i]); + Group dest = *src; + // And then clean up all the stuff that needs to be a deep copy, + // and zero out all the dynamic stuff that will get regenerated. + dest.clean = false; + if(src->exprA) dest.exprA = src->exprA->DeepCopyKeep(); + ZERO(&(dest.solved)); + ZERO(&(dest.poly)); + ZERO(&(dest.polyError)); + ZERO(&(dest.mesh)); + ZERO(&(dest.meshError)); + + ZERO(&(dest.remap)); + src->remap.DeepCopyInto(&(dest.remap)); + + ZERO(&(dest.impMesh)); + ZERO(&(dest.impEntity)); + ut->group.Add(&dest); + } + for(i = 0; i < request.n; i++) { + ut->request.Add(&(request.elem[i])); + } + for(i = 0; i < constraint.n; i++) { + Constraint *src = &(constraint.elem[i]); + Constraint dest = *src; + if(src->exprA) dest.exprA = src->exprA->DeepCopyKeep(); + ZERO(&(dest.dogd)); + ut->constraint.Add(&dest); + } + for(i = 0; i < param.n; i++) { + ut->param.Add(&(param.elem[i])); + } + ut->activeGroup = SS.GW.activeGroup; + + uk->write = WRAP(uk->write + 1, MAX_UNDO); +} + +void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { + if(uk->cnt <= 0) oops(); + (uk->cnt)--; + uk->write = WRAP(uk->write - 1, MAX_UNDO); + + UndoState *ut = &(uk->d[uk->write]); + + int i; + // Free everything in the main copy of the program before replacing it + for(i = 0; i < group.n; i++) { + Group *g = &(group.elem[i]); + if(g->exprA) Expr::FreeKeep(&(g->exprA)); + g->poly.Clear(); + g->mesh.Clear(); + g->meshError.interferesAt.Clear(); + g->remap.Clear(); + g->impMesh.Clear(); + g->impEntity.Clear(); + } + for(i = 0; i < constraint.n; i++) { + Constraint *c = &(constraint.elem[i]); + if(c->exprA) Expr::FreeKeep(&(c->exprA)); + } + group.Clear(); + request.Clear(); + constraint.Clear(); + param.Clear(); + + // And then do a shallow copy of the state from the undo list + ut->group.MoveSelfInto(&group); + ut->request.MoveSelfInto(&request); + ut->constraint.MoveSelfInto(&constraint); + ut->param.MoveSelfInto(¶m); + SS.GW.activeGroup = ut->activeGroup; + + // No need to free it, since a shallow copy was made above + ZERO(ut); + + // And reset the state everywhere else in the program, since the + // sketch just changed a lot. + SS.GW.ClearSuper(); + SS.TW.ClearSuper(); + SS.ReloadAllImported(); + SS.GenerateAll(0, INT_MAX); + later.showTW = true; +} + +void SolveSpace::UndoClearStack(UndoStack *uk) { + while(uk->cnt > 0) { + uk->write = WRAP(uk->write - 1, MAX_UNDO); + (uk->cnt)--; + UndoClearState(&(uk->d[uk->write])); + } + ZERO(uk); // for good measure +} + +void SolveSpace::UndoClearState(UndoState *ut) { + int i; + for(i = 0; i < ut->group.n; i++) { + Group *g = &(ut->group.elem[i]); + + if(g->exprA) Expr::FreeKeep(&(g->exprA)); + g->remap.Clear(); + } + ut->group.Clear(); + ut->request.Clear(); + for(i = 0; i < ut->constraint.n; i++) { + Constraint *c = &(ut->constraint.elem[i]); + if(c->exprA) Expr::FreeKeep(&(c->exprA)); + } + ut->constraint.Clear(); + ut->param.Clear(); + ZERO(ut); +} + diff --git a/wishlist.txt b/wishlist.txt index 65cf718a..e1a7e8bb 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,4 +1,8 @@ +stupidity where stuff gets double-added to entity list +replace linear search through IdLists with faster (binary search?) +point face distance constraint + STL check for meshes, and T intersection removal STL export better triangle combining (Simplify()) for meshes @@ -8,10 +12,8 @@ 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 - +reference dimensions (just to look at, no equations)