solvespace/src/constraint.cpp
ruevs 7e08b02de1 Allow comments to be associated with point entities
If a single point is selected when a "Constrain | Comment" (`;`) is added
then the comment is associated with the point and its position becomes
relative to the point. In this way the comment will move with the point.

If nothing is selected or more than a single point is selected then
the behaviour is as before and the comment is "floating".

Closes #1032
2021-05-12 19:22:46 -04:00

818 lines
34 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::LENGTH_DIFFERENCE: s = C_("constr-name", "length-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 {
Error(_("Bad selection for length ratio constraint. This "
"constraint can apply to:\n\n"
" * two line segments\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 {
Error(_("Bad selection for length difference constraint. This "
"constraint can apply to:\n\n"
" * two line segments\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 */