Solvespace
925 строк · 39.4 Кб
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
9std::string Constraint::DescriptionString() const {
10std::string s;
11switch(type) {
12case Type::POINTS_COINCIDENT: s = C_("constr-name", "pts-coincident"); break;
13case Type::PT_PT_DISTANCE: s = C_("constr-name", "pt-pt-distance"); break;
14case Type::PT_LINE_DISTANCE: s = C_("constr-name", "pt-line-distance"); break;
15case Type::PT_PLANE_DISTANCE: s = C_("constr-name", "pt-plane-distance"); break;
16case Type::PT_FACE_DISTANCE: s = C_("constr-name", "pt-face-distance"); break;
17case Type::PROJ_PT_DISTANCE: s = C_("constr-name", "proj-pt-pt-distance"); break;
18case Type::PT_IN_PLANE: s = C_("constr-name", "pt-in-plane"); break;
19case Type::PT_ON_LINE: s = C_("constr-name", "pt-on-line"); break;
20case Type::PT_ON_FACE: s = C_("constr-name", "pt-on-face"); break;
21case Type::EQUAL_LENGTH_LINES: s = C_("constr-name", "eq-length"); break;
22case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break;
23case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break;
24case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break;
25case Type::ARC_ARC_LEN_RATIO: s = C_("constr-name", "arc-arc-length-ratio"); break;
26case Type::ARC_LINE_LEN_RATIO: s = C_("constr-name", "arc-line-length-ratio"); break;
27case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break;
28case Type::ARC_ARC_DIFFERENCE: s = C_("constr-name", "arc-arc-len-difference"); break;
29case Type::ARC_LINE_DIFFERENCE: s = C_("constr-name", "arc-line-len-difference"); break;
30case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break;
31case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break;
32case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break;
33case Type::SYMMETRIC_LINE: s = C_("constr-name", "symmetric-line"); break;
34case Type::AT_MIDPOINT: s = C_("constr-name", "at-midpoint"); break;
35case Type::HORIZONTAL: s = C_("constr-name", "horizontal"); break;
36case Type::VERTICAL: s = C_("constr-name", "vertical"); break;
37case Type::DIAMETER: s = C_("constr-name", "diameter"); break;
38case Type::PT_ON_CIRCLE: s = C_("constr-name", "pt-on-circle"); break;
39case Type::SAME_ORIENTATION: s = C_("constr-name", "same-orientation"); break;
40case Type::ANGLE: s = C_("constr-name", "angle"); break;
41case Type::PARALLEL: s = C_("constr-name", "parallel"); break;
42case Type::ARC_LINE_TANGENT: s = C_("constr-name", "arc-line-tangent"); break;
43case Type::CUBIC_LINE_TANGENT: s = C_("constr-name", "cubic-line-tangent"); break;
44case Type::CURVE_CURVE_TANGENT: s = C_("constr-name", "curve-curve-tangent"); break;
45case Type::PERPENDICULAR: s = C_("constr-name", "perpendicular"); break;
46case Type::EQUAL_RADIUS: s = C_("constr-name", "eq-radius"); break;
47case Type::EQUAL_ANGLE: s = C_("constr-name", "eq-angle"); break;
48case Type::EQUAL_LINE_ARC_LEN: s = C_("constr-name", "eq-line-len-arc-len"); break;
49case Type::WHERE_DRAGGED: s = C_("constr-name", "lock-where-dragged"); break;
50case Type::COMMENT: s = C_("constr-name", "comment"); break;
51default: s = "???"; break;
52}
53
54return ssprintf("c%03x-%s", h.v, s.c_str());
55}
56
57#ifndef LIBRARY
58
59//-----------------------------------------------------------------------------
60// Delete all constraints with the specified type, entityA, ptA. We use this
61// when auto-removing constraints that would become redundant.
62//-----------------------------------------------------------------------------
63void Constraint::DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA)
64{
65SK.constraint.ClearTags();
66for(auto &constraint : SK.constraint) {
67ConstraintBase *ct = &constraint;
68if(ct->type != type) continue;
69
70if(ct->entityA != entityA) continue;
71if(ct->ptA != ptA) continue;
72ct->tag = 1;
73}
74SK.constraint.RemoveTagged();
75// And no need to do anything special, since nothing
76// ever depends on a constraint. But do clear the
77// hover, in case the just-deleted constraint was
78// hovered.
79SS.GW.hover.Clear();
80}
81
82hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) {
83if(rememberForUndo) SS.UndoRemember();
84
85hConstraint hc = SK.constraint.AddAndAssignId(c);
86SK.GetConstraint(hc)->Generate(&SK.param);
87
88SS.MarkGroupDirty(c->group);
89SK.GetGroup(c->group)->dofCheckOk = false;
90return c->h;
91}
92
93hConstraint Constraint::Constrain(Constraint::Type type, hEntity ptA, hEntity ptB,
94hEntity entityA, hEntity entityB,
95bool other, bool other2)
96{
97Constraint c = {};
98c.group = SS.GW.activeGroup;
99c.workplane = SS.GW.ActiveWorkplane();
100c.type = type;
101c.ptA = ptA;
102c.ptB = ptB;
103c.entityA = entityA;
104c.entityB = entityB;
105c.other = other;
106c.other2 = other2;
107return AddConstraint(&c, /*rememberForUndo=*/false);
108}
109
110hConstraint Constraint::TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
111hEntity entityA, hEntity entityB,
112bool other, bool other2) {
113int rankBefore, rankAfter;
114SolveResult howBefore = SS.TestRankForGroup(SS.GW.activeGroup, &rankBefore);
115hConstraint hc = Constrain(type, ptA, ptB, entityA, entityB, other, other2);
116SolveResult howAfter = SS.TestRankForGroup(SS.GW.activeGroup, &rankAfter);
117// There are two cases where the constraint is clearly redundant:
118// * If the group wasn't overconstrained and now it is;
119// * If the group was overconstrained, and adding the constraint doesn't change rank at all.
120if((howBefore == SolveResult::OKAY && howAfter == SolveResult::REDUNDANT_OKAY) ||
121(howBefore == SolveResult::REDUNDANT_OKAY && howAfter == SolveResult::REDUNDANT_OKAY &&
122rankBefore == rankAfter)) {
123SK.constraint.RemoveById(hc);
124hc = {};
125}
126return hc;
127}
128
129hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
130return Constrain(Type::POINTS_COINCIDENT, ptA, ptB,
131Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
132}
133
134bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc) {
135Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
136l1 = SK.GetEntity(line->point[1])->PointGetNum();
137Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
138a2 = SK.GetEntity(arc->point[2])->PointGetNum();
139if(l0.Equals(a1) || l1.Equals(a1)) {
140c->other = false;
141} else if(l0.Equals(a2) || l1.Equals(a2)) {
142c->other = true;
143} else {
144Error(_("The tangent arc and line segment must share an "
145"endpoint. Constrain them with Constrain -> "
146"On Point before constraining tangent."));
147return false;
148}
149return true;
150}
151
152bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic) {
153Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
154l1 = SK.GetEntity(line->point[1])->PointGetNum();
155Vector as = cubic->CubicGetStartNum(),
156af = cubic->CubicGetFinishNum();
157
158if(l0.Equals(as) || l1.Equals(as)) {
159c->other = false;
160} else if(l0.Equals(af) || l1.Equals(af)) {
161c->other = true;
162} else {
163Error(_("The tangent cubic and line segment must share an "
164"endpoint. Constrain them with Constrain -> "
165"On Point before constraining tangent."));
166return false;
167}
168return true;
169}
170
171bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB) {
172Vector as = eA->EndpointStart(),
173af = eA->EndpointFinish(),
174bs = eB->EndpointStart(),
175bf = eB->EndpointFinish();
176if(as.Equals(bs)) {
177c->other = false;
178c->other2 = false;
179} else if(as.Equals(bf)) {
180c->other = false;
181c->other2 = true;
182} else if(af.Equals(bs)) {
183c->other = true;
184c->other2 = false;
185} else if(af.Equals(bf)) {
186c->other = true;
187c->other2 = true;
188} else {
189Error(_("The curves must share an endpoint. Constrain them "
190"with Constrain -> On Point before constraining "
191"tangent."));
192return false;
193}
194return true;
195}
196
197void Constraint::MenuConstrain(Command id) {
198std::vector<Constraint> newcons;
199
200Constraint c = {};
201c.group = SS.GW.activeGroup;
202c.workplane = SS.GW.ActiveWorkplane();
203
204SS.GW.GroupSelection();
205auto const &gs = SS.GW.gs;
206
207switch(id) {
208case Command::DISTANCE_DIA:
209case Command::REF_DISTANCE: {
210if(gs.points == 2 && gs.n == 2) {
211c.type = Type::PT_PT_DISTANCE;
212c.ptA = gs.point[0];
213c.ptB = gs.point[1];
214} else if(gs.lineSegments == 1 && gs.n == 1) {
215c.type = Type::PT_PT_DISTANCE;
216Entity *e = SK.GetEntity(gs.entity[0]);
217c.ptA = e->point[0];
218c.ptB = e->point[1];
219} else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) {
220c.type = Type::PROJ_PT_DISTANCE;
221c.ptA = gs.point[0];
222c.ptB = gs.point[1];
223c.entityA = gs.vector[0];
224} else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) {
225c.type = Type::PT_PLANE_DISTANCE;
226c.ptA = gs.point[0];
227c.entityA = gs.entity[0];
228} else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
229c.type = Type::PT_LINE_DISTANCE;
230c.ptA = gs.point[0];
231c.entityA = gs.entity[0];
232} else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
233c.type = Type::PT_FACE_DISTANCE;
234c.ptA = gs.point[0];
235c.entityA = gs.face[0];
236} else if(gs.circlesOrArcs == 1 && gs.n == 1) {
237c.type = Type::DIAMETER;
238c.entityA = gs.entity[0];
239Entity* arc = SK.GetEntity(gs.entity[0]);
240if ((arc->type == EntityBase::Type::ARC_OF_CIRCLE)
241&& (!SS.arcDimDefaultDiameter))
242{
243c.other = true;
244}
245} else {
246Error(_("Bad selection for distance / diameter constraint. This "
247"constraint can apply to:\n\n"
248" * two points (distance between points)\n"
249" * a line segment (length)\n"
250" * two points and a line segment or normal (projected distance)\n"
251" * a workplane and a point (minimum distance)\n"
252" * a line segment and a point (minimum distance)\n"
253" * a plane face and a point (minimum distance)\n"
254" * a circle or an arc (diameter)\n"));
255return;
256}
257if(c.type == Type::PT_PT_DISTANCE || c.type == Type::PROJ_PT_DISTANCE) {
258Vector n = SS.GW.projRight.Cross(SS.GW.projUp);
259Vector a = SK.GetEntity(c.ptA)->PointGetNum();
260Vector b = SK.GetEntity(c.ptB)->PointGetNum();
261c.disp.offset = n.Cross(a.Minus(b));
262c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale);
263} else {
264c.disp.offset = Vector::From(0, 0, 0);
265}
266
267if(id == Command::REF_DISTANCE) {
268c.reference = true;
269}
270
271c.valA = 0;
272c.ModifyToSatisfy();
273AddConstraint(&c);
274newcons.push_back(c);
275break;
276}
277
278case Command::ON_ENTITY:
279if(gs.points >= 2 && gs.points == gs.n) {
280c.type = Type::POINTS_COINCIDENT;
281c.ptA = gs.point[0];
282for(int k = 1; k < gs.points; k++) {
283c.ptB = gs.point[k];
284newcons.push_back(c);
285}
286} else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
287c.type = Type::PT_IN_PLANE;
288c.ptA = gs.point[0];
289c.entityA = gs.entity[0];
290newcons.push_back(c);
291} else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
292c.type = Type::PT_ON_LINE;
293c.ptA = gs.point[0];
294c.entityA = gs.entity[0];
295newcons.push_back(c);
296} else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
297c.type = Type::PT_ON_CIRCLE;
298c.ptA = gs.point[0];
299c.entityA = gs.entity[0];
300newcons.push_back(c);
301} else if(gs.points == 1 && gs.faces >= 1 && gs.n == gs.points+gs.faces) {
302c.type = Type::PT_ON_FACE;
303c.ptA = gs.point[0];
304for (int k=0; k<gs.faces; k++) {
305c.entityA = gs.face[k];
306newcons.push_back(c);
307}
308} else {
309Error(_("Bad selection for on point / curve / plane constraint. "
310"This constraint can apply to:\n\n"
311" * two or more points (points coincident)\n"
312" * a point and a workplane (point in plane)\n"
313" * a point and a line segment (point on line)\n"
314" * a point and a circle or arc (point on curve)\n"
315" * a point and one to three plane faces (point on face(s))\n"));
316return;
317}
318for (auto&& nc : newcons)
319AddConstraint(&nc);
320break;
321
322case Command::EQUAL:
323if(gs.lineSegments >= 2 && gs.lineSegments == gs.n) {
324c.type = Type::EQUAL_LENGTH_LINES;
325c.entityA = gs.entity[0];
326for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
327c.entityB = gs.entity[k];
328newcons.push_back(c);
329}
330} else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
331c.type = Type::EQ_PT_LN_DISTANCES;
332c.entityA = gs.entity[0];
333c.ptA = gs.point[0];
334c.entityB = gs.entity[1];
335c.ptB = gs.point[1];
336newcons.push_back(c);
337} else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
338// The same line segment for the distances, but different
339// points.
340c.type = Type::EQ_PT_LN_DISTANCES;
341c.entityA = gs.entity[0];
342c.ptA = gs.point[0];
343c.entityB = gs.entity[0];
344c.ptB = gs.point[1];
345newcons.push_back(c);
346} else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
347c.type = Type::EQ_LEN_PT_LINE_D;
348c.entityA = gs.entity[0];
349c.entityB = gs.entity[1];
350c.ptA = gs.point[0];
351newcons.push_back(c);
352} else if(gs.circlesOrArcs >= 2 && gs.circlesOrArcs == gs.n) {
353c.type = Type::EQUAL_RADIUS;
354c.entityA = gs.entity[0];
355for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
356c.entityB = gs.entity[k];
357newcons.push_back(c);
358}
359} else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
360c.type = Type::EQUAL_LINE_ARC_LEN;
361if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
362c.entityA = gs.entity[1];
363c.entityB = gs.entity[0];
364} else {
365c.entityA = gs.entity[0];
366c.entityB = gs.entity[1];
367}
368newcons.push_back(c);
369} else {
370Error(_("Bad selection for equal length / radius constraint. "
371"This constraint can apply to:\n\n"
372" * two or more line segments (equal length)\n"
373" * two line segments and two points "
374"(equal point-line distances)\n"
375" * a line segment and two points "
376"(equal point-line distances)\n"
377" * a line segment, and a point and line segment "
378"(point-line distance equals length)\n"
379" * two or more circles or arcs (equal radius)\n"
380" * a line segment and an arc "
381"(line segment length equals arc length)\n"));
382return;
383}
384SS.UndoRemember();
385for (auto&& nc : newcons){
386if(nc.type == Type::EQUAL_ANGLE) {
387// Infer the nearest supplementary angle from the sketch.
388Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
389b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
390a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
391b2 = SK.GetEntity(c.entityD)->VectorGetNum();
392double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
393
394if(d1*d2 < 0) {
395nc.other = true;
396}
397}
398AddConstraint(&nc, /*rememberForUndo=*/false);
399}
400break;
401
402case Command::RATIO:
403if(gs.lineSegments == 2 && gs.n == 2) {
404c.type = Type::LENGTH_RATIO;
405c.entityA = gs.entity[0];
406c.entityB = gs.entity[1];
407}
408else if(gs.arcs == 2 && gs.n == 2) {
409c.type = Type::ARC_ARC_LEN_RATIO;
410c.entityA = gs.entity[0];
411c.entityB = gs.entity[1];
412}
413else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
414c.type = Type::ARC_LINE_LEN_RATIO;
415if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
416c.entityA = gs.entity[1];
417c.entityB = gs.entity[0];
418} else {
419c.entityA = gs.entity[0];
420c.entityB = gs.entity[1];
421}
422} else {
423Error(_("Bad selection for length ratio constraint. This "
424"constraint can apply to:\n\n"
425" * two line segments\n"
426" * two arcs\n"
427" * one arc and one line segment\n"));
428return;
429}
430
431c.valA = 0;
432c.ModifyToSatisfy();
433AddConstraint(&c);
434newcons.push_back(c);
435break;
436
437case Command::DIFFERENCE:
438if(gs.lineSegments == 2 && gs.n == 2) {
439c.type = Type::LENGTH_DIFFERENCE;
440c.entityA = gs.entity[0];
441c.entityB = gs.entity[1];
442}
443else if(gs.arcs == 2 && gs.n == 2) {
444c.type = Type::ARC_ARC_DIFFERENCE;
445c.entityA = gs.entity[0];
446c.entityB = gs.entity[1];
447}
448else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
449c.type = Type::ARC_LINE_DIFFERENCE;
450if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
451c.entityA = gs.entity[1];
452c.entityB = gs.entity[0];
453} else {
454c.entityA = gs.entity[0];
455c.entityB = gs.entity[1];
456}
457} else {
458Error(_("Bad selection for length difference constraint. This "
459"constraint can apply to:\n\n"
460" * two line segments\n"
461" * two arcs\n"
462" * one arc and one line segment\n"));
463return;
464}
465
466c.valA = 0;
467c.ModifyToSatisfy();
468AddConstraint(&c);
469newcons.push_back(c);
470break;
471
472case Command::AT_MIDPOINT:
473if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
474c.type = Type::AT_MIDPOINT;
475c.entityA = gs.entity[0];
476c.ptA = gs.point[0];
477
478// If a point is at-midpoint, then no reason to also constrain
479// it on-line; so auto-remove that. Handle as one undo group.
480SS.UndoRemember();
481DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
482AddConstraint(&c, /*rememberForUndo=*/false);
483newcons.push_back(c);
484break;
485} else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
486c.type = Type::AT_MIDPOINT;
487int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
488c.entityA = gs.entity[i];
489c.entityB = gs.entity[1-i];
490AddConstraint(&c);
491newcons.push_back(c);
492} else {
493Error(_("Bad selection for at midpoint constraint. This "
494"constraint can apply to:\n\n"
495" * a line segment and a point "
496"(point at midpoint)\n"
497" * a line segment and a workplane "
498"(line's midpoint on plane)\n"));
499return;
500}
501
502break;
503
504case Command::SYMMETRIC:
505if(gs.points == 2 &&
506((gs.workplanes == 1 && gs.n == 3) ||
507(gs.n == 2)))
508{
509if(gs.entities > 0)
510c.entityA = gs.entity[0];
511c.ptA = gs.point[0];
512c.ptB = gs.point[1];
513c.type = Type::SYMMETRIC;
514} else if(gs.lineSegments == 1 &&
515((gs.workplanes == 1 && gs.n == 2) ||
516(gs.n == 1)))
517{
518Entity *line;
519if(SK.GetEntity(gs.entity[0])->IsWorkplane()) {
520line = SK.GetEntity(gs.entity[1]);
521c.entityA = gs.entity[0];
522} else {
523line = SK.GetEntity(gs.entity[0]);
524}
525c.ptA = line->point[0];
526c.ptB = line->point[1];
527c.type = Type::SYMMETRIC;
528} else if(SS.GW.LockedInWorkplane()
529&& gs.lineSegments == 2 && gs.n == 2)
530{
531Entity *l0 = SK.GetEntity(gs.entity[0]),
532*l1 = SK.GetEntity(gs.entity[1]);
533
534if((l1->group != SS.GW.activeGroup) ||
535(l1->construction && !(l0->construction)))
536{
537swap(l0, l1);
538}
539c.ptA = l1->point[0];
540c.ptB = l1->point[1];
541c.entityA = l0->h;
542c.type = Type::SYMMETRIC_LINE;
543} else if(SS.GW.LockedInWorkplane()
544&& gs.lineSegments == 1 && gs.points == 2 && gs.n == 3)
545{
546c.ptA = gs.point[0];
547c.ptB = gs.point[1];
548c.entityA = gs.entity[0];
549c.type = Type::SYMMETRIC_LINE;
550} else {
551Error(_("Bad selection for symmetric constraint. This constraint "
552"can apply to:\n\n"
553" * two points or a line segment "
554"(symmetric about workplane's coordinate axis)\n"
555" * line segment, and two points or a line segment "
556"(symmetric about line segment)\n"
557" * workplane, and two points or a line segment "
558"(symmetric about workplane)\n"));
559return;
560}
561// We may remove constraints so remember manually
562if(c.entityA == Entity::NO_ENTITY) {
563// Horizontal / vertical symmetry, implicit symmetry plane
564// normal to the workplane
565if(c.workplane == Entity::FREE_IN_3D) {
566Error(_("A workplane must be active when constraining "
567"symmetric without an explicit symmetry plane."));
568return;
569}
570Vector pa = SK.GetEntity(c.ptA)->PointGetNum();
571Vector pb = SK.GetEntity(c.ptB)->PointGetNum();
572Vector dp = pa.Minus(pb);
573EntityBase *norm = SK.GetEntity(c.workplane)->Normal();;
574Vector u = norm->NormalU(), v = norm->NormalV();
575if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) {
576c.type = Type::SYMMETRIC_HORIZ;
577} else {
578c.type = Type::SYMMETRIC_VERT;
579}
580if(gs.lineSegments == 1) {
581// If this line segment is already constrained horiz or
582// vert, then auto-remove that redundant constraint.
583// Handle as one undo group.
584SS.UndoRemember();
585DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]),
586Entity::NO_ENTITY);
587DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
588Entity::NO_ENTITY);
589AddConstraint(&c, /*rememberForUndo=*/false);
590newcons.push_back(c);
591break;
592}
593}
594AddConstraint(&c);
595newcons.push_back(c);
596break;
597
598case Command::VERTICAL:
599case Command::HORIZONTAL: {
600if(id == Command::HORIZONTAL) {
601c.type = Type::HORIZONTAL;
602} else {
603c.type = Type::VERTICAL;
604}
605if(c.workplane == Entity::FREE_IN_3D) {
606Error(_("Activate a workplane (with Sketch -> In Workplane) before "
607"applying a horizontal or vertical constraint."));
608return;
609}
610if(gs.lineSegments > 0 && gs.lineSegments == gs.n) {
611for (auto enti : gs.entity){
612c.entityA = enti;
613newcons.push_back(c);
614}
615} else if(gs.points >= 2 && gs.n == gs.points) {
616c.ptA = gs.point[0];
617for (int k = 1; k<gs.points; k++) {
618c.ptB = gs.point[k];
619newcons.push_back(c);
620}
621} else {
622Error(_("Bad selection for horizontal / vertical constraint. "
623"This constraint can apply to:\n\n"
624" * two or more points\n"
625" * one or more line segments\n"));
626return;
627}
628SS.UndoRemember();
629for (auto && nc: newcons)
630AddConstraint(&nc, /*rememberForUndo=*/false);
631break;
632}
633
634case Command::ORIENTED_SAME: {
635if(gs.anyNormals == 2 && gs.n == 2) {
636c.type = Type::SAME_ORIENTATION;
637c.entityA = gs.anyNormal[0];
638c.entityB = gs.anyNormal[1];
639} else {
640Error(_("Bad selection for same orientation constraint. This "
641"constraint can apply to:\n\n"
642" * two normals\n"));
643return;
644}
645SS.UndoRemember();
646
647Entity *nfree = SK.GetEntity(c.entityA);
648Entity *nref = SK.GetEntity(c.entityB);
649if(nref->group == SS.GW.activeGroup) {
650swap(nref, nfree);
651}
652if(nfree->group == SS.GW.activeGroup && nref->group != SS.GW.activeGroup) {
653// nfree is free, and nref is locked (since it came from a
654// previous group); so let's force nfree aligned to nref,
655// and make convergence easy
656Vector ru = nref ->NormalU(), rv = nref ->NormalV();
657Vector fu = nfree->NormalU(), fv = nfree->NormalV();
658
659if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) {
660// There might be an odd*90 degree rotation about the
661// normal vector; allow that, since the numerical
662// constraint does
663swap(ru, rv);
664}
665fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1);
666fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1);
667
668nfree->NormalForceTo(Quaternion::From(fu, fv));
669}
670AddConstraint(&c, /*rememberForUndo=*/false);
671newcons.push_back(c);
672break;
673}
674
675case Command::OTHER_ANGLE:
676if(gs.constraints == 1 && gs.n == 0) {
677Constraint *c = SK.GetConstraint(gs.constraint[0]);
678if(c->type == Type::ANGLE) {
679SS.UndoRemember();
680c->other = !(c->other);
681c->ModifyToSatisfy();
682break;
683}
684if(c->type == Type::EQUAL_ANGLE) {
685SS.UndoRemember();
686c->other = !(c->other);
687SS.MarkGroupDirty(c->group);
688break;
689}
690}
691Error(_("Must select an angle constraint."));
692return;
693
694case Command::REFERENCE:
695if(gs.constraints == 1 && gs.n == 0) {
696Constraint *c = SK.GetConstraint(gs.constraint[0]);
697if(c->HasLabel() && c->type != Type::COMMENT) {
698SS.UndoRemember();
699(c->reference) = !(c->reference);
700SS.MarkGroupDirty(c->group, /*onlyThis=*/true);
701break;
702}
703}
704Error(_("Must select a constraint with associated label."));
705return;
706
707case Command::ANGLE:
708case Command::REF_ANGLE: {
709if(gs.vectors == 3 && gs.n == 3) {
710c.type = Type::EQUAL_ANGLE;
711c.entityA = gs.vector[0];
712c.entityB = gs.vector[1];
713c.entityC = gs.vector[1];
714c.entityD = gs.vector[2];
715} else if(gs.vectors == 4 && gs.n == 4) {
716c.type = Type::EQUAL_ANGLE;
717c.entityA = gs.vector[0];
718c.entityB = gs.vector[1];
719c.entityC = gs.vector[2];
720c.entityD = gs.vector[3];
721} else if(gs.vectors == 2 && gs.n == 2) {
722c.type = Type::ANGLE;
723c.entityA = gs.vector[0];
724c.entityB = gs.vector[1];
725c.valA = 0;
726} else {
727Error(_("Bad selection for angle constraint. This constraint "
728"can apply to:\n\n"
729"Angle between:\n"
730" * two line segments\n"
731" * a line segment and a normal\n"
732" * two normals\n"
733"\nEqual angles:\n"
734" * four line segments or normals "
735"(equal angle between A,B and C,D)\n"
736" * three line segments or normals "
737"(equal angle between A,B and B,C)\n"));
738return;
739}
740
741Entity *ea = SK.GetEntity(c.entityA),
742*eb = SK.GetEntity(c.entityB);
743if(ea->type == Entity::Type::LINE_SEGMENT &&
744eb->type == Entity::Type::LINE_SEGMENT)
745{
746Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(),
747a1 = SK.GetEntity(ea->point[1])->PointGetNum(),
748b0 = SK.GetEntity(eb->point[0])->PointGetNum(),
749b1 = SK.GetEntity(eb->point[1])->PointGetNum();
750if(a0.Equals(b0) || a1.Equals(b1)) {
751// okay, vectors should be drawn in same sense
752} else if(a0.Equals(b1) || a1.Equals(b0)) {
753// vectors are in opposite sense
754c.other = true;
755} else {
756// no shared point; not clear which intersection to draw
757}
758}
759
760if(id == Command::REF_ANGLE) {
761c.reference = true;
762}
763
764c.ModifyToSatisfy();
765AddConstraint(&c);
766newcons.push_back(c);
767break;
768}
769
770case Command::PARALLEL:
771if(gs.faces == 2 && gs.n == 2) {
772c.type = Type::PARALLEL;
773c.entityA = gs.face[0];
774c.entityB = gs.face[1];
775newcons.push_back(c);
776} else if(gs.vectors > 1 && gs.vectors == gs.n) {
777c.type = Type::PARALLEL;
778c.entityA = gs.vector[0];
779for (std::vector<hEntity>::size_type k = 1; k < gs.vector.size();++k ){
780c.entityB = gs.vector[k];
781newcons.push_back(c);
782}
783} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
784Entity *line = SK.GetEntity(gs.entity[0]),
785*arc = SK.GetEntity(gs.entity[1]);
786if(line->type == Entity::Type::ARC_OF_CIRCLE) {
787swap(line, arc);
788}
789if(!ConstrainArcLineTangent(&c, line, arc)) {
790return;
791}
792c.type = Type::ARC_LINE_TANGENT;
793c.entityA = arc->h;
794c.entityB = line->h;
795newcons.push_back(c);
796} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
797Entity *line = SK.GetEntity(gs.entity[0]),
798*cubic = SK.GetEntity(gs.entity[1]);
799if(line->type == Entity::Type::CUBIC) {
800swap(line, cubic);
801}
802if(!ConstrainCubicLineTangent(&c, line, cubic)) {
803return;
804}
805c.type = Type::CUBIC_LINE_TANGENT;
806c.entityA = cubic->h;
807c.entityB = line->h;
808newcons.push_back(c);
809} else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
810if(!SS.GW.LockedInWorkplane()) {
811Error(_("Curve-curve tangency must apply in workplane."));
812return;
813}
814Entity *eA = SK.GetEntity(gs.entity[0]),
815*eB = SK.GetEntity(gs.entity[1]);
816if(!ConstrainCurveCurveTangent(&c, eA, eB)) {
817return;
818}
819c.type = Type::CURVE_CURVE_TANGENT;
820c.entityA = eA->h;
821c.entityB = eB->h;
822newcons.push_back(c);
823} else {
824Error(_("Bad selection for parallel / tangent constraint. This "
825"constraint can apply to:\n\n"
826" * two faces\n"
827" * two or more line segments (parallel)\n"
828" * one or more line segments and one or more normals (parallel)\n"
829" * two or more normals (parallel)\n"
830" * two line segments, arcs, or beziers, that share "
831"an endpoint (tangent)\n"));
832return;
833}
834SS.UndoRemember();
835for (auto&& nc:newcons)
836AddConstraint(&nc, /*rememberForUndo=*/false);
837break;
838
839case Command::PERPENDICULAR:
840if(gs.faces == 2 && gs.n == 2) {
841c.type = Type::PERPENDICULAR;
842c.entityA = gs.face[0];
843c.entityB = gs.face[1];
844} else if(gs.vectors == 2 && gs.n == 2) {
845c.type = Type::PERPENDICULAR;
846c.entityA = gs.vector[0];
847c.entityB = gs.vector[1];
848} else {
849Error(_("Bad selection for perpendicular constraint. This "
850"constraint can apply to:\n\n"
851" * two faces\n"
852" * two line segments\n"
853" * a line segment and a normal\n"
854" * two normals\n"));
855return;
856}
857AddConstraint(&c);
858newcons.push_back(c);
859break;
860
861case Command::WHERE_DRAGGED:
862if(gs.points == 1 && gs.n == 1) {
863c.type = Type::WHERE_DRAGGED;
864c.ptA = gs.point[0];
865} else {
866Error(_("Bad selection for lock point where dragged constraint. "
867"This constraint can apply to:\n\n"
868" * a point\n"));
869return;
870}
871AddConstraint(&c);
872newcons.push_back(c);
873break;
874
875case Command::COMMENT:
876if(gs.points == 1 && gs.n == 1) {
877c.type = Type::COMMENT;
878c.ptA = gs.point[0];
879c.group = SS.GW.activeGroup;
880c.workplane = SS.GW.ActiveWorkplane();
881c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
882AddConstraint(&c);
883newcons.push_back(c);
884} else {
885SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
886SS.GW.pending.command = Command::COMMENT;
887SS.GW.pending.description = _("click center of comment text");
888SS.ScheduleShowTW();
889}
890break;
891
892default: ssassert(false, "Unexpected menu ID");
893}
894for (auto nc:newcons){
895for(const Constraint &cc : SK.constraint) {
896if(nc.h != cc.h && nc.Equals(cc)) {
897// Oops, we already have this exact constraint. Remove the one we just added.
898SK.constraint.RemoveById(nc.h);
899SS.GW.ClearSelection();
900// And now select the old one, to give feedback.
901SS.GW.MakeSelected(cc.h);
902return;
903}
904}
905
906if(SK.constraint.FindByIdNoOops(nc.h)) {
907Constraint *constraint = SK.GetConstraint(nc.h);
908if(SS.TestRankForGroup(nc.group) == SolveResult::REDUNDANT_OKAY &&
909!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
910constraint->HasLabel()) {
911constraint->reference = true;
912}
913}
914
915if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
916id == Command::RATIO || id == Command::DIFFERENCE) &&
917SS.immediatelyEditDimension) {
918SS.GW.EditConstraint(nc.h);
919}
920}
921
922SS.GW.ClearSelection();
923}
924
925#endif /* ! LIBRARY */
926