856 lines
36 KiB
C++
856 lines
36 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Implementation of the Constraint menu, to create new constraints in
|
|
// the sketch.
|
|
//
|
|
// Copyright 2008-2013 Jonathan Westhues.
|
|
//-----------------------------------------------------------------------------
|
|
#include "solvespace.h"
|
|
|
|
std::string Constraint::DescriptionString() const {
|
|
std::string s;
|
|
switch(type) {
|
|
case Type::POINTS_COINCIDENT: s = C_("constr-name", "pts-coincident"); break;
|
|
case Type::PT_PT_DISTANCE: s = C_("constr-name", "pt-pt-distance"); break;
|
|
case Type::PT_LINE_DISTANCE: s = C_("constr-name", "pt-line-distance"); break;
|
|
case Type::PT_PLANE_DISTANCE: s = C_("constr-name", "pt-plane-distance"); break;
|
|
case Type::PT_FACE_DISTANCE: s = C_("constr-name", "pt-face-distance"); break;
|
|
case Type::PROJ_PT_DISTANCE: s = C_("constr-name", "proj-pt-pt-distance"); break;
|
|
case Type::PT_IN_PLANE: s = C_("constr-name", "pt-in-plane"); break;
|
|
case Type::PT_ON_LINE: s = C_("constr-name", "pt-on-line"); break;
|
|
case Type::PT_ON_FACE: s = C_("constr-name", "pt-on-face"); break;
|
|
case Type::EQUAL_LENGTH_LINES: s = C_("constr-name", "eq-length"); break;
|
|
case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break;
|
|
case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break;
|
|
case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break;
|
|
case Type::ARC_ARC_LEN_RATIO: s = C_("constr-name", "arc-arc-length-ratio"); break;
|
|
case Type::ARC_LINE_LEN_RATIO: s = C_("constr-name", "arc-line-length-ratio"); break;
|
|
case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break;
|
|
case Type::ARC_ARC_DIFFERENCE: s = C_("constr-name", "arc-arc-len-difference"); break;
|
|
case Type::ARC_LINE_DIFFERENCE: s = C_("constr-name", "arc-line-len-difference"); break;
|
|
case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break;
|
|
case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break;
|
|
case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break;
|
|
case Type::SYMMETRIC_LINE: s = C_("constr-name", "symmetric-line"); break;
|
|
case Type::AT_MIDPOINT: s = C_("constr-name", "at-midpoint"); break;
|
|
case Type::HORIZONTAL: s = C_("constr-name", "horizontal"); break;
|
|
case Type::VERTICAL: s = C_("constr-name", "vertical"); break;
|
|
case Type::DIAMETER: s = C_("constr-name", "diameter"); break;
|
|
case Type::PT_ON_CIRCLE: s = C_("constr-name", "pt-on-circle"); break;
|
|
case Type::SAME_ORIENTATION: s = C_("constr-name", "same-orientation"); break;
|
|
case Type::ANGLE: s = C_("constr-name", "angle"); break;
|
|
case Type::PARALLEL: s = C_("constr-name", "parallel"); break;
|
|
case Type::ARC_LINE_TANGENT: s = C_("constr-name", "arc-line-tangent"); break;
|
|
case Type::CUBIC_LINE_TANGENT: s = C_("constr-name", "cubic-line-tangent"); break;
|
|
case Type::CURVE_CURVE_TANGENT: s = C_("constr-name", "curve-curve-tangent"); break;
|
|
case Type::PERPENDICULAR: s = C_("constr-name", "perpendicular"); break;
|
|
case Type::EQUAL_RADIUS: s = C_("constr-name", "eq-radius"); break;
|
|
case Type::EQUAL_ANGLE: s = C_("constr-name", "eq-angle"); break;
|
|
case Type::EQUAL_LINE_ARC_LEN: s = C_("constr-name", "eq-line-len-arc-len"); break;
|
|
case Type::WHERE_DRAGGED: s = C_("constr-name", "lock-where-dragged"); break;
|
|
case Type::COMMENT: s = C_("constr-name", "comment"); break;
|
|
default: s = "???"; break;
|
|
}
|
|
|
|
return ssprintf("c%03x-%s", h.v, s.c_str());
|
|
}
|
|
|
|
#ifndef LIBRARY
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Delete all constraints with the specified type, entityA, ptA. We use this
|
|
// when auto-removing constraints that would become redundant.
|
|
//-----------------------------------------------------------------------------
|
|
void Constraint::DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA)
|
|
{
|
|
SK.constraint.ClearTags();
|
|
for(auto &constraint : SK.constraint) {
|
|
ConstraintBase *ct = &constraint;
|
|
if(ct->type != type) continue;
|
|
|
|
if(ct->entityA != entityA) continue;
|
|
if(ct->ptA != ptA) continue;
|
|
ct->tag = 1;
|
|
}
|
|
SK.constraint.RemoveTagged();
|
|
// And no need to do anything special, since nothing
|
|
// ever depends on a constraint. But do clear the
|
|
// hover, in case the just-deleted constraint was
|
|
// hovered.
|
|
SS.GW.hover.Clear();
|
|
}
|
|
|
|
hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) {
|
|
if(rememberForUndo) SS.UndoRemember();
|
|
|
|
hConstraint hc = SK.constraint.AddAndAssignId(c);
|
|
SK.GetConstraint(hc)->Generate(&SK.param);
|
|
|
|
SS.MarkGroupDirty(c->group);
|
|
SK.GetGroup(c->group)->dofCheckOk = false;
|
|
return c->h;
|
|
}
|
|
|
|
hConstraint Constraint::Constrain(Constraint::Type type, hEntity ptA, hEntity ptB,
|
|
hEntity entityA, hEntity entityB,
|
|
bool other, bool other2)
|
|
{
|
|
Constraint c = {};
|
|
c.group = SS.GW.activeGroup;
|
|
c.workplane = SS.GW.ActiveWorkplane();
|
|
c.type = type;
|
|
c.ptA = ptA;
|
|
c.ptB = ptB;
|
|
c.entityA = entityA;
|
|
c.entityB = entityB;
|
|
c.other = other;
|
|
c.other2 = other2;
|
|
return AddConstraint(&c, /*rememberForUndo=*/false);
|
|
}
|
|
|
|
hConstraint Constraint::TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
|
|
hEntity entityA, hEntity entityB,
|
|
bool other, bool other2) {
|
|
int rankBefore, rankAfter;
|
|
SolveResult howBefore = SS.TestRankForGroup(SS.GW.activeGroup, &rankBefore);
|
|
hConstraint hc = Constrain(type, ptA, ptB, entityA, entityB, other, other2);
|
|
SolveResult howAfter = SS.TestRankForGroup(SS.GW.activeGroup, &rankAfter);
|
|
// There are two cases where the constraint is clearly redundant:
|
|
// * If the group wasn't overconstrained and now it is;
|
|
// * If the group was overconstrained, and adding the constraint doesn't change rank at all.
|
|
if((howBefore == SolveResult::OKAY && howAfter == SolveResult::REDUNDANT_OKAY) ||
|
|
(howBefore == SolveResult::REDUNDANT_OKAY && howAfter == SolveResult::REDUNDANT_OKAY &&
|
|
rankBefore == rankAfter)) {
|
|
SK.constraint.RemoveById(hc);
|
|
hc = {};
|
|
}
|
|
return hc;
|
|
}
|
|
|
|
hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
|
|
return Constrain(Type::POINTS_COINCIDENT, ptA, ptB,
|
|
Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
|
|
}
|
|
|
|
bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc) {
|
|
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
|
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
|
Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
|
|
a2 = SK.GetEntity(arc->point[2])->PointGetNum();
|
|
if(l0.Equals(a1) || l1.Equals(a1)) {
|
|
c->other = false;
|
|
} else if(l0.Equals(a2) || l1.Equals(a2)) {
|
|
c->other = true;
|
|
} else {
|
|
Error(_("The tangent arc and line segment must share an "
|
|
"endpoint. Constrain them with Constrain -> "
|
|
"On Point before constraining tangent."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic) {
|
|
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
|
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
|
Vector as = cubic->CubicGetStartNum(),
|
|
af = cubic->CubicGetFinishNum();
|
|
|
|
if(l0.Equals(as) || l1.Equals(as)) {
|
|
c->other = false;
|
|
} else if(l0.Equals(af) || l1.Equals(af)) {
|
|
c->other = true;
|
|
} else {
|
|
Error(_("The tangent cubic and line segment must share an "
|
|
"endpoint. Constrain them with Constrain -> "
|
|
"On Point before constraining tangent."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB) {
|
|
Vector as = eA->EndpointStart(),
|
|
af = eA->EndpointFinish(),
|
|
bs = eB->EndpointStart(),
|
|
bf = eB->EndpointFinish();
|
|
if(as.Equals(bs)) {
|
|
c->other = false;
|
|
c->other2 = false;
|
|
} else if(as.Equals(bf)) {
|
|
c->other = false;
|
|
c->other2 = true;
|
|
} else if(af.Equals(bs)) {
|
|
c->other = true;
|
|
c->other2 = false;
|
|
} else if(af.Equals(bf)) {
|
|
c->other = true;
|
|
c->other2 = true;
|
|
} else {
|
|
Error(_("The curves must share an endpoint. Constrain them "
|
|
"with Constrain -> On Point before constraining "
|
|
"tangent."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Constraint::MenuConstrain(Command id) {
|
|
Constraint c = {};
|
|
c.group = SS.GW.activeGroup;
|
|
c.workplane = SS.GW.ActiveWorkplane();
|
|
|
|
SS.GW.GroupSelection();
|
|
auto const &gs = SS.GW.gs;
|
|
|
|
switch(id) {
|
|
case Command::DISTANCE_DIA:
|
|
case Command::REF_DISTANCE: {
|
|
if(gs.points == 2 && gs.n == 2) {
|
|
c.type = Type::PT_PT_DISTANCE;
|
|
c.ptA = gs.point[0];
|
|
c.ptB = gs.point[1];
|
|
} else if(gs.lineSegments == 1 && gs.n == 1) {
|
|
c.type = Type::PT_PT_DISTANCE;
|
|
Entity *e = SK.GetEntity(gs.entity[0]);
|
|
c.ptA = e->point[0];
|
|
c.ptB = e->point[1];
|
|
} else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) {
|
|
c.type = Type::PROJ_PT_DISTANCE;
|
|
c.ptA = gs.point[0];
|
|
c.ptB = gs.point[1];
|
|
c.entityA = gs.vector[0];
|
|
} else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) {
|
|
c.type = Type::PT_PLANE_DISTANCE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.entity[0];
|
|
} else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
|
|
c.type = Type::PT_LINE_DISTANCE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.entity[0];
|
|
} else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
|
|
c.type = Type::PT_FACE_DISTANCE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.face[0];
|
|
} else if(gs.circlesOrArcs == 1 && gs.n == 1) {
|
|
c.type = Type::DIAMETER;
|
|
c.entityA = gs.entity[0];
|
|
} else {
|
|
Error(_("Bad selection for distance / diameter constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two points (distance between points)\n"
|
|
" * a line segment (length)\n"
|
|
" * two points and a line segment or normal (projected distance)\n"
|
|
" * a workplane and a point (minimum distance)\n"
|
|
" * a line segment and a point (minimum distance)\n"
|
|
" * a plane face and a point (minimum distance)\n"
|
|
" * a circle or an arc (diameter)\n"));
|
|
return;
|
|
}
|
|
if(c.type == Type::PT_PT_DISTANCE || c.type == Type::PROJ_PT_DISTANCE) {
|
|
Vector n = SS.GW.projRight.Cross(SS.GW.projUp);
|
|
Vector a = SK.GetEntity(c.ptA)->PointGetNum();
|
|
Vector b = SK.GetEntity(c.ptB)->PointGetNum();
|
|
c.disp.offset = n.Cross(a.Minus(b));
|
|
c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale);
|
|
} else {
|
|
c.disp.offset = Vector::From(0, 0, 0);
|
|
}
|
|
|
|
if(id == Command::REF_DISTANCE) {
|
|
c.reference = true;
|
|
}
|
|
|
|
c.valA = 0;
|
|
c.ModifyToSatisfy();
|
|
AddConstraint(&c);
|
|
break;
|
|
}
|
|
|
|
case Command::ON_ENTITY:
|
|
if(gs.points == 2 && gs.n == 2) {
|
|
c.type = Type::POINTS_COINCIDENT;
|
|
c.ptA = gs.point[0];
|
|
c.ptB = gs.point[1];
|
|
} else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
|
|
c.type = Type::PT_IN_PLANE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.entity[0];
|
|
} else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
|
c.type = Type::PT_ON_LINE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.entity[0];
|
|
} else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
|
|
c.type = Type::PT_ON_CIRCLE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.entity[0];
|
|
} else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) {
|
|
c.type = Type::PT_ON_FACE;
|
|
c.ptA = gs.point[0];
|
|
c.entityA = gs.face[0];
|
|
} else {
|
|
Error(_("Bad selection for on point / curve / plane constraint. "
|
|
"This constraint can apply to:\n\n"
|
|
" * two points (points coincident)\n"
|
|
" * a point and a workplane (point in plane)\n"
|
|
" * a point and a line segment (point on line)\n"
|
|
" * a point and a circle or arc (point on curve)\n"
|
|
" * a point and a plane face (point on face)\n"));
|
|
return;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::EQUAL:
|
|
if(gs.lineSegments == 2 && gs.n == 2) {
|
|
c.type = Type::EQUAL_LENGTH_LINES;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
} else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
|
|
c.type = Type::EQ_PT_LN_DISTANCES;
|
|
c.entityA = gs.entity[0];
|
|
c.ptA = gs.point[0];
|
|
c.entityB = gs.entity[1];
|
|
c.ptB = gs.point[1];
|
|
} else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
|
|
// The same line segment for the distances, but different
|
|
// points.
|
|
c.type = Type::EQ_PT_LN_DISTANCES;
|
|
c.entityA = gs.entity[0];
|
|
c.ptA = gs.point[0];
|
|
c.entityB = gs.entity[0];
|
|
c.ptB = gs.point[1];
|
|
} else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
|
|
c.type = Type::EQ_LEN_PT_LINE_D;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
c.ptA = gs.point[0];
|
|
} else if(gs.vectors == 4 && gs.n == 4) {
|
|
c.type = Type::EQUAL_ANGLE;
|
|
c.entityA = gs.vector[0];
|
|
c.entityB = gs.vector[1];
|
|
c.entityC = gs.vector[2];
|
|
c.entityD = gs.vector[3];
|
|
} else if(gs.vectors == 3 && gs.n == 3) {
|
|
c.type = Type::EQUAL_ANGLE;
|
|
c.entityA = gs.vector[0];
|
|
c.entityB = gs.vector[1];
|
|
c.entityC = gs.vector[1];
|
|
c.entityD = gs.vector[2];
|
|
} else if(gs.circlesOrArcs == 2 && gs.n == 2) {
|
|
c.type = Type::EQUAL_RADIUS;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
} else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
|
c.type = Type::EQUAL_LINE_ARC_LEN;
|
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
|
c.entityA = gs.entity[1];
|
|
c.entityB = gs.entity[0];
|
|
} else {
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
} else {
|
|
Error(_("Bad selection for equal length / radius constraint. "
|
|
"This constraint can apply to:\n\n"
|
|
" * two line segments (equal length)\n"
|
|
" * two line segments and two points "
|
|
"(equal point-line distances)\n"
|
|
" * a line segment and two points "
|
|
"(equal point-line distances)\n"
|
|
" * a line segment, and a point and line segment "
|
|
"(point-line distance equals length)\n"
|
|
" * four line segments or normals "
|
|
"(equal angle between A,B and C,D)\n"
|
|
" * three line segments or normals "
|
|
"(equal angle between A,B and B,C)\n"
|
|
" * two circles or arcs (equal radius)\n"
|
|
" * a line segment and an arc "
|
|
"(line segment length equals arc length)\n"));
|
|
return;
|
|
}
|
|
if(c.type == Type::EQUAL_ANGLE) {
|
|
// Infer the nearest supplementary angle from the sketch.
|
|
Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
|
|
b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
|
|
a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
|
|
b2 = SK.GetEntity(c.entityD)->VectorGetNum();
|
|
double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
|
|
|
|
if(d1*d2 < 0) {
|
|
c.other = true;
|
|
}
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::RATIO:
|
|
if(gs.lineSegments == 2 && gs.n == 2) {
|
|
c.type = Type::LENGTH_RATIO;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
else if(gs.arcs == 2 && gs.n == 2) {
|
|
c.type = Type::ARC_ARC_LEN_RATIO;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
|
c.type = Type::ARC_LINE_LEN_RATIO;
|
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
|
c.entityA = gs.entity[1];
|
|
c.entityB = gs.entity[0];
|
|
} else {
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
} else {
|
|
Error(_("Bad selection for length ratio constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two line segments\n"
|
|
" * two arcs\n"
|
|
" * one arc and one line segment\n"));
|
|
return;
|
|
}
|
|
|
|
c.valA = 0;
|
|
c.ModifyToSatisfy();
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::DIFFERENCE:
|
|
if(gs.lineSegments == 2 && gs.n == 2) {
|
|
c.type = Type::LENGTH_DIFFERENCE;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
else if(gs.arcs == 2 && gs.n == 2) {
|
|
c.type = Type::ARC_ARC_DIFFERENCE;
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
|
c.type = Type::ARC_LINE_DIFFERENCE;
|
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
|
c.entityA = gs.entity[1];
|
|
c.entityB = gs.entity[0];
|
|
} else {
|
|
c.entityA = gs.entity[0];
|
|
c.entityB = gs.entity[1];
|
|
}
|
|
} else {
|
|
Error(_("Bad selection for length difference constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two line segments\n"
|
|
" * two arcs\n"
|
|
" * one arc and one line segment\n"));
|
|
return;
|
|
}
|
|
|
|
c.valA = 0;
|
|
c.ModifyToSatisfy();
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::AT_MIDPOINT:
|
|
if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
|
|
c.type = Type::AT_MIDPOINT;
|
|
c.entityA = gs.entity[0];
|
|
c.ptA = gs.point[0];
|
|
|
|
// If a point is at-midpoint, then no reason to also constrain
|
|
// it on-line; so auto-remove that. Handle as one undo group.
|
|
SS.UndoRemember();
|
|
DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
|
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
|
break;
|
|
} else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
|
|
c.type = Type::AT_MIDPOINT;
|
|
int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
|
|
c.entityA = gs.entity[i];
|
|
c.entityB = gs.entity[1-i];
|
|
} else {
|
|
Error(_("Bad selection for at midpoint constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * a line segment and a point "
|
|
"(point at midpoint)\n"
|
|
" * a line segment and a workplane "
|
|
"(line's midpoint on plane)\n"));
|
|
return;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::SYMMETRIC:
|
|
if(gs.points == 2 &&
|
|
((gs.workplanes == 1 && gs.n == 3) ||
|
|
(gs.n == 2)))
|
|
{
|
|
if(gs.entities > 0)
|
|
c.entityA = gs.entity[0];
|
|
c.ptA = gs.point[0];
|
|
c.ptB = gs.point[1];
|
|
c.type = Type::SYMMETRIC;
|
|
} else if(gs.lineSegments == 1 &&
|
|
((gs.workplanes == 1 && gs.n == 2) ||
|
|
(gs.n == 1)))
|
|
{
|
|
Entity *line;
|
|
if(SK.GetEntity(gs.entity[0])->IsWorkplane()) {
|
|
line = SK.GetEntity(gs.entity[1]);
|
|
c.entityA = gs.entity[0];
|
|
} else {
|
|
line = SK.GetEntity(gs.entity[0]);
|
|
}
|
|
c.ptA = line->point[0];
|
|
c.ptB = line->point[1];
|
|
c.type = Type::SYMMETRIC;
|
|
} else if(SS.GW.LockedInWorkplane()
|
|
&& gs.lineSegments == 2 && gs.n == 2)
|
|
{
|
|
Entity *l0 = SK.GetEntity(gs.entity[0]),
|
|
*l1 = SK.GetEntity(gs.entity[1]);
|
|
|
|
if((l1->group != SS.GW.activeGroup) ||
|
|
(l1->construction && !(l0->construction)))
|
|
{
|
|
swap(l0, l1);
|
|
}
|
|
c.ptA = l1->point[0];
|
|
c.ptB = l1->point[1];
|
|
c.entityA = l0->h;
|
|
c.type = Type::SYMMETRIC_LINE;
|
|
} else if(SS.GW.LockedInWorkplane()
|
|
&& gs.lineSegments == 1 && gs.points == 2 && gs.n == 3)
|
|
{
|
|
c.ptA = gs.point[0];
|
|
c.ptB = gs.point[1];
|
|
c.entityA = gs.entity[0];
|
|
c.type = Type::SYMMETRIC_LINE;
|
|
} else {
|
|
Error(_("Bad selection for symmetric constraint. This constraint "
|
|
"can apply to:\n\n"
|
|
" * two points or a line segment "
|
|
"(symmetric about workplane's coordinate axis)\n"
|
|
" * line segment, and two points or a line segment "
|
|
"(symmetric about line segment)\n"
|
|
" * workplane, and two points or a line segment "
|
|
"(symmetric about workplane)\n"));
|
|
return;
|
|
}
|
|
// We may remove constraints so remember manually
|
|
if(c.entityA == Entity::NO_ENTITY) {
|
|
// Horizontal / vertical symmetry, implicit symmetry plane
|
|
// normal to the workplane
|
|
if(c.workplane == Entity::FREE_IN_3D) {
|
|
Error(_("A workplane must be active when constraining "
|
|
"symmetric without an explicit symmetry plane."));
|
|
return;
|
|
}
|
|
Vector pa = SK.GetEntity(c.ptA)->PointGetNum();
|
|
Vector pb = SK.GetEntity(c.ptB)->PointGetNum();
|
|
Vector dp = pa.Minus(pb);
|
|
EntityBase *norm = SK.GetEntity(c.workplane)->Normal();;
|
|
Vector u = norm->NormalU(), v = norm->NormalV();
|
|
if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) {
|
|
c.type = Type::SYMMETRIC_HORIZ;
|
|
} else {
|
|
c.type = Type::SYMMETRIC_VERT;
|
|
}
|
|
if(gs.lineSegments == 1) {
|
|
// If this line segment is already constrained horiz or
|
|
// vert, then auto-remove that redundant constraint.
|
|
// Handle as one undo group.
|
|
SS.UndoRemember();
|
|
DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]),
|
|
Entity::NO_ENTITY);
|
|
DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
|
|
Entity::NO_ENTITY);
|
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
|
break;
|
|
}
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::VERTICAL:
|
|
case Command::HORIZONTAL: {
|
|
hEntity ha, hb;
|
|
if(c.workplane == Entity::FREE_IN_3D) {
|
|
Error(_("Activate a workplane (with Sketch -> In Workplane) before "
|
|
"applying a horizontal or vertical constraint."));
|
|
return;
|
|
}
|
|
if(gs.lineSegments == 1 && gs.n == 1) {
|
|
c.entityA = gs.entity[0];
|
|
Entity *e = SK.GetEntity(c.entityA);
|
|
ha = e->point[0];
|
|
hb = e->point[1];
|
|
} else if(gs.points == 2 && gs.n == 2) {
|
|
ha = c.ptA = gs.point[0];
|
|
hb = c.ptB = gs.point[1];
|
|
} else {
|
|
Error(_("Bad selection for horizontal / vertical constraint. "
|
|
"This constraint can apply to:\n\n"
|
|
" * two points\n"
|
|
" * a line segment\n"));
|
|
return;
|
|
}
|
|
if(id == Command::HORIZONTAL) {
|
|
c.type = Type::HORIZONTAL;
|
|
} else {
|
|
c.type = Type::VERTICAL;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
}
|
|
|
|
case Command::ORIENTED_SAME: {
|
|
if(gs.anyNormals == 2 && gs.n == 2) {
|
|
c.type = Type::SAME_ORIENTATION;
|
|
c.entityA = gs.anyNormal[0];
|
|
c.entityB = gs.anyNormal[1];
|
|
} else {
|
|
Error(_("Bad selection for same orientation constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two normals\n"));
|
|
return;
|
|
}
|
|
SS.UndoRemember();
|
|
|
|
Entity *nfree = SK.GetEntity(c.entityA);
|
|
Entity *nref = SK.GetEntity(c.entityB);
|
|
if(nref->group == SS.GW.activeGroup) {
|
|
swap(nref, nfree);
|
|
}
|
|
if(nfree->group == SS.GW.activeGroup && nref->group != SS.GW.activeGroup) {
|
|
// nfree is free, and nref is locked (since it came from a
|
|
// previous group); so let's force nfree aligned to nref,
|
|
// and make convergence easy
|
|
Vector ru = nref ->NormalU(), rv = nref ->NormalV();
|
|
Vector fu = nfree->NormalU(), fv = nfree->NormalV();
|
|
|
|
if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) {
|
|
// There might be an odd*90 degree rotation about the
|
|
// normal vector; allow that, since the numerical
|
|
// constraint does
|
|
swap(ru, rv);
|
|
}
|
|
fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1);
|
|
fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1);
|
|
|
|
nfree->NormalForceTo(Quaternion::From(fu, fv));
|
|
}
|
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
|
break;
|
|
}
|
|
|
|
case Command::OTHER_ANGLE:
|
|
if(gs.constraints == 1 && gs.n == 0) {
|
|
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
|
if(c->type == Type::ANGLE) {
|
|
SS.UndoRemember();
|
|
c->other = !(c->other);
|
|
c->ModifyToSatisfy();
|
|
break;
|
|
}
|
|
if(c->type == Type::EQUAL_ANGLE) {
|
|
SS.UndoRemember();
|
|
c->other = !(c->other);
|
|
SS.MarkGroupDirty(c->group);
|
|
break;
|
|
}
|
|
}
|
|
Error(_("Must select an angle constraint."));
|
|
return;
|
|
|
|
case Command::REFERENCE:
|
|
if(gs.constraints == 1 && gs.n == 0) {
|
|
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
|
if(c->HasLabel() && c->type != Type::COMMENT) {
|
|
SS.UndoRemember();
|
|
(c->reference) = !(c->reference);
|
|
SS.MarkGroupDirty(c->group, /*onlyThis=*/true);
|
|
break;
|
|
}
|
|
}
|
|
Error(_("Must select a constraint with associated label."));
|
|
return;
|
|
|
|
case Command::ANGLE:
|
|
case Command::REF_ANGLE: {
|
|
if(gs.vectors == 2 && gs.n == 2) {
|
|
c.type = Type::ANGLE;
|
|
c.entityA = gs.vector[0];
|
|
c.entityB = gs.vector[1];
|
|
c.valA = 0;
|
|
} else {
|
|
Error(_("Bad selection for angle constraint. This constraint "
|
|
"can apply to:\n\n"
|
|
" * two line segments\n"
|
|
" * a line segment and a normal\n"
|
|
" * two normals\n"));
|
|
return;
|
|
}
|
|
|
|
Entity *ea = SK.GetEntity(c.entityA),
|
|
*eb = SK.GetEntity(c.entityB);
|
|
if(ea->type == Entity::Type::LINE_SEGMENT &&
|
|
eb->type == Entity::Type::LINE_SEGMENT)
|
|
{
|
|
Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(),
|
|
a1 = SK.GetEntity(ea->point[1])->PointGetNum(),
|
|
b0 = SK.GetEntity(eb->point[0])->PointGetNum(),
|
|
b1 = SK.GetEntity(eb->point[1])->PointGetNum();
|
|
if(a0.Equals(b0) || a1.Equals(b1)) {
|
|
// okay, vectors should be drawn in same sense
|
|
} else if(a0.Equals(b1) || a1.Equals(b0)) {
|
|
// vectors are in opposite sense
|
|
c.other = true;
|
|
} else {
|
|
// no shared point; not clear which intersection to draw
|
|
}
|
|
}
|
|
|
|
if(id == Command::REF_ANGLE) {
|
|
c.reference = true;
|
|
}
|
|
|
|
c.ModifyToSatisfy();
|
|
AddConstraint(&c);
|
|
break;
|
|
}
|
|
|
|
case Command::PARALLEL:
|
|
if(gs.vectors == 2 && gs.n == 2) {
|
|
c.type = Type::PARALLEL;
|
|
c.entityA = gs.vector[0];
|
|
c.entityB = gs.vector[1];
|
|
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
|
*arc = SK.GetEntity(gs.entity[1]);
|
|
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
|
|
swap(line, arc);
|
|
}
|
|
if(!ConstrainArcLineTangent(&c, line, arc)) {
|
|
return;
|
|
}
|
|
c.type = Type::ARC_LINE_TANGENT;
|
|
c.entityA = arc->h;
|
|
c.entityB = line->h;
|
|
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
|
*cubic = SK.GetEntity(gs.entity[1]);
|
|
if(line->type == Entity::Type::CUBIC) {
|
|
swap(line, cubic);
|
|
}
|
|
if(!ConstrainCubicLineTangent(&c, line, cubic)) {
|
|
return;
|
|
}
|
|
c.type = Type::CUBIC_LINE_TANGENT;
|
|
c.entityA = cubic->h;
|
|
c.entityB = line->h;
|
|
} else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
|
|
if(!SS.GW.LockedInWorkplane()) {
|
|
Error(_("Curve-curve tangency must apply in workplane."));
|
|
return;
|
|
}
|
|
Entity *eA = SK.GetEntity(gs.entity[0]),
|
|
*eB = SK.GetEntity(gs.entity[1]);
|
|
if(!ConstrainCurveCurveTangent(&c, eA, eB)) {
|
|
return;
|
|
}
|
|
c.type = Type::CURVE_CURVE_TANGENT;
|
|
c.entityA = eA->h;
|
|
c.entityB = eB->h;
|
|
} else {
|
|
Error(_("Bad selection for parallel / tangent constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two line segments (parallel)\n"
|
|
" * a line segment and a normal (parallel)\n"
|
|
" * two normals (parallel)\n"
|
|
" * two line segments, arcs, or beziers, that share "
|
|
"an endpoint (tangent)\n"));
|
|
return;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::PERPENDICULAR:
|
|
if(gs.vectors == 2 && gs.n == 2) {
|
|
c.type = Type::PERPENDICULAR;
|
|
c.entityA = gs.vector[0];
|
|
c.entityB = gs.vector[1];
|
|
} else {
|
|
Error(_("Bad selection for perpendicular constraint. This "
|
|
"constraint can apply to:\n\n"
|
|
" * two line segments\n"
|
|
" * a line segment and a normal\n"
|
|
" * two normals\n"));
|
|
return;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::WHERE_DRAGGED:
|
|
if(gs.points == 1 && gs.n == 1) {
|
|
c.type = Type::WHERE_DRAGGED;
|
|
c.ptA = gs.point[0];
|
|
} else {
|
|
Error(_("Bad selection for lock point where dragged constraint. "
|
|
"This constraint can apply to:\n\n"
|
|
" * a point\n"));
|
|
return;
|
|
}
|
|
AddConstraint(&c);
|
|
break;
|
|
|
|
case Command::COMMENT:
|
|
if(gs.points == 1 && gs.n == 1) {
|
|
c.type = Type::COMMENT;
|
|
c.ptA = gs.point[0];
|
|
c.group = SS.GW.activeGroup;
|
|
c.workplane = SS.GW.ActiveWorkplane();
|
|
c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
|
|
AddConstraint(&c);
|
|
} else {
|
|
SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
|
|
SS.GW.pending.command = Command::COMMENT;
|
|
SS.GW.pending.description = _("click center of comment text");
|
|
SS.ScheduleShowTW();
|
|
}
|
|
break;
|
|
|
|
default: ssassert(false, "Unexpected menu ID");
|
|
}
|
|
|
|
for(const Constraint &cc : SK.constraint) {
|
|
if(c.h != cc.h && c.Equals(cc)) {
|
|
// Oops, we already have this exact constraint. Remove the one we just added.
|
|
SK.constraint.RemoveById(c.h);
|
|
SS.GW.ClearSelection();
|
|
// And now select the old one, to give feedback.
|
|
SS.GW.MakeSelected(cc.h);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(SK.constraint.FindByIdNoOops(c.h)) {
|
|
Constraint *constraint = SK.GetConstraint(c.h);
|
|
if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY &&
|
|
!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
|
|
constraint->HasLabel()) {
|
|
constraint->reference = true;
|
|
}
|
|
}
|
|
|
|
if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
|
|
id == Command::RATIO || id == Command::DIFFERENCE) &&
|
|
SS.immediatelyEditDimension) {
|
|
SS.GW.EditConstraint(c.h);
|
|
}
|
|
|
|
SS.GW.ClearSelection();
|
|
}
|
|
|
|
#endif /* ! LIBRARY */
|