Add undo/redo. This saves the param guesses, constraints, groups,

and requests to a separate list. It's messy, because I have to make
a deep copy (e.g. of the remap list for the groups, or Expr *
stuff) of some things. Others (e.g. the polygon or mesh) will be
regenerated, so they should be discarded, but they must not get
double-freed.

In any case, works superficially. And fix a few memory leaks
unrelated to this, and remove some dead code.

[git-p4: depot-paths = "//depot/solvespace/": change = 1775]
solver
Jonathan Westhues 2008-06-04 02:22:30 -08:00
parent 71391e6a55
commit 48612bde3d
12 changed files with 259 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,hGroup> group;
IdList<Request,hRequest> request;
IdList<Constraint,hConstraint> constraint;
IdList<Param,hParam> 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;

View File

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

View File

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

3
ui.h
View File

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

154
undoredo.cpp Normal file
View File

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

View File

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