Add angle constraints. I'm doing these differently from SketchFlat,

as a constraint on the direction cosine, rather than driving the
dot product against a rotated vector to zero. The drawing is the
ugly part; to do that for skew lines, I gave up.

Also add a function to clear non-existent items on the selection
after solving, since that could have caused an oops().

[git-p4: depot-paths = "//depot/solvespace/": change = 1727]
solver
Jonathan Westhues 2008-05-17 03:15:14 -08:00
parent 749e2a0149
commit b480613763
11 changed files with 237 additions and 37 deletions

View File

@ -237,6 +237,33 @@ void Constraint::MenuConstrain(int id) {
AddConstraint(&c);
break;
case GraphicsWindow::MNU_OTHER_ANGLE:
if(gs.constraints == 1 && gs.n == 0) {
Constraint *c = SS.GetConstraint(gs.constraint[0]);
if(c->type == ANGLE) {
c->otherAngle = !(c->otherAngle);
c->ModifyToSatisfy();
break;
}
}
Error("Must select an angle constraint.");
break;
case GraphicsWindow::MNU_ANGLE:
if(gs.vectors == 2 && gs.n == 2) {
c.type = ANGLE;
c.entityA = gs.vector[0];
c.entityB = gs.vector[1];
c.exprA = Expr::FromConstant(0)->DeepCopyKeep();
c.otherAngle = true;
} else {
Error("Bad selection for angle constraint.");
return;
}
c.ModifyToSatisfy();
AddConstraint(&c);
break;
case GraphicsWindow::MNU_PARALLEL:
if(gs.vectors == 2 && gs.n == 2) {
c.type = PARALLEL;
@ -374,6 +401,21 @@ ExprVector Constraint::PointInThreeSpace(hEntity workplane, Expr *u, Expr *v) {
}
void Constraint::ModifyToSatisfy(void) {
if(type == ANGLE) {
Vector a = SS.GetEntity(entityA)->VectorGetNum();
Vector b = SS.GetEntity(entityB)->VectorGetNum();
if(otherAngle) a = a.ScaledBy(-1);
if(workplane.v != Entity::FREE_IN_3D.v) {
a = a.ProjectVectorInto(workplane);
b = b.ProjectVectorInto(workplane);
}
double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude());
double theta = acos(c)*180/PI;
Expr::FreeKeep(&exprA);
exprA = Expr::FromConstant(theta)->DeepCopyKeep();
} else {
// We'll fix these ones up by looking at their symbolic equation;
// that means no extra work.
IdList<Equation,hEquation> l;
// An uninit IdList could lead us to free some random address, bad.
memset(&l, 0, sizeof(l));
@ -389,6 +431,7 @@ void Constraint::ModifyToSatisfy(void) {
exprA = Expr::FromConstant(nd)->DeepCopyKeep();
l.Clear();
}
}
void Constraint::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
@ -399,19 +442,22 @@ void Constraint::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
}
void Constraint::Generate(IdList<Equation,hEquation> *l) {
Expr *exA = NULL;
if(exprA) exA = exprA->DeepCopy();
switch(type) {
case PT_PT_DISTANCE:
AddEq(l, Distance(workplane, ptA, ptB)->Minus(exprA), 0);
AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0);
break;
case PT_LINE_DISTANCE:
AddEq(l,
PointLineDistance(workplane, ptA, entityA)->Minus(exprA), 0);
PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0);
break;
case PT_PLANE_DISTANCE: {
ExprVector pt = SS.GetEntity(ptA)->PointGetExprs();
AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exprA), 0);
AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0);
break;
}
@ -428,14 +474,14 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
Entity *b = SS.GetEntity(entityB);
Expr *la = Distance(workplane, a->point[0], a->point[1]);
Expr *lb = Distance(workplane, b->point[0], b->point[1]);
AddEq(l, (la->Div(lb))->Minus(exprA), 0);
AddEq(l, (la->Div(lb))->Minus(exA), 0);
break;
}
case DIAMETER: {
Entity *circle = SS.GetEntity(entityA);
Expr *r = circle->CircleGetRadiusExpr();
AddEq(l, (r->Times(Expr::FromConstant(2)))->Minus(exprA), 0);
AddEq(l, (r->Times(Expr::FromConstant(2)))->Minus(exA), 0);
break;
}
@ -656,6 +702,34 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
break;
}
case ANGLE: {
Entity *a = SS.GetEntity(entityA);
Entity *b = SS.GetEntity(entityB);
ExprVector ae = a->VectorGetExprs();
ExprVector be = b->VectorGetExprs();
if(otherAngle) ae = ae.ScaledBy(Expr::FromConstant(-1));
Expr *c;
if(workplane.v == Entity::FREE_IN_3D.v) {
Expr *mags = (ae.Magnitude())->Times(be.Magnitude());
c = (ae.Dot(be))->Div(mags);
} else {
Entity *w = SS.GetEntity(workplane);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
Expr *ua = u.Dot(ae);
Expr *va = v.Dot(ae);
Expr *ub = u.Dot(be);
Expr *vb = v.Dot(be);
Expr *maga = (ua->Square()->Plus(va->Square()))->Sqrt();
Expr *magb = (ub->Square()->Plus(vb->Square()))->Sqrt();
Expr *dot = (ua->Times(ub))->Plus(va->Times(vb));
c = dot->Div(maga->Times(magb));
}
Expr *rads = exA->Times(Expr::FromConstant(PI/180));
AddEq(l, c->Minus(rads->Cos()), 0);
break;
}
case PARALLEL: {
ExprVector a = SS.GetEntity(entityA)->VectorGetExprs();
ExprVector b = SS.GetEntity(entityB)->VectorGetExprs();

View File

@ -7,6 +7,7 @@ bool Constraint::HasLabel(void) {
case PT_PT_DISTANCE:
case DIAMETER:
case LENGTH_RATIO:
case ANGLE:
return true;
default:
@ -247,6 +248,83 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
break;
}
case ANGLE: {
Entity *a = SS.GetEntity(entityA);
Entity *b = SS.GetEntity(entityB);
Vector a0 = a->VectorGetRefPoint();
Vector b0 = b->VectorGetRefPoint();
Vector da = a->VectorGetNum();
Vector db = b->VectorGetNum();
if(otherAngle) da = da.ScaledBy(-1);
if(workplane.v != Entity::FREE_IN_3D.v) {
a0 = a0.ProjectInto(workplane);
b0 = b0.ProjectInto(workplane);
da = da.ProjectVectorInto(workplane);
db = db.ProjectVectorInto(workplane);
}
// Make an orthogonal coordinate system from those directions
Vector dn = da.Cross(db); // normal to both
Vector dna = dn.Cross(da); // normal to da
Vector dnb = dn.Cross(db); // normal to db
// At the intersection of the lines
// a0 + pa*da = b0 + pb*db (where pa, pb are scalar params)
// So dot this equation against dna and dnb to get two equations
// to solve for da and db
double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna));
double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb));
Vector pi = a0.Plus(da.ScaledBy(pa));
Vector ref;
if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) {
ref = pi.Plus(disp.offset);
// We draw in a coordinate system centered at pi, with
// basis vectors da and dna.
da = da.WithMagnitude(1); dna = dna.WithMagnitude(1);
Vector rm = ref.Minus(pi);
double rda = rm.Dot(da), rdna = rm.Dot(dna);
double r = sqrt(rda*rda + rdna*rdna);
double c = (da.Dot(db))/(da.Magnitude()*db.Magnitude());
double thetaf = acos(c);
Vector m = da.ScaledBy(cos(thetaf/2)).Plus(
dna.ScaledBy(sin(thetaf/2)));
if(m.Dot(rm) < 0) {
da = da.ScaledBy(-1); dna = dna.ScaledBy(-1);
}
Vector prev = da.ScaledBy(r).Plus(pi);
int i, n = 30;
for(i = 0; i <= n; i++) {
double theta = (i*thetaf)/n;
Vector p = da. ScaledBy(r*cos(theta)).Plus(
dna.ScaledBy(r*sin(theta))).Plus(pi);
LineDrawOrGetDistance(prev, p);
prev = p;
}
double tl = atan2(rm.Dot(gu), rm.Dot(gr));
double adj = EllipticalInterpolation(
glxStrWidth(exprA->Print())/2, glxStrHeight()/2, tl);
ref = ref.Plus(rm.WithMagnitude(adj + 3/SS.GW.scale));
} else {
// The lines are skew; no wonderful way to illustrate that.
ref = a->VectorGetRefPoint().Plus(b->VectorGetRefPoint());
ref = ref.ScaledBy(0.5).Plus(disp.offset);
glPushMatrix();
gu = gu.WithMagnitude(1);
glxTranslatev(ref.Plus(gu.ScaledBy(-1.5*glxStrHeight())));
glxOntoWorkplane(gr, gu);
glxWriteTextRefCenter("angle between skew lines");
glPopMatrix();
}
DoLabel(ref, labelPos, gr, gu);
break;
}
case PARALLEL: {
for(int i = 0; i < 2; i++) {
Entity *e = SS.GetEntity(i == 0 ? entityA : entityB);

1
dsc.h
View File

@ -53,6 +53,7 @@ public:
Vector WithMagnitude(double s);
Vector ScaledBy(double s);
Vector ProjectInto(hEntity wrkpl);
Vector ProjectVectorInto(hEntity wrkpl);
double DivPivoting(Vector delta);
Vector ClosestOrtho(void);
Point2d Project2d(Vector u, Vector v);

View File

@ -112,6 +112,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = {
{ 'c', "Constraint.ptC.v", 'x', &(SS.sv.c.ptC.v) },
{ 'c', "Constraint.entityA.v", 'x', &(SS.sv.c.entityA.v) },
{ 'c', "Constraint.entityB.v", 'x', &(SS.sv.c.entityB.v) },
{ 'c', "Constraint.otherAngle", 'b', &(SS.sv.c.otherAngle) },
{ 'c', "Constraint.disp.offset.x", 'f', &(SS.sv.c.disp.offset.x) },
{ 'c', "Constraint.disp.offset.y", 'f', &(SS.sv.c.disp.offset.y) },
{ 'c', "Constraint.disp.offset.z", 'f', &(SS.sv.c.disp.offset.z) },

View File

@ -70,8 +70,8 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 0, "&Constrain", 0, NULL },
{ 1, "&Distance / Diameter\tShift+D", MNU_DISTANCE_DIA, 'D'|S, mCon },
{ 1, "A&ngle\tShift+N", 0, 'N'|S, NULL },
{ 1, "Other S&upplementary Angle\tShift+U", 0, 'U'|S, NULL },
{ 1, "A&ngle\tShift+N", MNU_ANGLE, 'N'|S, mCon },
{ 1, "Other S&upplementary Angle\tShift+U", MNU_OTHER_ANGLE, 'U'|S, mCon },
{ 1, NULL, 0, NULL },
{ 1, "&Horizontal\tShift+H", MNU_HORIZONTAL, 'H'|S, mCon },
{ 1, "&Vertical\tShift+V", MNU_VERTICAL, 'V'|S, mCon },
@ -302,8 +302,7 @@ void GraphicsWindow::MenuEdit(int id) {
SS.constraint.RemoveTagged();
// Forget any mention of the just-deleted entity
SS.GW.ClearSelection();
SS.GW.hover.Clear();
SS.GW.ClearSuper();
// And regenerate to get rid of what it generates, plus anything
// that references it (since the regen code checks for that).
SS.GW.GeneratePerSolving();
@ -633,6 +632,13 @@ void GraphicsWindow::Selection::Draw(void) {
if(constraint.v) SS.GetConstraint(constraint)->Draw();
}
void GraphicsWindow::ClearSuper(void) {
ClearPending();
ClearSelection();
hover.Clear();
EnsureValidActives();
}
void GraphicsWindow::ClearPending(void) {
memset(&pending, 0, sizeof(pending));
}
@ -680,6 +686,22 @@ void GraphicsWindow::ClearSelection(void) {
InvalidateGraphics();
}
void GraphicsWindow::ClearNonexistentSelectionItems(void) {
bool change = false;
for(int i = 0; i < MAX_SELECTED; i++) {
Selection *s = &(selection[i]);
if(s->constraint.v && !(SS.constraint.FindByIdNoOops(s->constraint))) {
s->constraint.v = 0;
change = true;
}
if(s->entity.v && !(SS.entity.FindByIdNoOops(s->entity))) {
s->entity.v = 0;
change = true;
}
}
if(change) InvalidateGraphics();
}
void GraphicsWindow::GroupSelection(void) {
memset(&gs, 0, sizeof(gs));
int i;
@ -718,6 +740,9 @@ void GraphicsWindow::GroupSelection(void) {
case Entity::CIRCLE: (gs.circlesOrArcs)++; break;
}
}
if(s->constraint.v) {
gs.constraint[(gs.constraints)++] = s->constraint;
}
}
}
@ -794,7 +819,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
hr = AddRequest(Request::DATUM_POINT);
SS.GetEntity(hr.entity(0))->PointForceTo(v);
ClearSelection(); hover.Clear();
ClearSuper();
pending.operation = 0;
break;
@ -804,7 +829,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SS.GetEntity(hr.entity(1))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(1));
ClearSelection(); hover.Clear();
ClearSuper();
pending.operation = DRAGGING_NEW_LINE_POINT;
pending.point = hr.entity(2);
@ -848,9 +873,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp));
ConstrainPointByHovered(hr.entity(1));
ClearSelection(); hover.Clear();
ClearSuper();
ClearPending();
pending.operation = DRAGGING_NEW_RADIUS;
pending.circle = hr.entity(0);
pending.description = "click to set radius";
@ -869,9 +893,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SS.GetEntity(hr.entity(3))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(2));
ClearSelection(); hover.Clear();
ClearSuper();
ClearPending();
pending.operation = DRAGGING_NEW_ARC_POINT;
pending.point = hr.entity(3);
pending.description = "click to place point";
@ -885,7 +908,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SS.GetEntity(hr.entity(4))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(1));
ClearSelection(); hover.Clear();
ClearSuper();
pending.operation = DRAGGING_NEW_CUBIC_POINT;
pending.point = hr.entity(4);
@ -899,8 +922,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp));
ConstrainPointByHovered(hr.entity(1));
ClearSelection(); hover.Clear();
ClearPending();
ClearSuper();
break;
case DRAGGING_RADIUS:
@ -986,12 +1008,13 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {
if(GraphicsEditControlIsVisible()) return;
if(hover.constraint.v) {
ClearSelection();
Constraint *c = SS.GetConstraint(hover.constraint);
Vector p3 = c->GetLabelPos();
Point2d p2 = ProjectPoint(p3);
ShowGraphicsEditControl((int)p2.x, (int)p2.y, c->exprA->Print());
constraintBeingEdited = hover.constraint;
ClearSuper();
}
}

View File

@ -497,7 +497,9 @@ void Group::Draw(void) {
int i;
glEnable(GL_LIGHTING);
GLfloat vec[] = { 0.3f, 0.3f, 0.3f, 1.0 };
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, vec);
GLfloat vec2[] = { 1.0f, 0.3f, 0.3f, 1.0 };
glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, vec2);
for(i = 0; i < faces.n; i++) {
glxFillPolygon(&(faces.elem[i]));
#if 0

View File

@ -343,7 +343,8 @@ public:
static const int DIAMETER = 90;
static const int PT_ON_CIRCLE = 100;
static const int SAME_ORIENTATION = 110;
static const int PARALLEL = 120;
static const int ANGLE = 120;
static const int PARALLEL = 121;
static const int EQUAL_RADIUS = 130;
int tag;
@ -362,6 +363,7 @@ public:
hEntity ptC;
hEntity entityA;
hEntity entityB;
bool otherAngle;
// These define how the constraint is drawn on-screen.
struct {

View File

@ -184,6 +184,10 @@ void SolveSpace::GenerateAll(bool andSolve) {
prev.Clear();
InvalidateGraphics();
// Remove nonexistent selection items, for same reason we waited till
// the end to put up a dialog box.
GW.ClearNonexistentSelectionItems();
if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) {
// Don't display any errors until we've regenerated fully. The
// sketch is not necessarily in a consistent state until we've

View File

@ -344,6 +344,7 @@ void TextWindow::ScreenChangeExtrudeSides(int link, DWORD v) {
g->subtype = Group::EXTRUDE_ONE_SIDED;
}
SS.GW.GeneratePerSolving();
SS.GW.ClearSuper();
}
void TextWindow::ShowGroupInfo(void) {
Group *g = SS.group.FindById(shown->group);

7
ui.h
View File

@ -120,6 +120,8 @@ public:
MNU_GROUP_TRANS,
// Constrain
MNU_DISTANCE_DIA,
MNU_ANGLE,
MNU_OTHER_ANGLE,
MNU_EQUAL,
MNU_RATIO,
MNU_ON_ENTITY,
@ -227,11 +229,13 @@ public:
Selection selection[MAX_SELECTED];
void HitTestMakeSelection(Point2d mp);
void ClearSelection(void);
void ClearNonexistentSelectionItems(void);
struct {
hEntity point[MAX_SELECTED];
hEntity entity[MAX_SELECTED];
hEntity anyNormal[MAX_SELECTED];
hEntity vector[MAX_SELECTED];
hConstraint constraint[MAX_SELECTED];
int points;
int entities;
int workplanes;
@ -239,10 +243,13 @@ public:
int circlesOrArcs;
int anyNormals;
int vectors;
int constraints;
int n;
} gs;
void GroupSelection(void);
void ClearSuper(void);
// This sets what gets displayed.
bool showWorkplanes;
bool showNormals;

View File

@ -307,17 +307,24 @@ Vector Vector::WithMagnitude(double v) {
}
}
Vector Vector::ProjectInto(hEntity wrkpl) {
Vector Vector::ProjectVectorInto(hEntity wrkpl) {
Entity *w = SS.GetEntity(wrkpl);
Vector u = w->Normal()->NormalU();
Vector v = w->Normal()->NormalV();
double up = this->Dot(u);
double vp = this->Dot(v);
return (u.ScaledBy(up)).Plus(v.ScaledBy(vp));
}
Vector Vector::ProjectInto(hEntity wrkpl) {
Entity *w = SS.GetEntity(wrkpl);
Vector p0 = w->WorkplaneGetOffset();
Vector f = this->Minus(p0);
double up = f.Dot(u);
double vp = f.Dot(v);
return p0.Plus((u.ScaledBy(up)).Plus(v.ScaledBy(vp)));
return p0.Plus(f.ProjectVectorInto(wrkpl));
}
Point2d Vector::Project2d(Vector u, Vector v) {