1 //-----------------------------------------------------------------------------
2 // Implementation of the Constraint menu, to create new constraints in
3 // the sketch.
4 //
5 // Copyright 2008-2013 Jonathan Westhues.
6 //-----------------------------------------------------------------------------
7 #include "solvespace.h"
8 
DescriptionString(void)9 std::string Constraint::DescriptionString(void) {
10     const char *s;
11     switch(type) {
12         case POINTS_COINCIDENT:     s = "pts-coincident"; break;
13         case PT_PT_DISTANCE:        s = "pt-pt-distance"; break;
14         case PT_LINE_DISTANCE:      s = "pt-line-distance"; break;
15         case PT_PLANE_DISTANCE:     s = "pt-plane-distance"; break;
16         case PT_FACE_DISTANCE:      s = "pt-face-distance"; break;
17         case PROJ_PT_DISTANCE:      s = "proj-pt-pt-distance"; break;
18         case PT_IN_PLANE:           s = "pt-in-plane"; break;
19         case PT_ON_LINE:            s = "pt-on-line"; break;
20         case PT_ON_FACE:            s = "pt-on-face"; break;
21         case EQUAL_LENGTH_LINES:    s = "eq-length"; break;
22         case EQ_LEN_PT_LINE_D:      s = "eq-length-and-pt-ln-dist"; break;
23         case EQ_PT_LN_DISTANCES:    s = "eq-pt-line-distances"; break;
24         case LENGTH_RATIO:          s = "length-ratio"; break;
25         case LENGTH_DIFFERENCE:     s = "length-difference"; break;
26         case SYMMETRIC:             s = "symmetric"; break;
27         case SYMMETRIC_HORIZ:       s = "symmetric-h"; break;
28         case SYMMETRIC_VERT:        s = "symmetric-v"; break;
29         case SYMMETRIC_LINE:        s = "symmetric-line"; break;
30         case AT_MIDPOINT:           s = "at-midpoint"; break;
31         case HORIZONTAL:            s = "horizontal"; break;
32         case VERTICAL:              s = "vertical"; break;
33         case DIAMETER:              s = "diameter"; break;
34         case PT_ON_CIRCLE:          s = "pt-on-circle"; break;
35         case SAME_ORIENTATION:      s = "same-orientation"; break;
36         case ANGLE:                 s = "angle"; break;
37         case PARALLEL:              s = "parallel"; break;
38         case ARC_LINE_TANGENT:      s = "arc-line-tangent"; break;
39         case CUBIC_LINE_TANGENT:    s = "cubic-line-tangent"; break;
40         case CURVE_CURVE_TANGENT:   s = "curve-curve-tangent"; break;
41         case PERPENDICULAR:         s = "perpendicular"; break;
42         case EQUAL_RADIUS:          s = "eq-radius"; break;
43         case EQUAL_ANGLE:           s = "eq-angle"; break;
44         case EQUAL_LINE_ARC_LEN:    s = "eq-line-len-arc-len"; break;
45         case WHERE_DRAGGED:         s = "lock-where-dragged"; break;
46         case COMMENT:               s = "comment"; break;
47         default:                    s = "???"; break;
48     }
49 
50     return ssprintf("c%03x-%s", h.v, s);
51 }
52 
53 #ifndef LIBRARY
54 
55 //-----------------------------------------------------------------------------
56 // Delete all constraints with the specified type, entityA, ptA. We use this
57 // when auto-removing constraints that would become redundant.
58 //-----------------------------------------------------------------------------
DeleteAllConstraintsFor(int type,hEntity entityA,hEntity ptA)59 void Constraint::DeleteAllConstraintsFor(int type, hEntity entityA, hEntity ptA)
60 {
61     SK.constraint.ClearTags();
62     for(int i = 0; i < SK.constraint.n; i++) {
63         ConstraintBase *ct = &(SK.constraint.elem[i]);
64         if(ct->type != type) continue;
65 
66         if(ct->entityA.v != entityA.v) continue;
67         if(ct->ptA.v != ptA.v) continue;
68         ct->tag = 1;
69     }
70     SK.constraint.RemoveTagged();
71     // And no need to do anything special, since nothing
72     // ever depends on a constraint. But do clear the
73     // hover, in case the just-deleted constraint was
74     // hovered.
75     SS.GW.hover.Clear();
76 }
77 
AddConstraint(Constraint * c)78 hConstraint Constraint::AddConstraint(Constraint *c) {
79     return AddConstraint(c, true);
80 }
81 
AddConstraint(Constraint * c,bool rememberForUndo)82 hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) {
83     if(rememberForUndo) SS.UndoRemember();
84 
85     SK.constraint.AddAndAssignId(c);
86 
87     SS.MarkGroupDirty(c->group);
88     SS.ScheduleGenerateAll();
89     return c->h;
90 }
91 
Constrain(int type,hEntity ptA,hEntity ptB,hEntity entityA,hEntity entityB,bool other,bool other2)92 hConstraint Constraint::Constrain(int type, hEntity ptA, hEntity ptB,
93                                      hEntity entityA, hEntity entityB,
94                                      bool other, bool other2)
95 {
96     Constraint c = {};
97     c.group = SS.GW.activeGroup;
98     c.workplane = SS.GW.ActiveWorkplane();
99     c.type = type;
100     c.ptA = ptA;
101     c.ptB = ptB;
102     c.entityA = entityA;
103     c.entityB = entityB;
104     c.other = other;
105     c.other2 = other2;
106     return AddConstraint(&c, false);
107 }
108 
Constrain(int type,hEntity ptA,hEntity ptB,hEntity entityA)109 hConstraint Constraint::Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA){
110     return Constrain(type, ptA, ptB, entityA, Entity::NO_ENTITY, false, false);
111 }
112 
ConstrainCoincident(hEntity ptA,hEntity ptB)113 hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
114     return Constrain(POINTS_COINCIDENT, ptA, ptB,
115         Entity::NO_ENTITY, Entity::NO_ENTITY, false, false);
116 }
117 
MenuConstrain(int id)118 void Constraint::MenuConstrain(int id) {
119     Constraint c = {};
120     c.group = SS.GW.activeGroup;
121     c.workplane = SS.GW.ActiveWorkplane();
122 
123     SS.GW.GroupSelection();
124 #define gs (SS.GW.gs)
125 
126     switch(id) {
127         case GraphicsWindow::MNU_DISTANCE_DIA:
128         case GraphicsWindow::MNU_REF_DISTANCE: {
129             if(gs.points == 2 && gs.n == 2) {
130                 c.type = PT_PT_DISTANCE;
131                 c.ptA = gs.point[0];
132                 c.ptB = gs.point[1];
133             } else if(gs.lineSegments == 1 && gs.n == 1) {
134                 c.type = PT_PT_DISTANCE;
135                 Entity *e = SK.GetEntity(gs.entity[0]);
136                 c.ptA = e->point[0];
137                 c.ptB = e->point[1];
138             } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) {
139                 c.type = PROJ_PT_DISTANCE;
140                 c.ptA = gs.point[0];
141                 c.ptB = gs.point[1];
142                 c.entityA = gs.vector[0];
143             } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) {
144                 c.type = PT_PLANE_DISTANCE;
145                 c.ptA = gs.point[0];
146                 c.entityA = gs.entity[0];
147             } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
148                 c.type = PT_LINE_DISTANCE;
149                 c.ptA = gs.point[0];
150                 c.entityA = gs.entity[0];
151             } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
152                 c.type = PT_FACE_DISTANCE;
153                 c.ptA = gs.point[0];
154                 c.entityA = gs.face[0];
155             } else if(gs.circlesOrArcs == 1 && gs.n == 1) {
156                 c.type = DIAMETER;
157                 c.entityA = gs.entity[0];
158             } else {
159                 Error(
160 "Bad selection for distance / diameter constraint. This "
161 "constraint can apply to:\n\n"
162 "    * two points (distance between points)\n"
163 "    * a line segment (length)\n"
164 "    * two points and a line segment or normal (projected distance)\n"
165 "    * a workplane and a point (minimum distance)\n"
166 "    * a line segment and a point (minimum distance)\n"
167 "    * a plane face and a point (minimum distance)\n"
168 "    * a circle or an arc (diameter)\n");
169                 return;
170             }
171             if(c.type == PT_PT_DISTANCE || c.type == PROJ_PT_DISTANCE) {
172                 Vector n = SS.GW.projRight.Cross(SS.GW.projUp);
173                 Vector a = SK.GetEntity(c.ptA)->PointGetNum();
174                 Vector b = SK.GetEntity(c.ptB)->PointGetNum();
175                 c.disp.offset = n.Cross(a.Minus(b));
176                 c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale);
177             } else {
178                 c.disp.offset = Vector::From(0, 0, 0);
179             }
180 
181             if(id == GraphicsWindow::MNU_REF_DISTANCE) {
182                 c.reference = true;
183             }
184 
185             c.valA = 0;
186             c.ModifyToSatisfy();
187             AddConstraint(&c);
188             break;
189         }
190 
191         case GraphicsWindow::MNU_ON_ENTITY:
192             if(gs.points == 2 && gs.n == 2) {
193                 c.type = POINTS_COINCIDENT;
194                 c.ptA = gs.point[0];
195                 c.ptB = gs.point[1];
196             } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
197                 c.type = PT_IN_PLANE;
198                 c.ptA = gs.point[0];
199                 c.entityA = gs.entity[0];
200             } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
201                 c.type = PT_ON_LINE;
202                 c.ptA = gs.point[0];
203                 c.entityA = gs.entity[0];
204             } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
205                 c.type = PT_ON_CIRCLE;
206                 c.ptA = gs.point[0];
207                 c.entityA = gs.entity[0];
208             } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) {
209                 c.type = PT_ON_FACE;
210                 c.ptA = gs.point[0];
211                 c.entityA = gs.face[0];
212             } else {
213                 Error("Bad selection for on point / curve / plane constraint. "
214                       "This constraint can apply to:\n\n"
215                       "    * two points (points coincident)\n"
216                       "    * a point and a workplane (point in plane)\n"
217                       "    * a point and a line segment (point on line)\n"
218                       "    * a point and a circle or arc (point on curve)\n"
219                       "    * a point and a plane face (point on face)\n");
220                 return;
221             }
222             AddConstraint(&c);
223             break;
224 
225         case GraphicsWindow::MNU_EQUAL:
226             if(gs.lineSegments == 2 && gs.n == 2) {
227                 c.type = EQUAL_LENGTH_LINES;
228                 c.entityA = gs.entity[0];
229                 c.entityB = gs.entity[1];
230             } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
231                 c.type = EQ_PT_LN_DISTANCES;
232                 c.entityA = gs.entity[0];
233                 c.ptA = gs.point[0];
234                 c.entityB = gs.entity[1];
235                 c.ptB = gs.point[1];
236             } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
237                 // The same line segment for the distances, but different
238                 // points.
239                 c.type = EQ_PT_LN_DISTANCES;
240                 c.entityA = gs.entity[0];
241                 c.ptA = gs.point[0];
242                 c.entityB = gs.entity[0];
243                 c.ptB = gs.point[1];
244             } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
245                 c.type = EQ_LEN_PT_LINE_D;
246                 c.entityA = gs.entity[0];
247                 c.entityB = gs.entity[1];
248                 c.ptA = gs.point[0];
249             } else if(gs.vectors == 4 && gs.n == 4) {
250                 c.type = EQUAL_ANGLE;
251                 c.entityA = gs.vector[0];
252                 c.entityB = gs.vector[1];
253                 c.entityC = gs.vector[2];
254                 c.entityD = gs.vector[3];
255             } else if(gs.vectors == 3 && gs.n == 3) {
256                 c.type = EQUAL_ANGLE;
257                 c.entityA = gs.vector[0];
258                 c.entityB = gs.vector[1];
259                 c.entityC = gs.vector[1];
260                 c.entityD = gs.vector[2];
261             } else if(gs.circlesOrArcs == 2 && gs.n == 2) {
262                 c.type = EQUAL_RADIUS;
263                 c.entityA = gs.entity[0];
264                 c.entityB = gs.entity[1];
265             } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
266                 c.type = EQUAL_LINE_ARC_LEN;
267                 if(SK.GetEntity(gs.entity[0])->type == Entity::ARC_OF_CIRCLE) {
268                     c.entityA = gs.entity[1];
269                     c.entityB = gs.entity[0];
270                 } else {
271                     c.entityA = gs.entity[0];
272                     c.entityB = gs.entity[1];
273                 }
274             } else {
275                 Error("Bad selection for equal length / radius constraint. "
276                       "This constraint can apply to:\n\n"
277                       "    * two line segments (equal length)\n"
278                       "    * two line segments and two points "
279                               "(equal point-line distances)\n"
280                       "    * a line segment and two points "
281                               "(equal point-line distances)\n"
282                       "    * a line segment, and a point and line segment "
283                               "(point-line distance equals length)\n"
284                       "    * four line segments or normals "
285                               "(equal angle between A,B and C,D)\n"
286                       "    * three line segments or normals "
287                               "(equal angle between A,B and B,C)\n"
288                       "    * two circles or arcs (equal radius)\n"
289                       "    * a line segment and an arc "
290                               "(line segment length equals arc length)\n");
291                 return;
292             }
293             if(c.type == EQUAL_ANGLE) {
294                 // Infer the nearest supplementary angle from the sketch.
295                 Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
296                        b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
297                        a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
298                        b2 = SK.GetEntity(c.entityD)->VectorGetNum();
299                 double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
300 
301                 if(d1*d2 < 0) {
302                     c.other = true;
303                 }
304             }
305             AddConstraint(&c);
306             break;
307 
308         case GraphicsWindow::MNU_RATIO:
309             if(gs.lineSegments == 2 && gs.n == 2) {
310                 c.type = LENGTH_RATIO;
311                 c.entityA = gs.entity[0];
312                 c.entityB = gs.entity[1];
313             } else {
314                 Error("Bad selection for length ratio constraint. This "
315                       "constraint can apply to:\n\n"
316                       "    * two line segments\n");
317                 return;
318             }
319 
320             c.valA = 0;
321             c.ModifyToSatisfy();
322             AddConstraint(&c);
323             break;
324 
325         case GraphicsWindow::MNU_DIFFERENCE:
326             if(gs.lineSegments == 2 && gs.n == 2) {
327                 c.type = LENGTH_DIFFERENCE;
328                 c.entityA = gs.entity[0];
329                 c.entityB = gs.entity[1];
330             } else {
331                 Error("Bad selection for length difference constraint. This "
332                       "constraint can apply to:\n\n"
333                       "    * two line segments\n");
334                 return;
335             }
336 
337             c.valA = 0;
338             c.ModifyToSatisfy();
339             AddConstraint(&c);
340             break;
341 
342         case GraphicsWindow::MNU_AT_MIDPOINT:
343             if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
344                 c.type = AT_MIDPOINT;
345                 c.entityA = gs.entity[0];
346                 c.ptA = gs.point[0];
347 
348                 // If a point is at-midpoint, then no reason to also constrain
349                 // it on-line; so auto-remove that.
350                 DeleteAllConstraintsFor(PT_ON_LINE, c.entityA, c.ptA);
351             } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
352                 c.type = AT_MIDPOINT;
353                 int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
354                 c.entityA = gs.entity[i];
355                 c.entityB = gs.entity[1-i];
356             } else {
357                 Error("Bad selection for at midpoint constraint. This "
358                       "constraint can apply to:\n\n"
359                       "    * a line segment and a point "
360                             "(point at midpoint)\n"
361                       "    * a line segment and a workplane "
362                             "(line's midpoint on plane)\n");
363                 return;
364             }
365             AddConstraint(&c);
366             break;
367 
368         case GraphicsWindow::MNU_SYMMETRIC:
369             if(gs.points == 2 &&
370                                 ((gs.workplanes == 1 && gs.n == 3) ||
371                                  (gs.n == 2)))
372             {
373                 if(gs.entities > 0)
374                     c.entityA = gs.entity[0];
375                 c.ptA = gs.point[0];
376                 c.ptB = gs.point[1];
377             } else if(gs.lineSegments == 1 &&
378                                 ((gs.workplanes == 1 && gs.n == 2) ||
379                                  (gs.n == 1)))
380             {
381                 Entity *line;
382                 if(SK.GetEntity(gs.entity[0])->IsWorkplane()) {
383                     line = SK.GetEntity(gs.entity[1]);
384                     c.entityA = gs.entity[0];
385                 } else {
386                     line = SK.GetEntity(gs.entity[0]);
387                 }
388                 c.ptA = line->point[0];
389                 c.ptB = line->point[1];
390             } else if(SS.GW.LockedInWorkplane()
391                         && gs.lineSegments == 2 && gs.n == 2)
392             {
393                 Entity *l0 = SK.GetEntity(gs.entity[0]),
394                        *l1 = SK.GetEntity(gs.entity[1]);
395 
396                 if((l1->group.v != SS.GW.activeGroup.v) ||
397                    (l1->construction && !(l0->construction)))
398                 {
399                     swap(l0, l1);
400                 }
401                 c.ptA = l1->point[0];
402                 c.ptB = l1->point[1];
403                 c.entityA = l0->h;
404                 c.type = SYMMETRIC_LINE;
405             } else if(SS.GW.LockedInWorkplane()
406                         && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3)
407             {
408                 c.ptA = gs.point[0];
409                 c.ptB = gs.point[1];
410                 c.entityA = gs.entity[0];
411                 c.type = SYMMETRIC_LINE;
412             } else {
413                 Error("Bad selection for symmetric constraint. This constraint "
414                       "can apply to:\n\n"
415                       "    * two points or a line segment "
416                           "(symmetric about workplane's coordinate axis)\n"
417                       "    * line segment, and two points or a line segment "
418                           "(symmetric about line segment)\n"
419                       "    * workplane, and two points or a line segment "
420                           "(symmetric about workplane)\n");
421                 return;
422             }
423             if(c.type != 0) {
424                 // Already done, symmetry about a line segment in a workplane
425             } else if(c.entityA.v == Entity::NO_ENTITY.v) {
426                 // Horizontal / vertical symmetry, implicit symmetry plane
427                 // normal to the workplane
428                 if(c.workplane.v == Entity::FREE_IN_3D.v) {
429                     Error("Must be locked in to workplane when constraining "
430                           "symmetric without an explicit symmetry plane.");
431                     return;
432                 }
433                 Vector pa = SK.GetEntity(c.ptA)->PointGetNum();
434                 Vector pb = SK.GetEntity(c.ptB)->PointGetNum();
435                 Vector dp = pa.Minus(pb);
436                 EntityBase *norm = SK.GetEntity(c.workplane)->Normal();;
437                 Vector u = norm->NormalU(), v = norm->NormalV();
438                 if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) {
439                     c.type = SYMMETRIC_HORIZ;
440                 } else {
441                     c.type = SYMMETRIC_VERT;
442                 }
443                 if(gs.lineSegments == 1) {
444                     // If this line segment is already constrained horiz or
445                     // vert, then auto-remove that redundant constraint.
446                     DeleteAllConstraintsFor(HORIZONTAL, (gs.entity[0]),
447                         Entity::NO_ENTITY);
448                     DeleteAllConstraintsFor(VERTICAL, (gs.entity[0]),
449                         Entity::NO_ENTITY);
450 
451                 }
452             } else {
453                 // Symmetry with a symmetry plane specified explicitly.
454                 c.type = SYMMETRIC;
455             }
456             AddConstraint(&c);
457             break;
458 
459         case GraphicsWindow::MNU_VERTICAL:
460         case GraphicsWindow::MNU_HORIZONTAL: {
461             hEntity ha, hb;
462             if(c.workplane.v == Entity::FREE_IN_3D.v) {
463                 Error("Select workplane before constraining horiz/vert.");
464                 return;
465             }
466             if(gs.lineSegments == 1 && gs.n == 1) {
467                 c.entityA = gs.entity[0];
468                 Entity *e = SK.GetEntity(c.entityA);
469                 ha = e->point[0];
470                 hb = e->point[1];
471             } else if(gs.points == 2 && gs.n == 2) {
472                 ha = c.ptA = gs.point[0];
473                 hb = c.ptB = gs.point[1];
474             } else {
475                 Error("Bad selection for horizontal / vertical constraint. "
476                       "This constraint can apply to:\n\n"
477                       "    * two points\n"
478                       "    * a line segment\n");
479                 return;
480             }
481             if(id == GraphicsWindow::MNU_HORIZONTAL) {
482                 c.type = HORIZONTAL;
483             } else {
484                 c.type = VERTICAL;
485             }
486             AddConstraint(&c);
487             break;
488         }
489 
490         case GraphicsWindow::MNU_ORIENTED_SAME: {
491             if(gs.anyNormals == 2 && gs.n == 2) {
492                 c.type = SAME_ORIENTATION;
493                 c.entityA = gs.anyNormal[0];
494                 c.entityB = gs.anyNormal[1];
495             } else {
496                 Error("Bad selection for same orientation constraint. This "
497                       "constraint can apply to:\n\n"
498                       "    * two normals\n");
499                 return;
500             }
501             SS.UndoRemember();
502 
503             Entity *nfree = SK.GetEntity(c.entityA);
504             Entity *nref  = SK.GetEntity(c.entityB);
505             if(nref->group.v == SS.GW.activeGroup.v) {
506                 swap(nref, nfree);
507             }
508             if(nfree->group.v == SS.GW.activeGroup.v &&
509                nref ->group.v != SS.GW.activeGroup.v)
510             {
511                 // nfree is free, and nref is locked (since it came from a
512                 // previous group); so let's force nfree aligned to nref,
513                 // and make convergence easy
514                 Vector ru = nref ->NormalU(), rv = nref ->NormalV();
515                 Vector fu = nfree->NormalU(), fv = nfree->NormalV();
516 
517                 if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) {
518                     // There might be an odd*90 degree rotation about the
519                     // normal vector; allow that, since the numerical
520                     // constraint does
521                     swap(ru, rv);
522                 }
523                 fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1);
524                 fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1);
525 
526                 nfree->NormalForceTo(Quaternion::From(fu, fv));
527             }
528             AddConstraint(&c, false);
529             break;
530         }
531 
532         case GraphicsWindow::MNU_OTHER_ANGLE:
533             if(gs.constraints == 1 && gs.n == 0) {
534                 Constraint *c = SK.GetConstraint(gs.constraint[0]);
535                 if(c->type == ANGLE) {
536                     SS.UndoRemember();
537                     c->other = !(c->other);
538                     c->ModifyToSatisfy();
539                     break;
540                 }
541                 if(c->type == EQUAL_ANGLE) {
542                     SS.UndoRemember();
543                     c->other = !(c->other);
544                     SS.MarkGroupDirty(c->group);
545                     SS.ScheduleGenerateAll();
546                     break;
547                 }
548             }
549             Error("Must select an angle constraint.");
550             return;
551 
552         case GraphicsWindow::MNU_REFERENCE:
553             if(gs.constraints == 1 && gs.n == 0) {
554                 Constraint *c = SK.GetConstraint(gs.constraint[0]);
555                 if(c->HasLabel() && c->type != COMMENT) {
556                     (c->reference) = !(c->reference);
557                     SK.GetGroup(c->group)->clean = false;
558                     SS.GenerateAll();
559                     break;
560                 }
561             }
562             Error("Must select a constraint with associated label.");
563             return;
564 
565         case GraphicsWindow::MNU_ANGLE:
566         case GraphicsWindow::MNU_REF_ANGLE: {
567             if(gs.vectors == 2 && gs.n == 2) {
568                 c.type = ANGLE;
569                 c.entityA = gs.vector[0];
570                 c.entityB = gs.vector[1];
571                 c.valA = 0;
572             } else {
573                 Error("Bad selection for angle constraint. This constraint "
574                       "can apply to:\n\n"
575                       "    * two line segments\n"
576                       "    * a line segment and a normal\n"
577                       "    * two normals\n");
578                 return;
579             }
580 
581             Entity *ea = SK.GetEntity(c.entityA),
582                    *eb = SK.GetEntity(c.entityB);
583             if(ea->type == Entity::LINE_SEGMENT &&
584                eb->type == Entity::LINE_SEGMENT)
585             {
586                 Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(),
587                        a1 = SK.GetEntity(ea->point[1])->PointGetNum(),
588                        b0 = SK.GetEntity(eb->point[0])->PointGetNum(),
589                        b1 = SK.GetEntity(eb->point[1])->PointGetNum();
590                 if(a0.Equals(b0) || a1.Equals(b1)) {
591                     // okay, vectors should be drawn in same sense
592                 } else if(a0.Equals(b1) || a1.Equals(b0)) {
593                     // vectors are in opposite sense
594                     c.other = true;
595                 } else {
596                     // no shared point; not clear which intersection to draw
597                 }
598             }
599 
600             if(id == GraphicsWindow::MNU_REF_ANGLE) {
601                 c.reference = true;
602             }
603 
604             c.ModifyToSatisfy();
605             AddConstraint(&c);
606             break;
607         }
608 
609         case GraphicsWindow::MNU_PARALLEL:
610             if(gs.vectors == 2 && gs.n == 2) {
611                 c.type = PARALLEL;
612                 c.entityA = gs.vector[0];
613                 c.entityB = gs.vector[1];
614             } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
615                 Entity *line = SK.GetEntity(gs.entity[0]);
616                 Entity *arc  = SK.GetEntity(gs.entity[1]);
617                 if(line->type == Entity::ARC_OF_CIRCLE) {
618                     swap(line, arc);
619                 }
620                 Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
621                        l1 = SK.GetEntity(line->point[1])->PointGetNum();
622                 Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
623                        a2 = SK.GetEntity(arc->point[2])->PointGetNum();
624 
625                 if(l0.Equals(a1) || l1.Equals(a1)) {
626                     c.other = false;
627                 } else if(l0.Equals(a2) || l1.Equals(a2)) {
628                     c.other = true;
629                 } else {
630                     Error("The tangent arc and line segment must share an "
631                           "endpoint. Constrain them with Constrain -> "
632                           "On Point before constraining tangent.");
633                     return;
634                 }
635                 c.type = ARC_LINE_TANGENT;
636                 c.entityA = arc->h;
637                 c.entityB = line->h;
638             } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
639                 Entity *line  = SK.GetEntity(gs.entity[0]);
640                 Entity *cubic = SK.GetEntity(gs.entity[1]);
641                 if(line->type == Entity::CUBIC) {
642                     swap(line, cubic);
643                 }
644                 Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
645                        l1 = SK.GetEntity(line->point[1])->PointGetNum();
646                 Vector as = cubic->CubicGetStartNum(),
647                        af = cubic->CubicGetFinishNum();
648 
649                 if(l0.Equals(as) || l1.Equals(as)) {
650                     c.other = false;
651                 } else if(l0.Equals(af) || l1.Equals(af)) {
652                     c.other = true;
653                 } else {
654                     Error("The tangent cubic and line segment must share an "
655                           "endpoint. Constrain them with Constrain -> "
656                           "On Point before constraining tangent.");
657                     return;
658                 }
659                 c.type = CUBIC_LINE_TANGENT;
660                 c.entityA = cubic->h;
661                 c.entityB = line->h;
662             } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
663                 if(!SS.GW.LockedInWorkplane()) {
664                     Error("Curve-curve tangency must apply in workplane.");
665                     return;
666                 }
667                 Entity *eA = SK.GetEntity(gs.entity[0]),
668                        *eB = SK.GetEntity(gs.entity[1]);
669                 Vector as = eA->EndpointStart(),
670                        af = eA->EndpointFinish(),
671                        bs = eB->EndpointStart(),
672                        bf = eB->EndpointFinish();
673                 if(as.Equals(bs)) {
674                     c.other = false; c.other2 = false;
675                 } else if(as.Equals(bf)) {
676                     c.other = false; c.other2 = true;
677                 } else if(af.Equals(bs)) {
678                     c.other = true; c.other2 = false;
679                 } else if(af.Equals(bf)) {
680                     c.other = true; c.other2 = true;
681                 } else {
682                     Error("The curves must share an endpoint. Constrain them "
683                           "with Constrain -> On Point before constraining "
684                           "tangent.");
685                     return;
686                 }
687                 c.type = CURVE_CURVE_TANGENT;
688                 c.entityA = eA->h;
689                 c.entityB = eB->h;
690             } else {
691                 Error("Bad selection for parallel / tangent constraint. This "
692                       "constraint can apply to:\n\n"
693                       "    * two line segments (parallel)\n"
694                       "    * a line segment and a normal (parallel)\n"
695                       "    * two normals (parallel)\n"
696                       "    * two line segments, arcs, or beziers, that share "
697                             "an endpoint (tangent)\n");
698                 return;
699             }
700             AddConstraint(&c);
701             break;
702 
703         case GraphicsWindow::MNU_PERPENDICULAR:
704             if(gs.vectors == 2 && gs.n == 2) {
705                 c.type = PERPENDICULAR;
706                 c.entityA = gs.vector[0];
707                 c.entityB = gs.vector[1];
708             } else {
709                 Error("Bad selection for perpendicular constraint. This "
710                       "constraint can apply to:\n\n"
711                       "    * two line segments\n"
712                       "    * a line segment and a normal\n"
713                       "    * two normals\n");
714                 return;
715             }
716             AddConstraint(&c);
717             break;
718 
719         case GraphicsWindow::MNU_WHERE_DRAGGED:
720             if(gs.points == 1 && gs.n == 1) {
721                 c.type = WHERE_DRAGGED;
722                 c.ptA = gs.point[0];
723             } else {
724                 Error("Bad selection for lock point where dragged constraint. "
725                       "This constraint can apply to:\n\n"
726                       "    * a point\n");
727                 return;
728             }
729             AddConstraint(&c);
730             break;
731 
732         case GraphicsWindow::MNU_COMMENT:
733             SS.GW.pending.operation = GraphicsWindow::MNU_COMMENT;
734             SS.GW.pending.description = "click center of comment text";
735             SS.ScheduleShowTW();
736             break;
737 
738         default: oops();
739     }
740 
741     SS.GW.ClearSelection();
742     InvalidateGraphics();
743 }
744 
745 #endif /* ! LIBRARY */
746