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