diff --git a/constraint.cpp b/constraint.cpp index 17d2daf..33310f2 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -38,6 +38,7 @@ char *Constraint::DescriptionString(void) { void Constraint::AddConstraint(Constraint *c) { SS.constraint.AddAndAssignId(c); + SS.MarkGroupDirty(c->group); SS.GenerateAll(); } diff --git a/file.cpp b/file.cpp index 97e9b2b..86b7c89 100644 --- a/file.cpp +++ b/file.cpp @@ -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[] = { diff --git a/graphicswin.cpp b/graphicswin.cpp index a36a3aa..e197ae7 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -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); diff --git a/sketch.cpp b/sketch.cpp index 7414a98..82472a5 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -23,7 +23,7 @@ void Group::AddParam(IdList *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(); } diff --git a/solvespace.cpp b/solvespace.cpp index 69389f4..dae6d47 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -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, diff --git a/solvespace.h b/solvespace.h index f792803..e3d3abd 100644 --- a/solvespace.h +++ b/solvespace.h @@ -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; diff --git a/textwin.cpp b/textwin.cpp index b613ef5..12eedcf 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -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 { diff --git a/ui.h b/ui.h index 285d584..b3c3405 100644 --- a/ui.h +++ b/ui.h @@ -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);