Turn newly created redundant constraints with a label into references.

This is a fairly standard CAD feature; it conveys the same
information and has the same recovery path, without erroring out,
so seems like an obvious win.
This commit is contained in:
EvilSpirit 2017-01-11 20:59:07 +07:00 committed by whitequark
parent c00ab25740
commit 43db2201fd
5 changed files with 97 additions and 23 deletions

View File

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

View File

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

View File

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

View File

@ -397,9 +397,15 @@ public:
bool NewtonSolve(int tag);
void MarkParamsFree(bool findFree);
int CalculateDof();
SolveResult Solve(Group *g, int *dof, List<hConstraint> *bad,
bool andFindBad, bool andFindFree, bool forceDofCheck = false);
SolveResult SolveRank(Group *g, int *dof, List<hConstraint> *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();

View File

@ -475,28 +475,8 @@ SolveResult System::Solve(Group *g, int *dof, List<hConstraint> *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<hConstraint> *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;
}