diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2220d..63ad6b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ New sketch features: drag the point from the source sketch. * When dragging an arc or rectangle point, it will be automatically constrained to other points with a click. + * When adding a constraint which has a label and is redundant with another + constraint, the constraint is added as a reference, avoiding an error. New export/import features: * Three.js: allow configuring projection for exported model, and initially diff --git a/src/constraint.cpp b/src/constraint.cpp index 14e3271..b1f5d1f 100644 --- a/src/constraint.cpp +++ b/src/constraint.cpp @@ -733,6 +733,14 @@ void Constraint::MenuConstrain(Command id) { default: ssassert(false, "Unexpected menu ID"); } + if(SK.constraint.FindByIdNoOops(c.h)) { + Constraint *constraint = SK.GetConstraint(c.h); + if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY && + constraint->HasLabel()) { + constraint->reference = true; + } + } + SS.GW.ClearSelection(); InvalidateGraphics(); } diff --git a/src/generate.cpp b/src/generate.cpp index a89f59b..fc5769b 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -498,7 +498,7 @@ void SolveSpaceUI::SolveGroupAndReport(hGroup hg, bool andFindFree) { } } -void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { +void SolveSpaceUI::WriteEqSystemForGroup(hGroup hg) { int i; // Clear out the system to be solved. sys.entity.Clear(); @@ -528,6 +528,11 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { } MarkDraggedParams(); +} + +void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { + WriteEqSystemForGroup(hg); + Group *g = SK.GetGroup(hg); g->solved.remove.Clear(); SolveResult how = sys.Solve(g, &(g->solved.dof), &(g->solved.remove), @@ -541,6 +546,15 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { FreeAllTemporary(); } +SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg) { + WriteEqSystemForGroup(hg); + Group *g = SK.GetGroup(hg); + SolveResult result = sys.SolveRank(g, NULL, NULL, false, false, + /*forceDofCheck=*/!g->dofCheckOk); + FreeAllTemporary(); + return result; +} + bool SolveSpaceUI::ActiveGroupsOkay() { for(int i = 0; i < SK.groupOrder.n; i++) { Group *g = SK.GetGroup(SK.groupOrder.elem[i]); diff --git a/src/solvespace.h b/src/solvespace.h index f88b185..e650fb8 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -397,9 +397,15 @@ public: bool NewtonSolve(int tag); + void MarkParamsFree(bool findFree); + int CalculateDof(); + SolveResult Solve(Group *g, int *dof, List *bad, bool andFindBad, bool andFindFree, bool forceDofCheck = false); + SolveResult SolveRank(Group *g, int *dof, List *bad, + bool andFindBad, bool andFindFree, bool forceDofCheck = false); + void Clear(); }; @@ -847,6 +853,8 @@ public: bool genForBBox = false); void SolveGroup(hGroup hg, bool andFindFree); void SolveGroupAndReport(hGroup hg, bool andFindFree); + SolveResult TestRankForGroup(hGroup hg); + void WriteEqSystemForGroup(hGroup hg); void MarkDraggedParams(); void ForceReferences(); diff --git a/src/system.cpp b/src/system.cpp index 5e6b807..e90611a 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -475,28 +475,8 @@ SolveResult System::Solve(Group *g, int *dof, List *bad, // This is not the full Jacobian, but any substitutions or single-eq // solves removed one equation and one unknown, therefore no effect // on the number of DOF. - if(dof) *dof = mat.n - mat.m; - - // If requested, find all the free (unbound) variables. This might be - // more than the number of degrees of freedom. Don't always do this, - // because the display would get annoying and it's slow. - for(i = 0; i < param.n; i++) { - Param *p = &(param.elem[i]); - p->free = false; - - if(andFindFree) { - if(p->tag == 0) { - p->tag = VAR_DOF_TEST; - WriteJacobian(0); - EvalJacobian(); - int rank = CalculateRank(); - if(rank == mat.m) { - p->free = true; - } - p->tag = 0; - } - } - } + if(dof) *dof = CalculateDof(); + MarkParamsFree(andFindFree); } // System solved correctly, so write the new values back in to the // main parameter table. @@ -537,9 +517,71 @@ didnt_converge: return rankOk ? SolveResult::DIDNT_CONVERGE : SolveResult::REDUNDANT_DIDNT_CONVERGE; } +SolveResult System::SolveRank(Group *g, int *dof, List *bad, + bool andFindBad, bool andFindFree, bool forceDofCheck) +{ + WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); + + // All params and equations are assigned to group zero. + param.ClearTags(); + eq.ClearTags(); + + if(!forceDofCheck) { + SolveBySubstitution(); + } + + // Now write the Jacobian, and do a rank test; that + // tells us if the system is inconsistently constrained. + if(!WriteJacobian(0)) { + return SolveResult::TOO_MANY_UNKNOWNS; + } + + bool rankOk = TestRank(); + if(!rankOk) { + if(!g->allowRedundant) { + if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck); + } + } else { + // This is not the full Jacobian, but any substitutions or single-eq + // solves removed one equation and one unknown, therefore no effect + // on the number of DOF. + if(dof) *dof = CalculateDof(); + MarkParamsFree(andFindFree); + } + return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; +} + void System::Clear() { entity.Clear(); param.Clear(); eq.Clear(); dragged.Clear(); } + +void System::MarkParamsFree(bool find) { + // If requested, find all the free (unbound) variables. This might be + // more than the number of degrees of freedom. Don't always do this, + // because the display would get annoying and it's slow. + for(int i = 0; i < param.n; i++) { + Param *p = &(param.elem[i]); + p->free = false; + + if(find) { + if(p->tag == 0) { + p->tag = VAR_DOF_TEST; + WriteJacobian(0); + EvalJacobian(); + int rank = CalculateRank(); + if(rank == mat.m) { + p->free = true; + } + p->tag = 0; + } + } + } +} + +int System::CalculateDof() { + return mat.n - mat.m; +} +