We now have selective solve; a group (and all the groups afterward)

becomes dirty when the user makes a change, and only the dirty
groups get solved. That's a huge effective speedup.

Also add delete function for groups. That has an interesting issue;
it actually ends up recursing on GenerateAll(), since GenerateAll()
calls ClearSuper(), ClearSuper() might need to recreate a group (if
all the groups were deleted), and that would activate the group,
which calls GenerateAll. The right solution is probably a deferred
execution mechanism, where you can schedule something to happen
before we go idle, but not do it right now.

[git-p4: depot-paths = "//depot/solvespace/": change = 1771]
This commit is contained in:
Jonathan Westhues 2008-06-02 03:43:27 -08:00
parent 5bbb27fd8e
commit 7d4a4fbb76
8 changed files with 117 additions and 29 deletions

View File

@ -38,6 +38,7 @@ char *Constraint::DescriptionString(void) {
void Constraint::AddConstraint(Constraint *c) {
SS.constraint.AddAndAssignId(c);
SS.MarkGroupDirty(c->group);
SS.GenerateAll();
}

View File

@ -2,6 +2,23 @@
#define VERSION_STRING "±²³SolveSpaceREVa"
hGroup SolveSpace::CreateDefaultDrawingGroup(void) {
Group g;
ZERO(&g);
// And an empty group, for the first stuff the user draws.
g.visible = true;
g.type = Group::DRAWING_WORKPLANE;
g.subtype = Group::WORKPLANE_BY_POINT_ORTHO;
g.predef.q = Quaternion::From(1, 0, 0, 0);
hRequest hr = Request::HREQUEST_REFERENCE_XY;
g.predef.origin = hr.entity(1);
g.name.strcpy("draw-in-plane");
group.AddAndAssignId(&g);
SS.GetGroup(g.h)->activeWorkplane = g.h.entity(0);
return g.h;
}
void SolveSpace::NewFile(void) {
constraint.Clear();
request.Clear();
@ -22,7 +39,7 @@ void SolveSpace::NewFile(void) {
// Let's create three two-d coordinate systems, for the coordinate
// planes; these are our references, present in every sketch.
Request r;
memset(&r, 0, sizeof(r));
ZERO(&r);
r.type = Request::WORKPLANE;
r.group = Group::HGROUP_REFERENCES;
r.workplane = Entity::FREE_IN_3D;
@ -36,15 +53,7 @@ void SolveSpace::NewFile(void) {
r.h = Request::HREQUEST_REFERENCE_ZX;
request.Add(&r);
// And an empty group, for the first stuff the user draws.
g.type = Group::DRAWING_WORKPLANE;
g.subtype = Group::WORKPLANE_BY_POINT_ORTHO;
g.predef.q = Quaternion::From(1, 0, 0, 0);
hRequest hr = Request::HREQUEST_REFERENCE_XY;
g.predef.origin = hr.entity(1);
g.name.strcpy("draw-in-plane");
group.AddAndAssignId(&g);
SS.GetGroup(g.h)->activeWorkplane = g.h.entity(0);
CreateDefaultDrawingGroup();
}
const SolveSpace::SaveTable SolveSpace::SAVED[] = {

View File

@ -245,8 +245,17 @@ void GraphicsWindow::EnsureValidActives(void) {
break;
}
}
if(i >= SS.group.n) oops();
activeGroup = SS.group.elem[i].h;
if(i >= SS.group.n) {
// This can happen if the user deletes all the groups in the
// sketch. It's difficult to prevent that, because the last
// group might have been deleted automatically, because it failed
// a dependency. There needs to be something, so create a plane
// drawing group and activate that. They should never be able
// to delete the references, though.
activeGroup = SS.CreateDefaultDrawingGroup();
} else {
activeGroup = SS.group.elem[i].h;
}
SS.GetGroup(activeGroup)->Activate();
change = true;
}
@ -309,7 +318,9 @@ void GraphicsWindow::MenuEdit(int id) {
case MNU_UNSELECT_ALL:
SS.GW.GroupSelection();
if(SS.GW.gs.n == 0 && SS.GW.pending.operation == 0) {
SS.TW.ClearSuper();
if(!TextEditControlIsVisible()) {
SS.TW.ClearSuper();
}
}
SS.GW.ClearSuper();
HideTextEditControl();
@ -401,6 +412,7 @@ c:
if(!he.isFromRequest()) continue;
Request *r = SS.GetRequest(he.request());
r->construction = !(r->construction);
SS.MarkGroupDirty(r->group);
}
SS.GW.ClearSelection();
SS.GenerateAll();
@ -581,6 +593,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
UpdateDraggedPoint(pending.point, x, y);
HitTestMakeSelection(mp);
}
SS.MarkGroupDirtyByEntity(pending.point);
break;
}
case DRAGGING_NEW_CUBIC_POINT: {
@ -594,6 +607,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
SS.GetEntity(hr.entity(2))->PointForceTo(p1);
Vector p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3));
SS.GetEntity(hr.entity(3))->PointForceTo(p2);
SS.MarkGroupDirtyByEntity(pending.point);
break;
}
case DRAGGING_NEW_ARC_POINT: {
@ -606,6 +621,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
Vector center = (ona.Plus(onb)).ScaledBy(0.5);
SS.GetEntity(hr.entity(1))->PointForceTo(center);
SS.MarkGroupDirtyByEntity(pending.point);
break;
}
case DRAGGING_NEW_RADIUS:
@ -615,6 +632,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
Point2d c2 = ProjectPoint(center);
double r = c2.DistanceTo(mp)/scale;
SS.GetEntity(circle->distance)->DistanceForceTo(r);
SS.MarkGroupDirtyByEntity(pending.circle);
break;
}
@ -644,6 +663,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
}
orig.mouse = mp;
normal->NormalForceTo(Quaternion::From(u, v));
SS.MarkGroupDirtyByEntity(pending.normal);
break;
}
@ -856,7 +877,7 @@ hRequest GraphicsWindow::AddRequest(int type) {
// we mustn't try to solve until reasonable values have been supplied
// for these new parameters, or else we'll get a numerical blowup.
SS.GenerateAll(-1, -1);
SS.MarkGroupDirty(r.group);
return r.h;
}
@ -1118,7 +1139,9 @@ void GraphicsWindow::EditControlDone(char *s) {
Constraint *c = SS.GetConstraint(constraintBeingEdited);
Expr::FreeKeep(&(c->exprA));
c->exprA = e->DeepCopyKeep();
HideGraphicsEditControl();
SS.MarkGroupDirty(c->group);
SS.GenerateAll();
} else {
Error("Not a valid number or expression: '%s'", s);

View File

@ -23,7 +23,7 @@ void Group::AddParam(IdList<Param,hParam> *param, hParam hp, double v) {
void Group::MenuGroup(int id) {
Group g;
memset(&g, 0, sizeof(g));
ZERO(&g);
g.visible = true;
if(id >= RECENT_IMPORT && id < (RECENT_IMPORT + MAX_RECENT)) {
@ -163,6 +163,8 @@ void Group::Activate(void) {
} else {
SS.GW.showFaces = false;
}
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
SS.GenerateAll();
SS.TW.Show();
}

View File

@ -28,6 +28,26 @@ void SolveSpace::AfterNewFile(void) {
TW.Show();
}
void SolveSpace::MarkGroupDirtyByEntity(hEntity he) {
Entity *e = SS.GetEntity(he);
MarkGroupDirty(e->group);
}
void SolveSpace::MarkGroupDirty(hGroup hg) {
int i;
bool go = false;
for(i = 0; i < group.n; i++) {
Group *g = &(group.elem[i]);
if(g->h.v == hg.v) {
go = true;
}
if(go) {
g->clean = false;
}
}
unsaved = true;
}
bool SolveSpace::PruneOrphans(void) {
int i;
for(i = 0; i < request.n; i++) {
@ -133,18 +153,24 @@ bool SolveSpace::PruneConstraints(hGroup hg) {
void SolveSpace::GenerateAll(void) {
int i;
int firstShown = INT_MAX, lastShown = 0;
// The references don't count, so start from group 1
for(i = 1; i < group.n; i++) {
int firstDirty = INT_MAX, lastVisible = 0;
// Start from the first dirty group, and solve until the active group,
// since all groups after the active group are hidden.
for(i = 0; i < group.n; i++) {
Group *g = &(group.elem[i]);
if(g->visible || (g->solved.how != Group::SOLVED_OKAY)) {
firstShown = min(firstShown, i);
lastShown = max(lastShown, i);
if((!g->clean) || (g->solved.how != Group::SOLVED_OKAY)) {
firstDirty = min(firstDirty, i);
}
if(g->h.v == SS.GW.activeGroup.v) {
lastVisible = i;
}
}
// Even if nothing is shown, we have to keep going; the entities get
// generated for hidden groups, even though they're not solved.
GenerateAll(firstShown, lastShown);
if(firstDirty == INT_MAX || lastVisible == 0) {
// All clean; so just regenerate the entities, and don't solve anything.
GenerateAll(-1, -1);
} else {
GenerateAll(firstDirty, lastVisible);
}
}
void SolveSpace::GenerateAll(int first, int last) {
@ -193,6 +219,7 @@ void SolveSpace::GenerateAll(int first, int last) {
if(g->h.v == Group::HGROUP_REFERENCES.v) {
ForceReferences();
g->solved.how = Group::SOLVED_OKAY;
g->clean = true;
} else {
if(i >= first && i <= last) {
// The group falls inside the range, so really solve it,
@ -200,6 +227,7 @@ void SolveSpace::GenerateAll(int first, int last) {
SolveGroup(g->h);
g->GeneratePolygon();
g->GenerateMesh();
g->clean = true;
} else {
// The group falls outside the range, so just assume that
// it's good wherever we left it. The mesh is unchanged,

View File

@ -243,6 +243,7 @@ public:
Constraint c;
} sv;
static void MenuFile(int id);
hGroup CreateDefaultDrawingGroup(void);
void NewFile(void);
bool SaveToFile(char *filename);
bool LoadFromFile(char *filename);
@ -250,6 +251,7 @@ public:
void ReloadAllImported(void);
void MarkGroupDirty(hGroup hg);
void MarkGroupDirtyByEntity(hEntity he);
struct {
int requests;

View File

@ -535,6 +535,7 @@ void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) {
} else if(g->subtype == Group::TWO_SIDED) {
g->subtype = Group::ONE_SIDED;
} else oops();
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
@ -545,6 +546,7 @@ void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) {
} else if(g->meshCombine == Group::COMBINE_AS_UNION) {
g->meshCombine = Group::COMBINE_AS_DIFFERENCE;
} else oops();
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
@ -552,6 +554,7 @@ void TextWindow::ScreenColor(int link, DWORD v) {
Group *g = SS.GetGroup(SS.TW.shown->group);
if(v < 0 || v >= MODEL_COLORS) return;
g->color = SS.TW.modelColor[v];
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
@ -567,15 +570,32 @@ void TextWindow::ScreenChangeGroupName(int link, DWORD v) {
SS.TW.edit.meaning = EDIT_GROUP_NAME;
SS.TW.edit.group.v = v;
}
void TextWindow::ScreenDeleteGroup(int link, DWORD v) {
hGroup hg = SS.TW.shown->group;
if(hg.v == SS.GW.activeGroup.v) {
Error("This group is currently active; activate a different group "
"before proceeding.");
return;
}
SS.group.RemoveById(SS.TW.shown->group);
// This is a major change, so let's re-solve everything.
SS.TW.ClearSuper();
SS.GW.ClearSuper();
SS.GenerateAll(0, INT_MAX);
}
void TextWindow::ShowGroupInfo(void) {
Group *g = SS.group.FindById(shown->group);
char *s, *s2;
s = (shown->group.v == Group::HGROUP_REFERENCES.v) ? "" : "(rename)";
Printf(true, "%FtGROUP %E%s %Fl%Ll%D%f%s%E",
g->DescriptionString(),
g->h.v, &TextWindow::ScreenChangeGroupName, s);
if(shown->group.v == Group::HGROUP_REFERENCES.v) {
Printf(true, "%FtGROUP %E%s", g->DescriptionString());
} else {
Printf(true, "%FtGROUP %E%s "
"(%Fl%Ll%D%frename%E / %Fl%Ll%D%fdel%E)",
g->DescriptionString(),
g->h.v, &TextWindow::ScreenChangeGroupName,
g->h.v, &TextWindow::ScreenDeleteGroup);
}
if(g->type == Group::IMPORTED) {
Printf(true, "%FtIMPORT %E '%s'", g->impFile);
@ -722,6 +742,8 @@ void TextWindow::EditControlDone(char *s) {
Group *g = SS.GetGroup(edit.group);
Expr::FreeKeep(&(g->exprA));
g->exprA = e->DeepCopyKeep();
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.TW.Show();
} else {

1
ui.h
View File

@ -89,6 +89,7 @@ public:
static void ScreenToggleGroupShown(int link, DWORD v);
static void ScreenHowGroupSolved(int link, DWORD v);
static void ScreenShowGroupsSpecial(int link, DWORD v);
static void ScreenDeleteGroup(int link, DWORD v);
static void ScreenHoverConstraint(int link, DWORD v);
static void ScreenHoverRequest(int link, DWORD v);