Solvespace
1058 строк · 40.9 Кб
1//-----------------------------------------------------------------------------
2// Given a constraint, generate one or more equations in our symbolic algebra
3// system to represent that constraint; also various geometric helper
4// functions for that.
5//
6// Copyright 2008-2013 Jonathan Westhues.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"
9
10const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 };
11
12bool ConstraintBase::HasLabel() const {
13switch(type) {
14case Type::PT_LINE_DISTANCE:
15case Type::PT_PLANE_DISTANCE:
16case Type::PT_FACE_DISTANCE:
17case Type::PT_PT_DISTANCE:
18case Type::PROJ_PT_DISTANCE:
19case Type::DIAMETER:
20case Type::LENGTH_RATIO:
21case Type::ARC_ARC_LEN_RATIO:
22case Type::ARC_LINE_LEN_RATIO:
23case Type::LENGTH_DIFFERENCE:
24case Type::ARC_ARC_DIFFERENCE:
25case Type::ARC_LINE_DIFFERENCE:
26case Type::ANGLE:
27case Type::COMMENT:
28return true;
29
30default:
31return false;
32}
33}
34
35bool ConstraintBase::IsProjectible() const {
36switch(type) {
37case Type::POINTS_COINCIDENT:
38case Type::PT_PT_DISTANCE:
39case Type::PT_LINE_DISTANCE:
40case Type::PT_ON_LINE:
41case Type::EQUAL_LENGTH_LINES:
42case Type::EQ_LEN_PT_LINE_D:
43case Type::EQ_PT_LN_DISTANCES:
44case Type::EQUAL_ANGLE:
45case Type::LENGTH_RATIO:
46case Type::ARC_ARC_LEN_RATIO:
47case Type::ARC_LINE_LEN_RATIO:
48case Type::LENGTH_DIFFERENCE:
49case Type::ARC_ARC_DIFFERENCE:
50case Type::ARC_LINE_DIFFERENCE:
51case Type::SYMMETRIC:
52case Type::SYMMETRIC_HORIZ:
53case Type::SYMMETRIC_VERT:
54case Type::SYMMETRIC_LINE:
55case Type::AT_MIDPOINT:
56case Type::HORIZONTAL:
57case Type::VERTICAL:
58case Type::ANGLE:
59case Type::PARALLEL:
60case Type::PERPENDICULAR:
61case Type::WHERE_DRAGGED:
62case Type::COMMENT:
63return true;
64
65case Type::PT_PLANE_DISTANCE:
66case Type::PT_FACE_DISTANCE:
67case Type::PROJ_PT_DISTANCE:
68case Type::PT_IN_PLANE:
69case Type::PT_ON_FACE:
70case Type::EQUAL_LINE_ARC_LEN:
71case Type::DIAMETER:
72case Type::PT_ON_CIRCLE:
73case Type::SAME_ORIENTATION:
74case Type::CUBIC_LINE_TANGENT:
75case Type::CURVE_CURVE_TANGENT:
76case Type::ARC_LINE_TANGENT:
77case Type::EQUAL_RADIUS:
78return false;
79}
80ssassert(false, "Impossible");
81}
82
83ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) {
84return a.Minus(b.ScaledBy(Expr::From(p)));
85}
86
87Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln)
88{
89EntityBase *ln = SK.GetEntity(hln);
90EntityBase *a = SK.GetEntity(ln->point[0]);
91EntityBase *b = SK.GetEntity(ln->point[1]);
92
93EntityBase *p = SK.GetEntity(hpt);
94
95if(wrkpl == EntityBase::FREE_IN_3D) {
96ExprVector ep = p->PointGetExprs();
97
98ExprVector ea = a->PointGetExprs();
99ExprVector eb = b->PointGetExprs();
100ExprVector eab = ea.Minus(eb);
101Expr *m = eab.Magnitude();
102
103return ((eab.Cross(ea.Minus(ep))).Magnitude())->Div(m);
104} else {
105Expr *ua, *va, *ub, *vb;
106a->PointGetExprsInWorkplane(wrkpl, &ua, &va);
107b->PointGetExprsInWorkplane(wrkpl, &ub, &vb);
108
109Expr *du = ua->Minus(ub);
110Expr *dv = va->Minus(vb);
111
112Expr *u, *v;
113p->PointGetExprsInWorkplane(wrkpl, &u, &v);
114
115Expr *m = ((du->Square())->Plus(dv->Square()))->Sqrt();
116
117Expr *proj = (dv->Times(ua->Minus(u)))->Minus(
118(du->Times(va->Minus(v))));
119
120return proj->Div(m);
121}
122}
123
124Expr *ConstraintBase::PointPlaneDistance(ExprVector p, hEntity hpl) {
125ExprVector n;
126Expr *d;
127SK.GetEntity(hpl)->WorkplaneGetPlaneExprs(&n, &d);
128return (p.Dot(n))->Minus(d);
129}
130
131Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) {
132EntityBase *pa = SK.GetEntity(hpa);
133EntityBase *pb = SK.GetEntity(hpb);
134ssassert(pa->IsPoint() && pb->IsPoint(),
135"Expected two points to measure projected distance between");
136
137if(wrkpl == EntityBase::FREE_IN_3D) {
138// This is true distance
139ExprVector ea, eb, eab;
140ea = pa->PointGetExprs();
141eb = pb->PointGetExprs();
142eab = ea.Minus(eb);
143
144return eab.Magnitude();
145} else {
146// This is projected distance, in the given workplane.
147Expr *au, *av, *bu, *bv;
148
149pa->PointGetExprsInWorkplane(wrkpl, &au, &av);
150pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv);
151
152Expr *du = au->Minus(bu);
153Expr *dv = av->Minus(bv);
154
155return ((du->Square())->Plus(dv->Square()))->Sqrt();
156}
157}
158
159//-----------------------------------------------------------------------------
160// Return the cosine of the angle between two vectors. If a workplane is
161// specified, then it's the cosine of their projections into that workplane.
162//-----------------------------------------------------------------------------
163Expr *ConstraintBase::DirectionCosine(hEntity wrkpl,
164ExprVector ae, ExprVector be)
165{
166if(wrkpl == EntityBase::FREE_IN_3D) {
167Expr *mags = (ae.Magnitude())->Times(be.Magnitude());
168return (ae.Dot(be))->Div(mags);
169} else {
170EntityBase *w = SK.GetEntity(wrkpl);
171ExprVector u = w->Normal()->NormalExprsU();
172ExprVector v = w->Normal()->NormalExprsV();
173Expr *ua = u.Dot(ae);
174Expr *va = v.Dot(ae);
175Expr *ub = u.Dot(be);
176Expr *vb = v.Dot(be);
177Expr *maga = (ua->Square()->Plus(va->Square()))->Sqrt();
178Expr *magb = (ub->Square()->Plus(vb->Square()))->Sqrt();
179Expr *dot = (ua->Times(ub))->Plus(va->Times(vb));
180return dot->Div(maga->Times(magb));
181}
182}
183
184ExprVector ConstraintBase::PointInThreeSpace(hEntity workplane,
185Expr *u, Expr *v)
186{
187EntityBase *w = SK.GetEntity(workplane);
188
189ExprVector ub = w->Normal()->NormalExprsU();
190ExprVector vb = w->Normal()->NormalExprsV();
191ExprVector ob = w->WorkplaneGetOffsetExprs();
192
193return (ub.ScaledBy(u)).Plus(vb.ScaledBy(v)).Plus(ob);
194}
195
196void ConstraintBase::ModifyToSatisfy() {
197if(type == Type::ANGLE) {
198Vector a = SK.GetEntity(entityA)->VectorGetNum();
199Vector b = SK.GetEntity(entityB)->VectorGetNum();
200if(other) a = a.ScaledBy(-1);
201if(workplane != EntityBase::FREE_IN_3D) {
202a = a.ProjectVectorInto(workplane);
203b = b.ProjectVectorInto(workplane);
204}
205double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude());
206valA = acos(c)*180/PI;
207} else if(type == Type::PT_ON_LINE) {
208EntityBase *eln = SK.GetEntity(entityA);
209EntityBase *ea = SK.GetEntity(eln->point[0]);
210EntityBase *eb = SK.GetEntity(eln->point[1]);
211EntityBase *ep = SK.GetEntity(ptA);
212ExprVector exp = ep->PointGetExprsInWorkplane(workplane);
213ExprVector exa = ea->PointGetExprsInWorkplane(workplane);
214ExprVector exb = eb->PointGetExprsInWorkplane(workplane);
215ExprVector exba = exb.Minus(exa);
216SK.GetParam(valP)->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval();
217} else {
218// We'll fix these ones up by looking at their symbolic equation;
219// that means no extra work.
220IdList<Equation,hEquation> l = {};
221// Generate the equations even if this is a reference dimension
222GenerateEquations(&l, /*forReference=*/true);
223ssassert(l.n == 1, "Expected constraint to generate a single equation");
224
225// These equations are written in the form f(...) - d = 0, where
226// d is the value of the valA.
227valA += (l[0].e)->Eval();
228
229l.Clear();
230}
231}
232
233void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const
234{
235Equation eq;
236eq.e = expr;
237eq.h = h.equation(index);
238l->Add(&eq);
239}
240
241void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, const ExprVector &v,
242int baseIndex) const {
243AddEq(l, v.x, baseIndex);
244AddEq(l, v.y, baseIndex + 1);
245if(workplane == EntityBase::FREE_IN_3D) {
246AddEq(l, v.z, baseIndex + 2);
247}
248}
249
250void ConstraintBase::Generate(IdList<Param,hParam> *l) {
251switch(type) {
252case Type::PARALLEL:
253case Type::CUBIC_LINE_TANGENT:
254// Add new parameter only when we operate in 3d space
255if(workplane != EntityBase::FREE_IN_3D) break;
256// fallthrough
257case Type::SAME_ORIENTATION:
258case Type::PT_ON_LINE: {
259Param p = {};
260valP = h.param(0);
261p.h = valP;
262l->Add(&p);
263break;
264}
265
266default:
267break;
268}
269}
270
271void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
272bool forReference) const {
273if(reference && !forReference) return;
274
275Expr *exA = Expr::From(valA);
276switch(type) {
277case Type::PT_PT_DISTANCE:
278AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0);
279return;
280
281case Type::PROJ_PT_DISTANCE: {
282ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(),
283pB = SK.GetEntity(ptB)->PointGetExprs(),
284dp = pB.Minus(pA);
285
286ExprVector pp = SK.GetEntity(entityA)->VectorGetExprs();
287pp = pp.WithMagnitude(Expr::From(1.0));
288
289AddEq(l, (dp.Dot(pp))->Minus(exA), 0);
290return;
291}
292
293case Type::PT_LINE_DISTANCE:
294AddEq(l,
295PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0);
296return;
297
298case Type::PT_PLANE_DISTANCE: {
299ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
300AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0);
301return;
302}
303
304case Type::PT_FACE_DISTANCE: {
305ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
306EntityBase *f = SK.GetEntity(entityA);
307ExprVector p0 = f->FaceGetPointExprs();
308ExprVector n = f->FaceGetNormalExprs();
309AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0);
310return;
311}
312
313case Type::EQUAL_LENGTH_LINES: {
314EntityBase *a = SK.GetEntity(entityA);
315EntityBase *b = SK.GetEntity(entityB);
316AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus(
317Distance(workplane, b->point[0], b->point[1])), 0);
318return;
319}
320
321// These work on distance squared, since the pt-line distances are
322// signed, and we want the absolute value.
323case Type::EQ_LEN_PT_LINE_D: {
324EntityBase *forLen = SK.GetEntity(entityA);
325Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]);
326Expr *d2 = PointLineDistance(workplane, ptA, entityB);
327AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
328return;
329}
330case Type::EQ_PT_LN_DISTANCES: {
331Expr *d1 = PointLineDistance(workplane, ptA, entityA);
332Expr *d2 = PointLineDistance(workplane, ptB, entityB);
333AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
334return;
335}
336
337case Type::LENGTH_RATIO: {
338EntityBase *a = SK.GetEntity(entityA);
339EntityBase *b = SK.GetEntity(entityB);
340Expr *la = Distance(workplane, a->point[0], a->point[1]);
341Expr *lb = Distance(workplane, b->point[0], b->point[1]);
342AddEq(l, (la->Div(lb))->Minus(exA), 0);
343return;
344}
345
346case Type::ARC_ARC_LEN_RATIO: {
347EntityBase *arc1 = SK.GetEntity(entityA),
348*arc2 = SK.GetEntity(entityB);
349
350// And get the arc1 radius, and the cosine of its angle
351EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
352*as1 = SK.GetEntity(arc1->point[1]),
353*af1 = SK.GetEntity(arc1->point[2]);
354
355ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
356aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
357Expr *r1 = aof1.Magnitude();
358
359ExprVector n1 = arc1->Normal()->NormalExprsN();
360ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
361ExprVector v1 = n1.Cross(u1);
362// so in our new csys, we start at (1, 0, 0)
363Expr *costheta1 = aof1.Dot(u1)->Div(r1);
364Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
365
366double thetas1, thetaf1, dtheta1;
367arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
368Expr *theta1;
369if(dtheta1 < 3*PI/4) {
370theta1 = costheta1->ACos();
371} else if(dtheta1 < 5*PI/4) {
372// As the angle crosses pi, cos theta1 is not invertible;
373// so use the sine to stop blowing up
374theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
375} else {
376theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
377}
378
379// And get the arc2 radius, and the cosine of its angle
380EntityBase *ao2 = SK.GetEntity(arc2->point[0]),
381*as2 = SK.GetEntity(arc2->point[1]),
382*af2 = SK.GetEntity(arc2->point[2]);
383
384ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()),
385aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs());
386Expr *r2 = aof2.Magnitude();
387
388ExprVector n2 = arc2->Normal()->NormalExprsN();
389ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0));
390ExprVector v2 = n2.Cross(u2);
391// so in our new csys, we start at (1, 0, 0)
392Expr *costheta2 = aof2.Dot(u2)->Div(r2);
393Expr *sintheta2 = aof2.Dot(v2)->Div(r2);
394
395double thetas2, thetaf2, dtheta2;
396arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2);
397Expr *theta2;
398if(dtheta2 < 3*PI/4) {
399theta2 = costheta2->ACos();
400} else if(dtheta2 < 5*PI/4) {
401// As the angle crosses pi, cos theta2 is not invertible;
402// so use the sine to stop blowing up
403theta2 = Expr::From(PI)->Minus(sintheta2->ASin());
404} else {
405theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos());
406}
407// And write the equation; (r1*theta1) / ( r2*theta2) = some ratio
408AddEq(l, (r1->Times(theta1))->Div(r2->Times(theta2))->Minus(exA), 0);
409return;
410}
411
412case Type::ARC_LINE_LEN_RATIO: {
413EntityBase *line = SK.GetEntity(entityA),
414*arc1 = SK.GetEntity(entityB);
415
416Expr *ll = Distance(workplane, line->point[0], line->point[1]);
417
418// And get the arc1 radius, and the cosine of its angle
419EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
420*as1 = SK.GetEntity(arc1->point[1]),
421*af1 = SK.GetEntity(arc1->point[2]);
422
423ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
424aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
425Expr *r1 = aof1.Magnitude();
426ExprVector n1 = arc1->Normal()->NormalExprsN();
427ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
428ExprVector v1 = n1.Cross(u1);
429// so in our new csys, we start at (1, 0, 0)
430Expr *costheta1 = aof1.Dot(u1)->Div(r1);
431Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
432
433double thetas1, thetaf1, dtheta1;
434arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
435Expr *theta1;
436if(dtheta1 < 3*PI/4) {
437theta1 = costheta1->ACos();
438} else if(dtheta1 < 5*PI/4) {
439// As the angle crosses pi, cos theta1 is not invertible;
440// so use the sine to stop blowing up
441theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
442} else {
443theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
444}
445// And write the equation; (r1*theta1) / ( length) = some ratio
446AddEq(l, (r1->Times(theta1))->Div(ll)->Minus(exA), 0);
447return;
448}
449
450case Type::LENGTH_DIFFERENCE: {
451EntityBase *a = SK.GetEntity(entityA);
452EntityBase *b = SK.GetEntity(entityB);
453Expr *la = Distance(workplane, a->point[0], a->point[1]);
454Expr *lb = Distance(workplane, b->point[0], b->point[1]);
455AddEq(l, (la->Minus(lb))->Minus(exA), 0);
456return;
457}
458
459case Type::ARC_ARC_DIFFERENCE: {
460EntityBase *arc1 = SK.GetEntity(entityA),
461*arc2 = SK.GetEntity(entityB);
462
463// And get the arc1 radius, and the cosine of its angle
464EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
465*as1 = SK.GetEntity(arc1->point[1]),
466*af1 = SK.GetEntity(arc1->point[2]);
467
468ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
469aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
470Expr *r1 = aof1.Magnitude();
471
472ExprVector n1 = arc1->Normal()->NormalExprsN();
473ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
474ExprVector v1 = n1.Cross(u1);
475// so in our new csys, we start at (1, 0, 0)
476Expr *costheta1 = aof1.Dot(u1)->Div(r1);
477Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
478
479double thetas1, thetaf1, dtheta1;
480arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
481Expr *theta1;
482if(dtheta1 < 3*PI/4) {
483theta1 = costheta1->ACos();
484} else if(dtheta1 < 5*PI/4) {
485// As the angle crosses pi, cos theta1 is not invertible;
486// so use the sine to stop blowing up
487theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
488} else {
489theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
490}
491
492// And get the arc2 radius, and the cosine of its angle
493EntityBase *ao2 = SK.GetEntity(arc2->point[0]),
494*as2 = SK.GetEntity(arc2->point[1]),
495*af2 = SK.GetEntity(arc2->point[2]);
496
497ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()),
498aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs());
499Expr *r2 = aof2.Magnitude();
500
501ExprVector n2 = arc2->Normal()->NormalExprsN();
502ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0));
503ExprVector v2 = n2.Cross(u2);
504// so in our new csys, we start at (1, 0, 0)
505Expr *costheta2 = aof2.Dot(u2)->Div(r2);
506Expr *sintheta2 = aof2.Dot(v2)->Div(r2);
507
508double thetas2, thetaf2, dtheta2;
509arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2);
510Expr *theta2;
511if(dtheta2 < 3*PI/4) {
512theta2 = costheta2->ACos();
513} else if(dtheta2 < 5*PI/4) {
514// As the angle crosses pi, cos theta2 is not invertible;
515// so use the sine to stop blowing up
516theta2 = Expr::From(PI)->Minus(sintheta2->ASin());
517} else {
518theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos());
519}
520// And write the equation; (r1*theta1) - ( r2*theta2) = some difference
521AddEq(l, (r1->Times(theta1))->Minus(r2->Times(theta2))->Minus(exA), 0);
522return;
523}
524
525case Type::ARC_LINE_DIFFERENCE: {
526EntityBase *line = SK.GetEntity(entityA),
527*arc1 = SK.GetEntity(entityB);
528
529Expr *ll = Distance(workplane, line->point[0], line->point[1]);
530
531// And get the arc1 radius, and the cosine of its angle
532EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
533*as1 = SK.GetEntity(arc1->point[1]),
534*af1 = SK.GetEntity(arc1->point[2]);
535
536ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
537aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
538Expr *r1 = aof1.Magnitude();
539ExprVector n1 = arc1->Normal()->NormalExprsN();
540ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
541ExprVector v1 = n1.Cross(u1);
542// so in our new csys, we start at (1, 0, 0)
543Expr *costheta1 = aof1.Dot(u1)->Div(r1);
544Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
545
546double thetas1, thetaf1, dtheta1;
547arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
548Expr *theta1;
549if(dtheta1 < 3*PI/4) {
550theta1 = costheta1->ACos();
551} else if(dtheta1 < 5*PI/4) {
552// As the angle crosses pi, cos theta1 is not invertible;
553// so use the sine to stop blowing up
554theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
555} else {
556theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
557}
558// And write the equation; (r1*theta1) - ( length) = some difference
559AddEq(l, (r1->Times(theta1))->Minus(ll)->Minus(exA), 0);
560return;
561}
562
563case Type::DIAMETER: {
564EntityBase *circle = SK.GetEntity(entityA);
565Expr *r = circle->CircleGetRadiusExpr();
566AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0);
567return;
568}
569
570case Type::EQUAL_RADIUS: {
571EntityBase *c1 = SK.GetEntity(entityA);
572EntityBase *c2 = SK.GetEntity(entityB);
573AddEq(l, (c1->CircleGetRadiusExpr())->Minus(
574c2->CircleGetRadiusExpr()), 0);
575return;
576}
577
578case Type::EQUAL_LINE_ARC_LEN: {
579EntityBase *line = SK.GetEntity(entityA),
580*arc = SK.GetEntity(entityB);
581
582// Get the line length
583ExprVector l0 = SK.GetEntity(line->point[0])->PointGetExprs(),
584l1 = SK.GetEntity(line->point[1])->PointGetExprs();
585Expr *ll = (l1.Minus(l0)).Magnitude();
586
587// And get the arc radius, and the cosine of its angle
588EntityBase *ao = SK.GetEntity(arc->point[0]),
589*as = SK.GetEntity(arc->point[1]),
590*af = SK.GetEntity(arc->point[2]);
591
592ExprVector aos = (as->PointGetExprs()).Minus(ao->PointGetExprs()),
593aof = (af->PointGetExprs()).Minus(ao->PointGetExprs());
594Expr *r = aof.Magnitude();
595
596ExprVector n = arc->Normal()->NormalExprsN();
597ExprVector u = aos.WithMagnitude(Expr::From(1.0));
598ExprVector v = n.Cross(u);
599// so in our new csys, we start at (1, 0, 0)
600Expr *costheta = aof.Dot(u)->Div(r);
601Expr *sintheta = aof.Dot(v)->Div(r);
602
603double thetas, thetaf, dtheta;
604arc->ArcGetAngles(&thetas, &thetaf, &dtheta);
605Expr *theta;
606if(dtheta < 3*PI/4) {
607theta = costheta->ACos();
608} else if(dtheta < 5*PI/4) {
609// As the angle crosses pi, cos theta is not invertible;
610// so use the sine to stop blowing up
611theta = Expr::From(PI)->Minus(sintheta->ASin());
612} else {
613theta = (Expr::From(2*PI))->Minus(costheta->ACos());
614}
615
616// And write the equation; r*theta = L
617AddEq(l, (r->Times(theta))->Minus(ll), 0);
618return;
619}
620
621case Type::POINTS_COINCIDENT: {
622EntityBase *a = SK.GetEntity(ptA);
623EntityBase *b = SK.GetEntity(ptB);
624if(workplane == EntityBase::FREE_IN_3D) {
625ExprVector pa = a->PointGetExprs();
626ExprVector pb = b->PointGetExprs();
627AddEq(l, pa.x->Minus(pb.x), 0);
628AddEq(l, pa.y->Minus(pb.y), 1);
629AddEq(l, pa.z->Minus(pb.z), 2);
630} else {
631Expr *au, *av;
632Expr *bu, *bv;
633a->PointGetExprsInWorkplane(workplane, &au, &av);
634b->PointGetExprsInWorkplane(workplane, &bu, &bv);
635AddEq(l, au->Minus(bu), 0);
636AddEq(l, av->Minus(bv), 1);
637}
638return;
639}
640
641case Type::PT_IN_PLANE:
642// This one works the same, whether projected or not.
643AddEq(l, PointPlaneDistance(
644SK.GetEntity(ptA)->PointGetExprs(), entityA), 0);
645return;
646
647case Type::PT_ON_FACE: {
648// a plane, n dot (p - p0) = 0
649ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
650EntityBase *f = SK.GetEntity(entityA);
651ExprVector p0 = f->FaceGetPointExprs();
652ExprVector n = f->FaceGetNormalExprs();
653AddEq(l, (p.Minus(p0)).Dot(n), 0);
654return;
655}
656
657case Type::PT_ON_LINE: {
658EntityBase *ln = SK.GetEntity(entityA);
659EntityBase *a = SK.GetEntity(ln->point[0]);
660EntityBase *b = SK.GetEntity(ln->point[1]);
661EntityBase *p = SK.GetEntity(ptA);
662
663ExprVector ep = p->PointGetExprsInWorkplane(workplane);
664ExprVector ea = a->PointGetExprsInWorkplane(workplane);
665ExprVector eb = b->PointGetExprsInWorkplane(workplane);
666
667ExprVector ptOnLine = ea.Plus(eb.Minus(ea).ScaledBy(Expr::From(valP)));
668ExprVector eq = ptOnLine.Minus(ep);
669
670AddEq(l, eq);
671return;
672}
673
674case Type::PT_ON_CIRCLE: {
675// This actually constrains the point to lie on the cylinder.
676EntityBase *circle = SK.GetEntity(entityA);
677ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs();
678ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
679EntityBase *normal = SK.GetEntity(circle->normal);
680ExprVector u = normal->NormalExprsU(),
681v = normal->NormalExprsV();
682
683Expr *du = (center.Minus(pt)).Dot(u),
684*dv = (center.Minus(pt)).Dot(v);
685
686Expr *r = circle->CircleGetRadiusExpr();
687
688AddEq(l, du->Square()->Plus(dv->Square())->Sqrt()->Minus(r), 0);
689return;
690}
691
692case Type::AT_MIDPOINT:
693if(workplane == EntityBase::FREE_IN_3D) {
694EntityBase *ln = SK.GetEntity(entityA);
695ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs();
696ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs();
697ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
698
699if(ptA.v) {
700ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
701AddEq(l, (m.x)->Minus(p.x), 0);
702AddEq(l, (m.y)->Minus(p.y), 1);
703AddEq(l, (m.z)->Minus(p.z), 2);
704} else {
705AddEq(l, PointPlaneDistance(m, entityB), 0);
706}
707} else {
708EntityBase *ln = SK.GetEntity(entityA);
709EntityBase *a = SK.GetEntity(ln->point[0]);
710EntityBase *b = SK.GetEntity(ln->point[1]);
711
712Expr *au, *av, *bu, *bv;
713a->PointGetExprsInWorkplane(workplane, &au, &av);
714b->PointGetExprsInWorkplane(workplane, &bu, &bv);
715Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
716Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
717
718if(ptA.v) {
719EntityBase *p = SK.GetEntity(ptA);
720Expr *pu, *pv;
721p->PointGetExprsInWorkplane(workplane, &pu, &pv);
722AddEq(l, pu->Minus(mu), 0);
723AddEq(l, pv->Minus(mv), 1);
724} else {
725ExprVector m = PointInThreeSpace(workplane, mu, mv);
726AddEq(l, PointPlaneDistance(m, entityB), 0);
727}
728}
729return;
730
731case Type::SYMMETRIC:
732if(workplane == EntityBase::FREE_IN_3D) {
733EntityBase *plane = SK.GetEntity(entityA);
734EntityBase *ea = SK.GetEntity(ptA);
735EntityBase *eb = SK.GetEntity(ptB);
736ExprVector a = ea->PointGetExprs();
737ExprVector b = eb->PointGetExprs();
738
739// The midpoint of the line connecting the symmetric points
740// lies on the plane of the symmetry.
741ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
742AddEq(l, PointPlaneDistance(m, plane->h), 0);
743
744// And projected into the plane of symmetry, the points are
745// coincident.
746Expr *au, *av, *bu, *bv;
747ea->PointGetExprsInWorkplane(plane->h, &au, &av);
748eb->PointGetExprsInWorkplane(plane->h, &bu, &bv);
749AddEq(l, au->Minus(bu), 1);
750AddEq(l, av->Minus(bv), 2);
751} else {
752EntityBase *plane = SK.GetEntity(entityA);
753EntityBase *a = SK.GetEntity(ptA);
754EntityBase *b = SK.GetEntity(ptB);
755
756Expr *au, *av, *bu, *bv;
757a->PointGetExprsInWorkplane(workplane, &au, &av);
758b->PointGetExprsInWorkplane(workplane, &bu, &bv);
759Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
760Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
761
762ExprVector m = PointInThreeSpace(workplane, mu, mv);
763AddEq(l, PointPlaneDistance(m, plane->h), 0);
764
765// Construct a vector within the workplane that is normal
766// to the symmetry pane's normal (i.e., that lies in the
767// plane of symmetry). The line connecting the points is
768// perpendicular to that constructed vector.
769EntityBase *w = SK.GetEntity(workplane);
770ExprVector u = w->Normal()->NormalExprsU();
771ExprVector v = w->Normal()->NormalExprsV();
772
773ExprVector pa = a->PointGetExprs();
774ExprVector pb = b->PointGetExprs();
775ExprVector n;
776Expr *d;
777plane->WorkplaneGetPlaneExprs(&n, &d);
778AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1);
779}
780return;
781
782case Type::SYMMETRIC_HORIZ:
783case Type::SYMMETRIC_VERT: {
784ssassert(workplane != Entity::FREE_IN_3D,
785"Unexpected horizontal/vertical symmetric constraint in 3d");
786
787EntityBase *a = SK.GetEntity(ptA);
788EntityBase *b = SK.GetEntity(ptB);
789
790Expr *au, *av, *bu, *bv;
791a->PointGetExprsInWorkplane(workplane, &au, &av);
792b->PointGetExprsInWorkplane(workplane, &bu, &bv);
793
794if(type == Type::SYMMETRIC_HORIZ) {
795AddEq(l, av->Minus(bv), 0);
796AddEq(l, au->Plus(bu), 1);
797} else {
798AddEq(l, au->Minus(bu), 0);
799AddEq(l, av->Plus(bv), 1);
800}
801return;
802}
803
804case Type::SYMMETRIC_LINE: {
805EntityBase *pa = SK.GetEntity(ptA);
806EntityBase *pb = SK.GetEntity(ptB);
807
808Expr *pau, *pav, *pbu, *pbv;
809pa->PointGetExprsInWorkplane(workplane, &pau, &pav);
810pb->PointGetExprsInWorkplane(workplane, &pbu, &pbv);
811
812EntityBase *ln = SK.GetEntity(entityA);
813EntityBase *la = SK.GetEntity(ln->point[0]);
814EntityBase *lb = SK.GetEntity(ln->point[1]);
815Expr *lau, *lav, *lbu, *lbv;
816la->PointGetExprsInWorkplane(workplane, &lau, &lav);
817lb->PointGetExprsInWorkplane(workplane, &lbu, &lbv);
818
819Expr *dpu = pbu->Minus(pau), *dpv = pbv->Minus(pav);
820Expr *dlu = lbu->Minus(lau), *dlv = lbv->Minus(lav);
821
822// The line through the points is perpendicular to the line
823// of symmetry.
824AddEq(l, (dlu->Times(dpu))->Plus(dlv->Times(dpv)), 0);
825
826// And the signed distances of the points to the line are
827// equal in magnitude and opposite in sign, so sum to zero
828Expr *dista = (dlv->Times(lau->Minus(pau)))->Minus(
829(dlu->Times(lav->Minus(pav))));
830Expr *distb = (dlv->Times(lau->Minus(pbu)))->Minus(
831(dlu->Times(lav->Minus(pbv))));
832AddEq(l, dista->Plus(distb), 1);
833
834return;
835}
836
837case Type::HORIZONTAL:
838case Type::VERTICAL: {
839ssassert(workplane != Entity::FREE_IN_3D,
840"Unexpected horizontal/vertical constraint in 3d");
841
842hEntity ha, hb;
843if(entityA.v) {
844EntityBase *e = SK.GetEntity(entityA);
845ha = e->point[0];
846hb = e->point[1];
847} else {
848ha = ptA;
849hb = ptB;
850}
851EntityBase *a = SK.GetEntity(ha);
852EntityBase *b = SK.GetEntity(hb);
853
854Expr *au, *av, *bu, *bv;
855a->PointGetExprsInWorkplane(workplane, &au, &av);
856b->PointGetExprsInWorkplane(workplane, &bu, &bv);
857
858AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0);
859return;
860}
861
862case Type::SAME_ORIENTATION: {
863EntityBase *a = SK.GetEntity(entityA);
864EntityBase *b = SK.GetEntity(entityB);
865
866ExprVector au = a->NormalExprsU(),
867an = a->NormalExprsN();
868ExprVector bu = b->NormalExprsU(),
869bv = b->NormalExprsV(),
870bn = b->NormalExprsN();
871
872ExprVector eq = VectorsParallel3d(an, bn, valP);
873AddEq(l, eq.x, 0);
874AddEq(l, eq.y, 1);
875AddEq(l, eq.z, 2);
876Expr *d1 = au.Dot(bv);
877Expr *d2 = au.Dot(bu);
878// Allow either orientation for the coordinate system, depending
879// on how it was drawn.
880if(fabs(d1->Eval()) < fabs(d2->Eval())) {
881AddEq(l, d1, 3);
882} else {
883AddEq(l, d2, 3);
884}
885return;
886}
887
888case Type::PERPENDICULAR:
889case Type::ANGLE: {
890EntityBase *a = SK.GetEntity(entityA);
891EntityBase *b = SK.GetEntity(entityB);
892ExprVector ae = a->VectorGetExprs();
893ExprVector be = b->VectorGetExprs();
894if(other) ae = ae.ScaledBy(Expr::From(-1));
895Expr *c = DirectionCosine(workplane, ae, be);
896
897if(type == Type::ANGLE) {
898// The direction cosine is equal to the cosine of the
899// specified angle
900Expr *rads = exA->Times(Expr::From(PI/180)),
901*rc = rads->Cos();
902double arc = fabs(rc->Eval());
903// avoid false detection of inconsistent systems by gaining
904// up as the difference in dot products gets small at small
905// angles; doubles still have plenty of precision, only
906// problem is that rank test
907Expr *mult = Expr::From(arc > 0.99 ? 0.01/(1.00001 - arc) : 1);
908AddEq(l, (c->Minus(rc))->Times(mult), 0);
909} else {
910// The dot product (and therefore the direction cosine)
911// is equal to zero, perpendicular.
912AddEq(l, c, 0);
913}
914return;
915}
916
917case Type::EQUAL_ANGLE: {
918EntityBase *a = SK.GetEntity(entityA);
919EntityBase *b = SK.GetEntity(entityB);
920EntityBase *c = SK.GetEntity(entityC);
921EntityBase *d = SK.GetEntity(entityD);
922ExprVector ae = a->VectorGetExprs();
923ExprVector be = b->VectorGetExprs();
924ExprVector ce = c->VectorGetExprs();
925ExprVector de = d->VectorGetExprs();
926
927if(other) ae = ae.ScaledBy(Expr::From(-1));
928
929Expr *cab = DirectionCosine(workplane, ae, be);
930Expr *ccd = DirectionCosine(workplane, ce, de);
931
932AddEq(l, cab->Minus(ccd), 0);
933return;
934}
935
936case Type::ARC_LINE_TANGENT: {
937EntityBase *arc = SK.GetEntity(entityA);
938EntityBase *line = SK.GetEntity(entityB);
939
940ExprVector ac = SK.GetEntity(arc->point[0])->PointGetExprs();
941ExprVector ap =
942SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs();
943
944ExprVector ld = line->VectorGetExprs();
945
946// The line is perpendicular to the radius
947AddEq(l, ld.Dot(ac.Minus(ap)), 0);
948return;
949}
950
951case Type::CUBIC_LINE_TANGENT: {
952EntityBase *cubic = SK.GetEntity(entityA);
953EntityBase *line = SK.GetEntity(entityB);
954
955ExprVector a;
956if(other) {
957a = cubic->CubicGetFinishTangentExprs();
958} else {
959a = cubic->CubicGetStartTangentExprs();
960}
961
962ExprVector b = line->VectorGetExprs();
963
964if(workplane == EntityBase::FREE_IN_3D) {
965ExprVector eq = VectorsParallel3d(a, b, valP);
966AddEq(l, eq);
967} else {
968EntityBase *w = SK.GetEntity(workplane);
969ExprVector wn = w->Normal()->NormalExprsN();
970AddEq(l, (a.Cross(b)).Dot(wn), 0);
971}
972return;
973}
974
975case Type::CURVE_CURVE_TANGENT: {
976bool parallel = true;
977int i;
978ExprVector dir[2];
979for(i = 0; i < 2; i++) {
980EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB);
981bool oth = (i == 0) ? other : other2;
982
983if(e->type == Entity::Type::ARC_OF_CIRCLE) {
984ExprVector center, endpoint;
985center = SK.GetEntity(e->point[0])->PointGetExprs();
986endpoint =
987SK.GetEntity(e->point[oth ? 2 : 1])->PointGetExprs();
988dir[i] = endpoint.Minus(center);
989// We're using the vector from the center of the arc to
990// an endpoint; so that's normal to the tangent, not
991// parallel.
992parallel = !parallel;
993} else if(e->type == Entity::Type::CUBIC) { // BRANCH_ALWAYS_TAKEN
994if(oth) {
995dir[i] = e->CubicGetFinishTangentExprs();
996} else {
997dir[i] = e->CubicGetStartTangentExprs();
998}
999} else {
1000ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT");
1001}
1002}
1003if(parallel) {
1004EntityBase *w = SK.GetEntity(workplane);
1005ExprVector wn = w->Normal()->NormalExprsN();
1006AddEq(l, ((dir[0]).Cross(dir[1])).Dot(wn), 0);
1007} else {
1008AddEq(l, (dir[0]).Dot(dir[1]), 0);
1009}
1010return;
1011}
1012
1013case Type::PARALLEL: {
1014EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB);
1015ExprVector a = ea->VectorGetExprsInWorkplane(workplane);
1016ExprVector b = eb->VectorGetExprsInWorkplane(workplane);
1017
1018if(workplane == EntityBase::FREE_IN_3D) {
1019ExprVector eq = VectorsParallel3d(a, b, valP);
1020AddEq(l, eq);
1021} else {
1022// We use expressions written in workplane csys, so we can assume the workplane
1023// normal is (0, 0, 1). We can write the equation as:
1024// Expr *eq = a.Cross(b).Dot(ExprVector::From(0.0, 0.0, 1.0));
1025// but this will just result in elimination of x and y terms after dot product.
1026// We can only use the z expression:
1027// Expr *eq = a.Cross(b).z;
1028// but it's more efficient to write it in the terms of pseudo-scalar product:
1029Expr *eq = (a.x->Times(b.y))->Minus(a.y->Times(b.x));
1030AddEq(l, eq, 0);
1031}
1032
1033return;
1034}
1035
1036case Type::WHERE_DRAGGED: {
1037EntityBase *ep = SK.GetEntity(ptA);
1038if(workplane == EntityBase::FREE_IN_3D) {
1039ExprVector ev = ep->PointGetExprs();
1040Vector v = ep->PointGetNum();
1041
1042AddEq(l, ev.x->Minus(Expr::From(v.x)), 0);
1043AddEq(l, ev.y->Minus(Expr::From(v.y)), 1);
1044AddEq(l, ev.z->Minus(Expr::From(v.z)), 2);
1045} else {
1046Expr *u, *v;
1047ep->PointGetExprsInWorkplane(workplane, &u, &v);
1048AddEq(l, u->Minus(Expr::From(u->Eval())), 0);
1049AddEq(l, v->Minus(Expr::From(v->Eval())), 1);
1050}
1051return;
1052}
1053
1054case Type::COMMENT:
1055return;
1056}
1057ssassert(false, "Unexpected constraint ID");
1058}
1059
1060