//----------------------------------------------------------------------------- // Given a constraint, generate one or more equations in our symbolic algebra // system to represent that constraint; also various geometric helper // functions for that. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 }; bool ConstraintBase::HasLabel() const { switch(type) { case Type::PT_LINE_DISTANCE: case Type::PT_PLANE_DISTANCE: case Type::PT_FACE_DISTANCE: case Type::PT_PT_DISTANCE: case Type::PROJ_PT_DISTANCE: case Type::DIAMETER: case Type::LENGTH_RATIO: case Type::ARC_ARC_LEN_RATIO: case Type::ARC_LINE_LEN_RATIO: case Type::LENGTH_DIFFERENCE: case Type::ARC_ARC_DIFFERENCE: case Type::ARC_LINE_DIFFERENCE: case Type::ANGLE: case Type::COMMENT: return true; default: return false; } } bool ConstraintBase::IsProjectible() const { switch(type) { case Type::POINTS_COINCIDENT: case Type::PT_PT_DISTANCE: case Type::PT_LINE_DISTANCE: case Type::PT_ON_LINE: case Type::EQUAL_LENGTH_LINES: case Type::EQ_LEN_PT_LINE_D: case Type::EQ_PT_LN_DISTANCES: case Type::EQUAL_ANGLE: case Type::LENGTH_RATIO: case Type::ARC_ARC_LEN_RATIO: case Type::ARC_LINE_LEN_RATIO: case Type::LENGTH_DIFFERENCE: case Type::ARC_ARC_DIFFERENCE: case Type::ARC_LINE_DIFFERENCE: case Type::SYMMETRIC: case Type::SYMMETRIC_HORIZ: case Type::SYMMETRIC_VERT: case Type::SYMMETRIC_LINE: case Type::AT_MIDPOINT: case Type::HORIZONTAL: case Type::VERTICAL: case Type::ANGLE: case Type::PARALLEL: case Type::PERPENDICULAR: case Type::WHERE_DRAGGED: case Type::COMMENT: return true; case Type::PT_PLANE_DISTANCE: case Type::PT_FACE_DISTANCE: case Type::PROJ_PT_DISTANCE: case Type::PT_IN_PLANE: case Type::PT_ON_FACE: case Type::EQUAL_LINE_ARC_LEN: case Type::DIAMETER: case Type::PT_ON_CIRCLE: case Type::SAME_ORIENTATION: case Type::CUBIC_LINE_TANGENT: case Type::CURVE_CURVE_TANGENT: case Type::ARC_LINE_TANGENT: case Type::EQUAL_RADIUS: return false; } ssassert(false, "Impossible"); } ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) { return a.Minus(b.ScaledBy(Expr::From(p))); } Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln) { EntityBase *ln = SK.GetEntity(hln); EntityBase *a = SK.GetEntity(ln->point[0]); EntityBase *b = SK.GetEntity(ln->point[1]); EntityBase *p = SK.GetEntity(hpt); if(wrkpl == EntityBase::FREE_IN_3D) { ExprVector ep = p->PointGetExprs(); ExprVector ea = a->PointGetExprs(); ExprVector eb = b->PointGetExprs(); ExprVector eab = ea.Minus(eb); Expr *m = eab.Magnitude(); return ((eab.Cross(ea.Minus(ep))).Magnitude())->Div(m); } else { Expr *ua, *va, *ub, *vb; a->PointGetExprsInWorkplane(wrkpl, &ua, &va); b->PointGetExprsInWorkplane(wrkpl, &ub, &vb); Expr *du = ua->Minus(ub); Expr *dv = va->Minus(vb); Expr *u, *v; p->PointGetExprsInWorkplane(wrkpl, &u, &v); Expr *m = ((du->Square())->Plus(dv->Square()))->Sqrt(); Expr *proj = (dv->Times(ua->Minus(u)))->Minus( (du->Times(va->Minus(v)))); return proj->Div(m); } } Expr *ConstraintBase::PointPlaneDistance(ExprVector p, hEntity hpl) { ExprVector n; Expr *d; SK.GetEntity(hpl)->WorkplaneGetPlaneExprs(&n, &d); return (p.Dot(n))->Minus(d); } Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) { EntityBase *pa = SK.GetEntity(hpa); EntityBase *pb = SK.GetEntity(hpb); ssassert(pa->IsPoint() && pb->IsPoint(), "Expected two points to measure projected distance between"); if(wrkpl == EntityBase::FREE_IN_3D) { // This is true distance ExprVector ea, eb, eab; ea = pa->PointGetExprs(); eb = pb->PointGetExprs(); eab = ea.Minus(eb); return eab.Magnitude(); } else { // This is projected distance, in the given workplane. Expr *au, *av, *bu, *bv; pa->PointGetExprsInWorkplane(wrkpl, &au, &av); pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv); Expr *du = au->Minus(bu); Expr *dv = av->Minus(bv); return ((du->Square())->Plus(dv->Square()))->Sqrt(); } } //----------------------------------------------------------------------------- // Return the cosine of the angle between two vectors. If a workplane is // specified, then it's the cosine of their projections into that workplane. //----------------------------------------------------------------------------- Expr *ConstraintBase::DirectionCosine(hEntity wrkpl, ExprVector ae, ExprVector be) { if(wrkpl == EntityBase::FREE_IN_3D) { Expr *mags = (ae.Magnitude())->Times(be.Magnitude()); return (ae.Dot(be))->Div(mags); } else { EntityBase *w = SK.GetEntity(wrkpl); 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)); return dot->Div(maga->Times(magb)); } } ExprVector ConstraintBase::PointInThreeSpace(hEntity workplane, Expr *u, Expr *v) { EntityBase *w = SK.GetEntity(workplane); ExprVector ub = w->Normal()->NormalExprsU(); ExprVector vb = w->Normal()->NormalExprsV(); ExprVector ob = w->WorkplaneGetOffsetExprs(); return (ub.ScaledBy(u)).Plus(vb.ScaledBy(v)).Plus(ob); } void ConstraintBase::ModifyToSatisfy() { if(type == Type::ANGLE) { Vector a = SK.GetEntity(entityA)->VectorGetNum(); Vector b = SK.GetEntity(entityB)->VectorGetNum(); if(other) a = a.ScaledBy(-1); if(workplane != EntityBase::FREE_IN_3D) { a = a.ProjectVectorInto(workplane); b = b.ProjectVectorInto(workplane); } double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); valA = acos(c)*180/PI; } else if(type == Type::PT_ON_LINE) { EntityBase *eln = SK.GetEntity(entityA); EntityBase *ea = SK.GetEntity(eln->point[0]); EntityBase *eb = SK.GetEntity(eln->point[1]); EntityBase *ep = SK.GetEntity(ptA); ExprVector exp = ep->PointGetExprsInWorkplane(workplane); ExprVector exa = ea->PointGetExprsInWorkplane(workplane); ExprVector exb = eb->PointGetExprsInWorkplane(workplane); ExprVector exba = exb.Minus(exa); SK.GetParam(valP)->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval(); } else { // We'll fix these ones up by looking at their symbolic equation; // that means no extra work. IdList l = {}; // Generate the equations even if this is a reference dimension GenerateEquations(&l, /*forReference=*/true); ssassert(l.n == 1, "Expected constraint to generate a single equation"); // These equations are written in the form f(...) - d = 0, where // d is the value of the valA. valA += (l[0].e)->Eval(); l.Clear(); } } void ConstraintBase::AddEq(IdList *l, Expr *expr, int index) const { Equation eq; eq.e = expr; eq.h = h.equation(index); l->Add(&eq); } void ConstraintBase::AddEq(IdList *l, const ExprVector &v, int baseIndex) const { AddEq(l, v.x, baseIndex); AddEq(l, v.y, baseIndex + 1); if(workplane == EntityBase::FREE_IN_3D) { AddEq(l, v.z, baseIndex + 2); } } void ConstraintBase::Generate(IdList *l) { switch(type) { case Type::PARALLEL: case Type::CUBIC_LINE_TANGENT: // Add new parameter only when we operate in 3d space if(workplane != EntityBase::FREE_IN_3D) break; // fallthrough case Type::SAME_ORIENTATION: case Type::PT_ON_LINE: { Param p = {}; valP = h.param(0); p.h = valP; l->Add(&p); break; } default: break; } } void ConstraintBase::GenerateEquations(IdList *l, bool forReference) const { if(reference && !forReference) return; Expr *exA = Expr::From(valA); switch(type) { case Type::PT_PT_DISTANCE: AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0); return; case Type::PROJ_PT_DISTANCE: { ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(), pB = SK.GetEntity(ptB)->PointGetExprs(), dp = pB.Minus(pA); ExprVector pp = SK.GetEntity(entityA)->VectorGetExprs(); pp = pp.WithMagnitude(Expr::From(1.0)); AddEq(l, (dp.Dot(pp))->Minus(exA), 0); return; } case Type::PT_LINE_DISTANCE: AddEq(l, PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0); return; case Type::PT_PLANE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0); return; } case Type::PT_FACE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0); return; } case Type::EQUAL_LENGTH_LINES: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus( Distance(workplane, b->point[0], b->point[1])), 0); return; } // These work on distance squared, since the pt-line distances are // signed, and we want the absolute value. case Type::EQ_LEN_PT_LINE_D: { EntityBase *forLen = SK.GetEntity(entityA); Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]); Expr *d2 = PointLineDistance(workplane, ptA, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); return; } case Type::EQ_PT_LN_DISTANCES: { Expr *d1 = PointLineDistance(workplane, ptA, entityA); Expr *d2 = PointLineDistance(workplane, ptB, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); return; } case Type::LENGTH_RATIO: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.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(exA), 0); return; } case Type::ARC_ARC_LEN_RATIO: { EntityBase *arc1 = SK.GetEntity(entityA), *arc2 = SK.GetEntity(entityB); // And get the arc1 radius, and the cosine of its angle EntityBase *ao1 = SK.GetEntity(arc1->point[0]), *as1 = SK.GetEntity(arc1->point[1]), *af1 = SK.GetEntity(arc1->point[2]); ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); Expr *r1 = aof1.Magnitude(); ExprVector n1 = arc1->Normal()->NormalExprsN(); ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); ExprVector v1 = n1.Cross(u1); // so in our new csys, we start at (1, 0, 0) Expr *costheta1 = aof1.Dot(u1)->Div(r1); Expr *sintheta1 = aof1.Dot(v1)->Div(r1); double thetas1, thetaf1, dtheta1; arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); Expr *theta1; if(dtheta1 < 3*PI/4) { theta1 = costheta1->ACos(); } else if(dtheta1 < 5*PI/4) { // As the angle crosses pi, cos theta1 is not invertible; // so use the sine to stop blowing up theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); } else { theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); } // And get the arc2 radius, and the cosine of its angle EntityBase *ao2 = SK.GetEntity(arc2->point[0]), *as2 = SK.GetEntity(arc2->point[1]), *af2 = SK.GetEntity(arc2->point[2]); ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()), aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs()); Expr *r2 = aof2.Magnitude(); ExprVector n2 = arc2->Normal()->NormalExprsN(); ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0)); ExprVector v2 = n2.Cross(u2); // so in our new csys, we start at (1, 0, 0) Expr *costheta2 = aof2.Dot(u2)->Div(r2); Expr *sintheta2 = aof2.Dot(v2)->Div(r2); double thetas2, thetaf2, dtheta2; arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2); Expr *theta2; if(dtheta2 < 3*PI/4) { theta2 = costheta2->ACos(); } else if(dtheta2 < 5*PI/4) { // As the angle crosses pi, cos theta2 is not invertible; // so use the sine to stop blowing up theta2 = Expr::From(PI)->Minus(sintheta2->ASin()); } else { theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos()); } // And write the equation; (r1*theta1) / ( r2*theta2) = some ratio AddEq(l, (r1->Times(theta1))->Div(r2->Times(theta2))->Minus(exA), 0); return; } case Type::ARC_LINE_LEN_RATIO: { EntityBase *line = SK.GetEntity(entityA), *arc1 = SK.GetEntity(entityB); Expr *ll = Distance(workplane, line->point[0], line->point[1]); // And get the arc1 radius, and the cosine of its angle EntityBase *ao1 = SK.GetEntity(arc1->point[0]), *as1 = SK.GetEntity(arc1->point[1]), *af1 = SK.GetEntity(arc1->point[2]); ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); Expr *r1 = aof1.Magnitude(); ExprVector n1 = arc1->Normal()->NormalExprsN(); ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); ExprVector v1 = n1.Cross(u1); // so in our new csys, we start at (1, 0, 0) Expr *costheta1 = aof1.Dot(u1)->Div(r1); Expr *sintheta1 = aof1.Dot(v1)->Div(r1); double thetas1, thetaf1, dtheta1; arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); Expr *theta1; if(dtheta1 < 3*PI/4) { theta1 = costheta1->ACos(); } else if(dtheta1 < 5*PI/4) { // As the angle crosses pi, cos theta1 is not invertible; // so use the sine to stop blowing up theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); } else { theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); } // And write the equation; (r1*theta1) / ( length) = some ratio AddEq(l, (r1->Times(theta1))->Div(ll)->Minus(exA), 0); return; } case Type::LENGTH_DIFFERENCE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.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->Minus(lb))->Minus(exA), 0); return; } case Type::ARC_ARC_DIFFERENCE: { EntityBase *arc1 = SK.GetEntity(entityA), *arc2 = SK.GetEntity(entityB); // And get the arc1 radius, and the cosine of its angle EntityBase *ao1 = SK.GetEntity(arc1->point[0]), *as1 = SK.GetEntity(arc1->point[1]), *af1 = SK.GetEntity(arc1->point[2]); ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); Expr *r1 = aof1.Magnitude(); ExprVector n1 = arc1->Normal()->NormalExprsN(); ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); ExprVector v1 = n1.Cross(u1); // so in our new csys, we start at (1, 0, 0) Expr *costheta1 = aof1.Dot(u1)->Div(r1); Expr *sintheta1 = aof1.Dot(v1)->Div(r1); double thetas1, thetaf1, dtheta1; arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); Expr *theta1; if(dtheta1 < 3*PI/4) { theta1 = costheta1->ACos(); } else if(dtheta1 < 5*PI/4) { // As the angle crosses pi, cos theta1 is not invertible; // so use the sine to stop blowing up theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); } else { theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); } // And get the arc2 radius, and the cosine of its angle EntityBase *ao2 = SK.GetEntity(arc2->point[0]), *as2 = SK.GetEntity(arc2->point[1]), *af2 = SK.GetEntity(arc2->point[2]); ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()), aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs()); Expr *r2 = aof2.Magnitude(); ExprVector n2 = arc2->Normal()->NormalExprsN(); ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0)); ExprVector v2 = n2.Cross(u2); // so in our new csys, we start at (1, 0, 0) Expr *costheta2 = aof2.Dot(u2)->Div(r2); Expr *sintheta2 = aof2.Dot(v2)->Div(r2); double thetas2, thetaf2, dtheta2; arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2); Expr *theta2; if(dtheta2 < 3*PI/4) { theta2 = costheta2->ACos(); } else if(dtheta2 < 5*PI/4) { // As the angle crosses pi, cos theta2 is not invertible; // so use the sine to stop blowing up theta2 = Expr::From(PI)->Minus(sintheta2->ASin()); } else { theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos()); } // And write the equation; (r1*theta1) - ( r2*theta2) = some difference AddEq(l, (r1->Times(theta1))->Minus(r2->Times(theta2))->Minus(exA), 0); return; } case Type::ARC_LINE_DIFFERENCE: { EntityBase *line = SK.GetEntity(entityA), *arc1 = SK.GetEntity(entityB); Expr *ll = Distance(workplane, line->point[0], line->point[1]); // And get the arc1 radius, and the cosine of its angle EntityBase *ao1 = SK.GetEntity(arc1->point[0]), *as1 = SK.GetEntity(arc1->point[1]), *af1 = SK.GetEntity(arc1->point[2]); ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); Expr *r1 = aof1.Magnitude(); ExprVector n1 = arc1->Normal()->NormalExprsN(); ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); ExprVector v1 = n1.Cross(u1); // so in our new csys, we start at (1, 0, 0) Expr *costheta1 = aof1.Dot(u1)->Div(r1); Expr *sintheta1 = aof1.Dot(v1)->Div(r1); double thetas1, thetaf1, dtheta1; arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); Expr *theta1; if(dtheta1 < 3*PI/4) { theta1 = costheta1->ACos(); } else if(dtheta1 < 5*PI/4) { // As the angle crosses pi, cos theta1 is not invertible; // so use the sine to stop blowing up theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); } else { theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); } // And write the equation; (r1*theta1) - ( length) = some difference AddEq(l, (r1->Times(theta1))->Minus(ll)->Minus(exA), 0); return; } case Type::DIAMETER: { EntityBase *circle = SK.GetEntity(entityA); Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0); return; } case Type::EQUAL_RADIUS: { EntityBase *c1 = SK.GetEntity(entityA); EntityBase *c2 = SK.GetEntity(entityB); AddEq(l, (c1->CircleGetRadiusExpr())->Minus( c2->CircleGetRadiusExpr()), 0); return; } case Type::EQUAL_LINE_ARC_LEN: { EntityBase *line = SK.GetEntity(entityA), *arc = SK.GetEntity(entityB); // Get the line length ExprVector l0 = SK.GetEntity(line->point[0])->PointGetExprs(), l1 = SK.GetEntity(line->point[1])->PointGetExprs(); Expr *ll = (l1.Minus(l0)).Magnitude(); // And get the arc radius, and the cosine of its angle EntityBase *ao = SK.GetEntity(arc->point[0]), *as = SK.GetEntity(arc->point[1]), *af = SK.GetEntity(arc->point[2]); ExprVector aos = (as->PointGetExprs()).Minus(ao->PointGetExprs()), aof = (af->PointGetExprs()).Minus(ao->PointGetExprs()); Expr *r = aof.Magnitude(); ExprVector n = arc->Normal()->NormalExprsN(); ExprVector u = aos.WithMagnitude(Expr::From(1.0)); ExprVector v = n.Cross(u); // so in our new csys, we start at (1, 0, 0) Expr *costheta = aof.Dot(u)->Div(r); Expr *sintheta = aof.Dot(v)->Div(r); double thetas, thetaf, dtheta; arc->ArcGetAngles(&thetas, &thetaf, &dtheta); Expr *theta; if(dtheta < 3*PI/4) { theta = costheta->ACos(); } else if(dtheta < 5*PI/4) { // As the angle crosses pi, cos theta is not invertible; // so use the sine to stop blowing up theta = Expr::From(PI)->Minus(sintheta->ASin()); } else { theta = (Expr::From(2*PI))->Minus(costheta->ACos()); } // And write the equation; r*theta = L AddEq(l, (r->Times(theta))->Minus(ll), 0); return; } case Type::POINTS_COINCIDENT: { EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); if(workplane == EntityBase::FREE_IN_3D) { ExprVector pa = a->PointGetExprs(); ExprVector pb = b->PointGetExprs(); AddEq(l, pa.x->Minus(pb.x), 0); AddEq(l, pa.y->Minus(pb.y), 1); AddEq(l, pa.z->Minus(pb.z), 2); } else { Expr *au, *av; Expr *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); AddEq(l, au->Minus(bu), 0); AddEq(l, av->Minus(bv), 1); } return; } case Type::PT_IN_PLANE: // This one works the same, whether projected or not. AddEq(l, PointPlaneDistance( SK.GetEntity(ptA)->PointGetExprs(), entityA), 0); return; case Type::PT_ON_FACE: { // a plane, n dot (p - p0) = 0 ExprVector p = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (p.Minus(p0)).Dot(n), 0); return; } case Type::PT_ON_LINE: { EntityBase *ln = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ln->point[0]); EntityBase *b = SK.GetEntity(ln->point[1]); EntityBase *p = SK.GetEntity(ptA); ExprVector ep = p->PointGetExprsInWorkplane(workplane); ExprVector ea = a->PointGetExprsInWorkplane(workplane); ExprVector eb = b->PointGetExprsInWorkplane(workplane); ExprVector ptOnLine = ea.Plus(eb.Minus(ea).ScaledBy(Expr::From(valP))); ExprVector eq = ptOnLine.Minus(ep); AddEq(l, eq); return; } case Type::PT_ON_CIRCLE: { // This actually constrains the point to lie on the cylinder. EntityBase *circle = SK.GetEntity(entityA); ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs(); ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *normal = SK.GetEntity(circle->normal); ExprVector u = normal->NormalExprsU(), v = normal->NormalExprsV(); Expr *du = (center.Minus(pt)).Dot(u), *dv = (center.Minus(pt)).Dot(v); Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, du->Square()->Plus(dv->Square())->Sqrt()->Minus(r), 0); return; } case Type::AT_MIDPOINT: if(workplane == EntityBase::FREE_IN_3D) { EntityBase *ln = SK.GetEntity(entityA); ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs(); ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs(); ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5)); if(ptA.v) { ExprVector p = SK.GetEntity(ptA)->PointGetExprs(); AddEq(l, (m.x)->Minus(p.x), 0); AddEq(l, (m.y)->Minus(p.y), 1); AddEq(l, (m.z)->Minus(p.z), 2); } else { AddEq(l, PointPlaneDistance(m, entityB), 0); } } else { EntityBase *ln = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ln->point[0]); EntityBase *b = SK.GetEntity(ln->point[1]); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); Expr *mu = Expr::From(0.5)->Times(au->Plus(bu)); Expr *mv = Expr::From(0.5)->Times(av->Plus(bv)); if(ptA.v) { EntityBase *p = SK.GetEntity(ptA); Expr *pu, *pv; p->PointGetExprsInWorkplane(workplane, &pu, &pv); AddEq(l, pu->Minus(mu), 0); AddEq(l, pv->Minus(mv), 1); } else { ExprVector m = PointInThreeSpace(workplane, mu, mv); AddEq(l, PointPlaneDistance(m, entityB), 0); } } return; case Type::SYMMETRIC: if(workplane == EntityBase::FREE_IN_3D) { EntityBase *plane = SK.GetEntity(entityA); EntityBase *ea = SK.GetEntity(ptA); EntityBase *eb = SK.GetEntity(ptB); ExprVector a = ea->PointGetExprs(); ExprVector b = eb->PointGetExprs(); // The midpoint of the line connecting the symmetric points // lies on the plane of the symmetry. ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5)); AddEq(l, PointPlaneDistance(m, plane->h), 0); // And projected into the plane of symmetry, the points are // coincident. Expr *au, *av, *bu, *bv; ea->PointGetExprsInWorkplane(plane->h, &au, &av); eb->PointGetExprsInWorkplane(plane->h, &bu, &bv); AddEq(l, au->Minus(bu), 1); AddEq(l, av->Minus(bv), 2); } else { EntityBase *plane = SK.GetEntity(entityA); EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); Expr *mu = Expr::From(0.5)->Times(au->Plus(bu)); Expr *mv = Expr::From(0.5)->Times(av->Plus(bv)); ExprVector m = PointInThreeSpace(workplane, mu, mv); AddEq(l, PointPlaneDistance(m, plane->h), 0); // Construct a vector within the workplane that is normal // to the symmetry pane's normal (i.e., that lies in the // plane of symmetry). The line connecting the points is // perpendicular to that constructed vector. EntityBase *w = SK.GetEntity(workplane); ExprVector u = w->Normal()->NormalExprsU(); ExprVector v = w->Normal()->NormalExprsV(); ExprVector pa = a->PointGetExprs(); ExprVector pb = b->PointGetExprs(); ExprVector n; Expr *d; plane->WorkplaneGetPlaneExprs(&n, &d); AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1); } return; case Type::SYMMETRIC_HORIZ: case Type::SYMMETRIC_VERT: { ssassert(workplane != Entity::FREE_IN_3D, "Unexpected horizontal/vertical symmetric constraint in 3d"); EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); if(type == Type::SYMMETRIC_HORIZ) { AddEq(l, av->Minus(bv), 0); AddEq(l, au->Plus(bu), 1); } else { AddEq(l, au->Minus(bu), 0); AddEq(l, av->Plus(bv), 1); } return; } case Type::SYMMETRIC_LINE: { EntityBase *pa = SK.GetEntity(ptA); EntityBase *pb = SK.GetEntity(ptB); Expr *pau, *pav, *pbu, *pbv; pa->PointGetExprsInWorkplane(workplane, &pau, &pav); pb->PointGetExprsInWorkplane(workplane, &pbu, &pbv); EntityBase *ln = SK.GetEntity(entityA); EntityBase *la = SK.GetEntity(ln->point[0]); EntityBase *lb = SK.GetEntity(ln->point[1]); Expr *lau, *lav, *lbu, *lbv; la->PointGetExprsInWorkplane(workplane, &lau, &lav); lb->PointGetExprsInWorkplane(workplane, &lbu, &lbv); Expr *dpu = pbu->Minus(pau), *dpv = pbv->Minus(pav); Expr *dlu = lbu->Minus(lau), *dlv = lbv->Minus(lav); // The line through the points is perpendicular to the line // of symmetry. AddEq(l, (dlu->Times(dpu))->Plus(dlv->Times(dpv)), 0); // And the signed distances of the points to the line are // equal in magnitude and opposite in sign, so sum to zero Expr *dista = (dlv->Times(lau->Minus(pau)))->Minus( (dlu->Times(lav->Minus(pav)))); Expr *distb = (dlv->Times(lau->Minus(pbu)))->Minus( (dlu->Times(lav->Minus(pbv)))); AddEq(l, dista->Plus(distb), 1); return; } case Type::HORIZONTAL: case Type::VERTICAL: { ssassert(workplane != Entity::FREE_IN_3D, "Unexpected horizontal/vertical constraint in 3d"); hEntity ha, hb; if(entityA.v) { EntityBase *e = SK.GetEntity(entityA); ha = e->point[0]; hb = e->point[1]; } else { ha = ptA; hb = ptB; } EntityBase *a = SK.GetEntity(ha); EntityBase *b = SK.GetEntity(hb); Expr *au, *av, *bu, *bv; a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0); return; } case Type::SAME_ORIENTATION: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); ExprVector au = a->NormalExprsU(), an = a->NormalExprsN(); ExprVector bu = b->NormalExprsU(), bv = b->NormalExprsV(), bn = b->NormalExprsN(); ExprVector eq = VectorsParallel3d(an, bn, valP); AddEq(l, eq.x, 0); AddEq(l, eq.y, 1); AddEq(l, eq.z, 2); Expr *d1 = au.Dot(bv); Expr *d2 = au.Dot(bu); // Allow either orientation for the coordinate system, depending // on how it was drawn. if(fabs(d1->Eval()) < fabs(d2->Eval())) { AddEq(l, d1, 3); } else { AddEq(l, d2, 3); } return; } case Type::PERPENDICULAR: case Type::ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); ExprVector ae = a->VectorGetExprs(); ExprVector be = b->VectorGetExprs(); if(other) ae = ae.ScaledBy(Expr::From(-1)); Expr *c = DirectionCosine(workplane, ae, be); if(type == Type::ANGLE) { // The direction cosine is equal to the cosine of the // specified angle Expr *rads = exA->Times(Expr::From(PI/180)), *rc = rads->Cos(); double arc = fabs(rc->Eval()); // avoid false detection of inconsistent systems by gaining // up as the difference in dot products gets small at small // angles; doubles still have plenty of precision, only // problem is that rank test Expr *mult = Expr::From(arc > 0.99 ? 0.01/(1.00001 - arc) : 1); AddEq(l, (c->Minus(rc))->Times(mult), 0); } else { // The dot product (and therefore the direction cosine) // is equal to zero, perpendicular. AddEq(l, c, 0); } return; } case Type::EQUAL_ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); EntityBase *c = SK.GetEntity(entityC); EntityBase *d = SK.GetEntity(entityD); ExprVector ae = a->VectorGetExprs(); ExprVector be = b->VectorGetExprs(); ExprVector ce = c->VectorGetExprs(); ExprVector de = d->VectorGetExprs(); if(other) ae = ae.ScaledBy(Expr::From(-1)); Expr *cab = DirectionCosine(workplane, ae, be); Expr *ccd = DirectionCosine(workplane, ce, de); AddEq(l, cab->Minus(ccd), 0); return; } case Type::ARC_LINE_TANGENT: { EntityBase *arc = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); ExprVector ac = SK.GetEntity(arc->point[0])->PointGetExprs(); ExprVector ap = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs(); ExprVector ld = line->VectorGetExprs(); // The line is perpendicular to the radius AddEq(l, ld.Dot(ac.Minus(ap)), 0); return; } case Type::CUBIC_LINE_TANGENT: { EntityBase *cubic = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); ExprVector a; if(other) { a = cubic->CubicGetFinishTangentExprs(); } else { a = cubic->CubicGetStartTangentExprs(); } ExprVector b = line->VectorGetExprs(); if(workplane == EntityBase::FREE_IN_3D) { ExprVector eq = VectorsParallel3d(a, b, valP); AddEq(l, eq); } else { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, (a.Cross(b)).Dot(wn), 0); } return; } case Type::CURVE_CURVE_TANGENT: { bool parallel = true; int i; ExprVector dir[2]; for(i = 0; i < 2; i++) { EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB); bool oth = (i == 0) ? other : other2; if(e->type == Entity::Type::ARC_OF_CIRCLE) { ExprVector center, endpoint; center = SK.GetEntity(e->point[0])->PointGetExprs(); endpoint = SK.GetEntity(e->point[oth ? 2 : 1])->PointGetExprs(); dir[i] = endpoint.Minus(center); // We're using the vector from the center of the arc to // an endpoint; so that's normal to the tangent, not // parallel. parallel = !parallel; } else if(e->type == Entity::Type::CUBIC) { // BRANCH_ALWAYS_TAKEN if(oth) { dir[i] = e->CubicGetFinishTangentExprs(); } else { dir[i] = e->CubicGetStartTangentExprs(); } } else { ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT"); } } if(parallel) { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, ((dir[0]).Cross(dir[1])).Dot(wn), 0); } else { AddEq(l, (dir[0]).Dot(dir[1]), 0); } return; } case Type::PARALLEL: { EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB); ExprVector a = ea->VectorGetExprsInWorkplane(workplane); ExprVector b = eb->VectorGetExprsInWorkplane(workplane); if(workplane == EntityBase::FREE_IN_3D) { ExprVector eq = VectorsParallel3d(a, b, valP); AddEq(l, eq); } else { // We use expressions written in workplane csys, so we can assume the workplane // normal is (0, 0, 1). We can write the equation as: // Expr *eq = a.Cross(b).Dot(ExprVector::From(0.0, 0.0, 1.0)); // but this will just result in elimination of x and y terms after dot product. // We can only use the z expression: // Expr *eq = a.Cross(b).z; // but it's more efficient to write it in the terms of pseudo-scalar product: Expr *eq = (a.x->Times(b.y))->Minus(a.y->Times(b.x)); AddEq(l, eq, 0); } return; } case Type::WHERE_DRAGGED: { EntityBase *ep = SK.GetEntity(ptA); if(workplane == EntityBase::FREE_IN_3D) { ExprVector ev = ep->PointGetExprs(); Vector v = ep->PointGetNum(); AddEq(l, ev.x->Minus(Expr::From(v.x)), 0); AddEq(l, ev.y->Minus(Expr::From(v.y)), 1); AddEq(l, ev.z->Minus(Expr::From(v.z)), 2); } else { Expr *u, *v; ep->PointGetExprsInWorkplane(workplane, &u, &v); AddEq(l, u->Minus(Expr::From(u->Eval())), 0); AddEq(l, v->Minus(Expr::From(v->Eval())), 1); } return; } case Type::COMMENT: return; } ssassert(false, "Unexpected constraint ID"); }