1
//-----------------------------------------------------------------------------
2
// Copyright 2008 Jonathan Westhues
4
// This file is part of SketchFlat.
6
// SketchFlat is free software: you can redistribute it and/or modify
7
// it under the terms of the GNU General Public License as published by
8
// the Free Software Foundation, either version 3 of the License, or
9
// (at your option) any later version.
11
// SketchFlat is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
// GNU General Public License for more details.
16
// You should have received a copy of the GNU General Public License
17
// along with SketchFlat. If not, see <http://www.gnu.org/licenses/>.
20
// Math corresponding to each constraint: given each constraint, return one
21
// or more metrics that determine how far we are from satisfying that
22
// constraint. The solver calls these repeatedly, in an effort to drive them
24
// Jonathan Westhues, April 2007
25
//-----------------------------------------------------------------------------
26
#include "PreCompiled.h"
30
#include "sketchflat.h"
32
static void ModifyConstraintToReflectSketch(SketchConstraint *c);
35
Equations *EQ = (Equations *)&EQalloc;
37
static void AddConstraint(SketchConstraint *c)
42
if(SK->constraints >= MAX_CONSTRAINTS_IN_SKETCH) {
43
uiError("Too many constraints!");
49
for(i = 0; i < SK->constraints; i++) {
50
if(SK->constraint[i].id > max) {
51
max = SK->constraint[i].id;
55
memcpy(&(SK->constraint[SK->constraints]), c, sizeof(*c));
56
SK->constraint[SK->constraints].id = (max + 1);
57
SK->constraint[SK->constraints].layer = GetCurrentLayer();
64
void DeleteConstraint(hConstraint hc)
70
// So that we can't accidentally get deleted later, remove ourselves
71
// from the selection. This would otherwise cause a problem if our
72
// selection included an entity, and a constraint on that entity, in
73
// that order; we'd delete the entity, and then delete the constraint
74
// because it is on the entity, and then try to delete the constraint
76
for(i = 0; i < MAX_SELECTED_ITEMS; i++) {
77
if(Selected[i].which == SEL_CONSTRAINT && Selected[i].constraint == hc)
79
Selected[i].which = SEL_NONE;
80
Selected[i].constraint = 0;
84
for(i = 0; i < SK->constraints; i++) {
85
if(SK->constraint[i].id == hc) {
87
memmove(&(SK->constraint[i]), &(SK->constraint[i+1]),
88
(SK->constraints - i)*sizeof(SK->constraint[0]));
97
SketchConstraint *ConstraintById(hConstraint hc)
100
for(i = 0; i < SK->constraints; i++) {
101
if(SK->constraint[i].id == hc) {
102
return &(SK->constraint[i]);
108
//-----------------------------------------------------------------------------
109
// Change the numerical value associated with certain types of constraints
110
// (e.g. distances and lengths). If we're solving, then we will do this in
111
// multiple steps, to decrease the odds of numerical disaster.
112
//-----------------------------------------------------------------------------
113
void ChangeConstraintValue(SketchConstraint *c, char *str)
117
if(c->type == CONSTRAINT_PT_LINE_DISTANCE ||
118
c->type == CONSTRAINT_LINE_LINE_DISTANCE)
120
// These distances are signed (correspond to above or below a
121
// horizontal line). The sign is never displayed to the user, since
122
// that's confusing, but they can flip the sign by entering a
124
BOOL neg = (c->v < 0);
126
nv = -FromDisplay(str);
128
nv = FromDisplay(str);
130
} else if(c->type == CONSTRAINT_PT_PT_DISTANCE ||
131
c->type == CONSTRAINT_RADIUS)
133
nv = FromDisplay(str);
134
} else if(c->type == CONSTRAINT_LINE_LINE_ANGLE) {
135
// As for point-line distances: the sign is not displayed to the
136
// user, but may be flipped by entering a negative number.
137
BOOL neg = (c->v < 0);
143
// And let's force that to lie between -180 and 180.
144
while(nv > 180) nv -= 360;
145
while(nv < -180) nv += 360;
146
} else if(c->type == CONSTRAINT_SCALE_MM ||
147
c->type == CONSTRAINT_SCALE_INCH)
149
nv = fabs(atof(str));
158
int n = toint(fabs((nv - cv0)) / 5000);
162
// Step the dimension to the requested value.
163
for(i = n; i >= 0; i--) {
164
c->v = nv + (i*(cv0 - nv))/n;
169
static void InitConstraint(SketchConstraint *c)
180
c->offset.x = toMicronsNotAffine(50);
184
static void HandleMenuSelection(int id)
186
// As a convenience, let's count how many of each are selected, because
187
// that determines which (if any) constraint a given menu item will
192
// Let's make sure that the constraint will be applied to at least
193
// one item on the currently selected layer. If not, then it is surely
194
// inconsistent, since the sketches are solved one layer at a time.
195
hLayer cl = GetCurrentLayer();
198
for(i = 0; i < gs.points; i++) {
199
if(LayerForPoint(gs.point[i]) == cl) {
200
goto items_on_current_layer_selected;
203
for(i = 0; i < gs.entities; i++) {
204
if(gs.entity[i] == REFERENCE_ENTITY) continue;
205
SketchEntity *e = EntityById(gs.entity[i]);
207
goto items_on_current_layer_selected;
210
for(i = 0; i < gs.lines; i++) {
211
if(LayerForLine(gs.line[i]) == cl) {
212
goto items_on_current_layer_selected;
215
uiError("Selection for constraint must contain at least one item on "
219
items_on_current_layer_selected:
221
// Set up a blank constraint, to possibly fill in later and add.
225
// And now create the appropriate constraint.
227
case MNU_CONSTR_DISTANCE:
228
if(gs.points == 2 && gs.lines == 0 && gs.entities == 0) {
229
// Plain distance, from point to point (2 points)
230
c.type = CONSTRAINT_PT_PT_DISTANCE;
234
ModifyConstraintToReflectSketch(&c);
238
if(gs.entities == 1 && gs.anyLines == 1 && gs.points == 0 &&
241
// Plain distance, from point to point. The two endpoints of
242
// a line segment, in this case.
243
c.type = CONSTRAINT_PT_PT_DISTANCE;
244
c.ptA = POINT_FOR_ENTITY(gs.entity[0], 0);
245
c.ptB = POINT_FOR_ENTITY(gs.entity[0], 1);
247
ModifyConstraintToReflectSketch(&c);
251
if(gs.points == 1 && gs.anyLines == 1 && gs.nonLineEntities == 0) {
252
// Distance from a point to a line; line can be either an
253
// infinite datum line, or the infinite extension of a
255
c.type = CONSTRAINT_PT_LINE_DISTANCE;
258
c.lineB = gs.line[0];
260
c.entityB = gs.entity[0];
263
ModifyConstraintToReflectSketch(&c);
267
if(gs.points == 0 && gs.anyLines == 2 && gs.nonLineEntities == 0) {
268
// Minimum distance from a line to a line (2 lines, 2 line
269
// segments, or a line and a line segment, and they have
270
// to be parallel or it doesn't work)
271
c.type = CONSTRAINT_LINE_LINE_DISTANCE;
273
c.lineA = gs.line[0];
274
c.lineB = gs.line[1];
275
} else if(gs.lines == 1) {
276
c.lineA = gs.line[0];
277
c.entityB = gs.entity[0];
278
} else if(gs.entities == 2) {
279
c.entityA = gs.entity[0];
280
c.entityB = gs.entity[1];
283
ModifyConstraintToReflectSketch(&c);
287
if(gs.points == 0 && gs.entities == 1 && gs.circlesOrArcs == 1
290
c.type = CONSTRAINT_RADIUS;
291
c.entityA = gs.entity[0];
293
ModifyConstraintToReflectSketch(&c);
298
uiError("Invalid selection for distance / diameter constraint. A "
299
"distance / diameter constraint may be applied to:\r\n\r\n"
300
" - two line segments/datum lines (perpendicular distance)\r\n"
301
" - a point and a line segment/datum line (perpendicular distance)\r\n"
303
" - a circle or an arc\r\n");
306
case MNU_CONSTR_ANGLE:
307
// Line to line (2 lines, 2 line segments, or a line and a line
309
if(gs.anyLines == 2 && gs.n == 2) {
310
c.type = CONSTRAINT_LINE_LINE_ANGLE;
312
c.lineA = gs.line[0];
313
c.lineB = gs.line[1];
314
} else if(gs.entities == 2) {
315
c.entityA = gs.entity[0];
316
c.entityB = gs.entity[1];
318
c.entityA = gs.entity[0];
319
c.lineB = gs.line[0];
322
ModifyConstraintToReflectSketch(&c);
327
uiError("Invalid selection for angle constraint. An angle "
328
"constraint may be applied to two items "
329
"from the set of:\r\n\r\n"
331
" - line segments\r\n");
334
case MNU_CONSTR_COINCIDENT:
335
if(gs.points == 2 && gs.lines == 0 && gs.entities == 0) {
337
c.type = CONSTRAINT_POINTS_COINCIDENT;
343
if(gs.points == 1 && gs.anyLines == 1 && gs.nonLineEntities == 0) {
344
// Point on line or line segment; this corresponds to a
345
// point-to-line distance constraint, with a distance
347
c.type = CONSTRAINT_POINT_ON_LINE;
351
c.lineB = gs.line[0];
353
c.entityB = gs.entity[0];
361
if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.entities == 1
364
// Point on circle. This is actually just a point-to-point
365
// distance constraint, from the center of the circle.
366
c.type = CONSTRAINT_ON_CIRCLE;
369
c.entityA = gs.entity[0];
376
"Invalid selection for coincidence constraint. A coincidence "
377
"constraint may be applied to:\r\n\r\n"
378
" - two points (points become coincident)\r\n"
379
" - a point and a curve (point lies on curve)\r\n"
380
" - a point and a datum line (point lies on line)\r\n");
383
case MNU_CONSTR_PERPENDICULAR:
384
case MNU_CONSTR_PARALLEL:
385
if((((id == MNU_CONSTR_PARALLEL) && gs.anyDirections == 2) ||
386
gs.anyLines == 2) && gs.n == 2)
390
// Parallelism can be defined between line segments, lines,
391
// circular arc tangents, and cubic spline tangents.
392
while(gs.lines > 0) {
394
c.lineA = gs.line[gs.lines-1];
396
c.lineB = gs.line[gs.lines-1];
401
while(gs.points > 0) {
403
c.ptA = gs.point[gs.points-1];
405
c.ptB = gs.point[gs.points-1];
410
while(gs.entities > 0) {
412
c.entityA = gs.entity[gs.entities-1];
414
c.entityB = gs.entity[gs.entities-1];
424
if(id == MNU_CONSTR_PARALLEL) {
425
// They make a zero degree angle with each other.
426
c.type = CONSTRAINT_PARALLEL;
429
// They make a ninety degree angle with each other.
430
c.type = CONSTRAINT_PERPENDICULAR;
436
if(id == MNU_CONSTR_PARALLEL) {
437
uiError("Invalid selection for parallel constraint. A parallel "
438
"constraint may be applied to two items from the set "
441
" - line segments\r\n"
442
" - endpoints of circular arcs (for tangency)\r\n"
443
" - endpoints of cubic splines (for tangency)\r\n");
445
uiError("Invalid selection for perpendicular constraint. A "
446
"perpendicular constraint may be applied to two items "
447
"from the set of:\r\n\r\n"
449
" - line segments\r\n");
453
case MNU_CONSTR_EQUAL:
454
// Equal length (2 line segments)
455
if(gs.points == 0 && gs.lines == 0 && gs.anyLines == 2 &&
458
c.type = CONSTRAINT_EQUAL_LENGTH;
459
c.entityA = gs.entity[0];
460
c.entityB = gs.entity[1];
465
// Equal radius (1 each circle or arc)
466
if(gs.circlesOrArcs == 2 && gs.n == 2) {
467
c.type = CONSTRAINT_EQUAL_RADIUS;
468
c.entityA = gs.entity[0];
469
c.entityB = gs.entity[1];
475
uiError("Invalid selection for equal constraint. An equal "
476
"constraint may be applied to:\r\n\r\n"
477
" - two line segments\r\n"
478
" - two circles, or arcs of circles\r\n");
481
case MNU_CONSTR_MIDPOINT:
482
// Point on line segment (point and line segment)
483
if(gs.points == 1 && gs.lines == 0 && gs.anyLines == 1 &&
486
c.type = CONSTRAINT_AT_MIDPOINT;
487
c.entityA = gs.entity[0];
494
uiError("Invalid selection for midpoint constraint. An midpoint "
495
"constraint may be applied to:\r\n\r\n"
496
" - a point and a line segment\r\n");
499
case MNU_CONSTR_SYMMETRIC:
500
if(gs.points == 2 && gs.lines == 1 && gs.entities == 0) {
501
c.type = CONSTRAINT_SYMMETRIC;
502
c.lineA = gs.line[0];
508
} else if(gs.anyLines == 2 && gs.entities == 1 && gs.points == 0 &&
509
gs.lines == 1 && gs.n == 2)
511
c.type = CONSTRAINT_SYMMETRIC;
512
c.lineA = gs.line[0];
513
c.ptA = POINT_FOR_ENTITY(gs.entity[0], 0);
514
c.ptB = POINT_FOR_ENTITY(gs.entity[0], 1);
520
uiError("Invalid selection for symmetry constraint. A symmetry "
521
"constraint may be applied to:\r\n\r\n"
522
" - two points and a datum line\r\n"
523
" - a line segment and a datum line\r\n");
526
case MNU_CONSTR_HORIZONTAL:
527
case MNU_CONSTR_VERTICAL:
528
if((gs.points == 2 && gs.n == 2) ||
529
(gs.entities == 1 && gs.anyLines == 1 && gs.n == 1))
531
c.type = (id == MNU_CONSTR_HORIZONTAL) ?
532
CONSTRAINT_HORIZONTAL : CONSTRAINT_VERTICAL;
535
// Two points that lie on a whicheveral line.
539
// A line segment whose endpoints lie on a whicheveral
540
// line (i.e., that whicheveral line segment).
541
c.ptA = POINT_FOR_ENTITY(gs.entity[0], 0);
542
c.ptB = POINT_FOR_ENTITY(gs.entity[0], 1);
549
uiError("Invalid selection for horizontal / vertical constraint. "
550
"A horizontal / vertical constraint may be applied to:"
553
" - a line segment\r\n");
556
case MNU_CONSTR_DRAG_HORIZ:
557
case MNU_CONSTR_DRAG_VERT:
558
if(gs.n == 1 && gs.points == 1) {
559
c.type = CONSTRAINT_FORCE_PARAM;
561
if(id == MNU_CONSTR_DRAG_HORIZ) {
562
c.paramA = X_COORD_FOR_PT(gs.point[0]);
564
c.paramA = Y_COORD_FOR_PT(gs.point[0]);
571
uiError("Invalid selection for draggable constraint. A draggable "
572
"constraint may be applied to:"
577
case MNU_CONSTR_DRAG_ANGLE:
578
if(gs.n == 2 && gs.points == 2) {
579
c.type = CONSTRAINT_FORCE_ANGLE;
587
uiError("Invalid selection for draggable about point constraint. A "
588
"draggable about point constraint may be applied to:"
590
" - two points (the center of rotation, and the "
591
"point to constrain)\r\n");
594
case MNU_CONSTR_SCALE_MM:
595
case MNU_CONSTR_SCALE_INCH:
596
if(gs.n == 1 && gs.entities == 1) {
597
SketchEntity *e = EntityById(gs.entity[0]);
598
if(e && e->type == ENTITY_IMPORTED) {
599
c.type = (id == MNU_CONSTR_SCALE_MM) ?
600
CONSTRAINT_SCALE_MM : CONSTRAINT_SCALE_INCH;
601
c.entityA = gs.entity[0];
604
ModifyConstraintToReflectSketch(&c);
610
uiError("Invalid selection for scale (in inches or mm) constraint. "
611
"A scale constraint may be applied to:"
613
" - an imported file\r\n");
618
void ConstrainCoincident(hPoint a, hPoint b)
629
c.type = CONSTRAINT_POINTS_COINCIDENT;
635
void MenuConstrain(int id)
637
if(id == MNU_CONSTR_SUPPLEMENTARY) {
640
// Selection should include a single item, an angle constraint
641
if(Selected[0].which != SEL_CONSTRAINT) goto badsel;
643
for(i = 1; i < MAX_SELECTED_ITEMS; i++) {
644
if(Selected[i].which != SEL_NONE) goto badsel;
646
c = ConstraintById(Selected[0].constraint);
647
if(c->type != CONSTRAINT_LINE_LINE_ANGLE) goto badsel;
649
// An angle that is equal modulo 180 degrees will generate the
650
// equivalent constraint, but display differently on-screen.
652
while(c->v > 180) c->v -= 360;
654
// And redraw, and resolve, even though resolving should do nothing.
656
ClearHoverAndSelected();
661
uiError("Must select an angle constraint.");
665
HandleMenuSelection(id);
667
ClearHoverAndSelected();
671
static Expr *EDistance(hPoint ptA, hPoint ptB)
673
hParam xA = X_COORD_FOR_PT(ptA);
674
hParam yA = Y_COORD_FOR_PT(ptA);
676
hParam xB = X_COORD_FOR_PT(ptB);
677
hParam yB = Y_COORD_FOR_PT(ptB);
680
ESquare(EMinus(EParam(xA), EParam(xB))),
681
ESquare(EMinus(EParam(yA), EParam(yB)))));
684
static void EGetPointAndDirectionForLine(hLine ln, hEntity e,
685
Expr **x0, Expr **y0, Expr **dx, Expr **dy)
690
hPoint ptA = POINT_FOR_ENTITY(e, 0);
691
hPoint ptB = POINT_FOR_ENTITY(e, 1);
693
hParam xA = X_COORD_FOR_PT(ptA);
694
hParam yA = Y_COORD_FOR_PT(ptA);
696
hParam xB = X_COORD_FOR_PT(ptB);
697
hParam yB = Y_COORD_FOR_PT(ptB);
699
if(dx) *dx = EMinus(EParam(xA), EParam(xB));
700
if(dy) *dy = EMinus(EParam(yA), EParam(yB));
702
if(x0) *x0 = EParam(xA);
703
if(y0) *y0 = EParam(yA);
705
hParam theta = THETA_FOR_LINE(ln);
706
hParam a = A_FOR_LINE(ln);
708
if(dx) *dx = ECos(EParam(theta));
709
if(dy) *dy = ESin(EParam(theta));
711
if(x0) *x0 = ENegate(ETimes(EParam(a), ESin(EParam(theta))));
712
if(y0) *y0 = ETimes(EParam(a), ECos(EParam(theta)));
716
static void EGetDirectionOrTangent(hLine ln, hEntity he, hPoint p,
717
Expr **dx, Expr **dy)
721
EGetPointAndDirectionForLine(ln, he, NULL, NULL, dx, dy);
726
hParam px = X_COORD_FOR_PT(p);
727
hParam py = Y_COORD_FOR_PT(p);
729
hEntity hep = ENTITY_FROM_POINT(p);
730
SketchEntity *e = EntityById(hep);
731
if(e->type == ENTITY_CIRCULAR_ARC) {
732
hPoint c = POINT_FOR_ENTITY(e->id, 2);
733
// This is the center of the circle that defines the arc.
734
hParam cx = X_COORD_FOR_PT(c);
735
hParam cy = Y_COORD_FOR_PT(c);
736
// The tangent is perpendicular to the radius.
737
*dx = EMinus(EParam(cy), EParam(py));
738
*dy = ENegate(EMinus(EParam(cx), EParam(px)));
739
} else if(e->type == ENTITY_CUBIC_SPLINE) {
740
int k = K_FROM_POINT(p);
741
// This is the nearest Bezier control point.
744
bx = X_COORD_FOR_PT(POINT_FOR_ENTITY(e->id, k+1));
745
by = Y_COORD_FOR_PT(POINT_FOR_ENTITY(e->id, k+1));
746
} else if(k == (e->points -1)) {
747
bx = X_COORD_FOR_PT(POINT_FOR_ENTITY(e->id, k-1));
748
by = Y_COORD_FOR_PT(POINT_FOR_ENTITY(e->id, k-1));
753
*dx = EMinus(EParam(bx), EParam(px));
754
*dy = EMinus(EParam(by), EParam(py));
760
static Expr *EDistanceFromExprPointToLine(Expr *xp, Expr *yp,
768
EGetPointAndDirectionForLine(ln, e, &x0, &y0, &dx, &dy);
770
Expr *d = EDiv( EMinus( ETimes(dx, EMinus(y0, yp)),
771
ETimes(dy, EMinus(x0, xp))),
772
ESqrt(EPlus(ESquare(dx), ESquare(dy))));
775
static Expr *EDistanceFromPointToLine(hPoint pt, hLine ln, hEntity e)
777
Expr *xp = EParam(X_COORD_FOR_PT(pt));
778
Expr *yp = EParam(Y_COORD_FOR_PT(pt));
780
return EDistanceFromExprPointToLine(xp, yp, ln, e);
783
static void AddEquation(hConstraint hc, int k, Expr *e)
785
if(EQ->eqns >= arraylen(EQ->eqn)) oops();
787
EQ->eqn[EQ->eqns].e = e;
788
EQ->eqn[EQ->eqns].he = EQUATION_FOR_CONSTRAINT(hc, k);
793
static void Make_PtPtDistance(SketchConstraint *c)
795
if(told(c->v, 0) || c->type == CONSTRAINT_POINTS_COINCIDENT) {
796
// Two points, zero distance apart; so we are trying to contrain
797
// the points coincident, for which we should write two equations,
798
// since that restricts two degrees of freedom.
802
// First equation: xA - xB = 0
803
pa = X_COORD_FOR_PT(c->ptA);
804
pb = X_COORD_FOR_PT(c->ptB);
806
AddEquation(c->id, 0, EMinus(EParam(pa), EParam(pb)));
808
// Second equation: yA - yB = 0
809
pa = Y_COORD_FOR_PT(c->ptA);
810
pb = Y_COORD_FOR_PT(c->ptB);
812
AddEquation(c->id, 1, EMinus(EParam(pa), EParam(pb)));
814
Expr *d = EDistance(c->ptA, c->ptB);
816
AddEquation(c->id, 0, EMinus(d, EConstant(c->v)));
820
static void Make_PtLineDistance(SketchConstraint *c)
822
Expr *d = EDistanceFromPointToLine(c->ptA, c->lineB, c->entityB);
824
AddEquation(c->id, 0, EMinus(d, EConstant(c->v)));
827
static void Make_LineLineDistance(SketchConstraint *c)
829
// We will choose an arbitrary point on one line, and constrain the
830
// distance to the other.
833
// A is a line segment, so use one of its endpoints.
834
d = EDistanceFromPointToLine(POINT_FOR_ENTITY(c->entityA, 0),
835
c->lineB, c->entityB);
836
} else if(c->entityB) {
837
// B is a line segment, so use one of its endpoints.
838
d = EDistanceFromPointToLine(POINT_FOR_ENTITY(c->entityB, 0),
839
c->lineA, c->entityA);
841
// Two datum lines. Choose an arbtirary point on lineA, and measure
842
// to the other line.
844
EGetPointAndDirectionForLine(c->lineA, 0, &x0, &y0, NULL, NULL);
845
d = EDistanceFromExprPointToLine(x0, y0, c->lineB, 0);
848
AddEquation(c->id, 0, EMinus(d, EConstant(c->v)));
851
static void Make_Angle(SketchConstraint *c)
856
EGetDirectionOrTangent(c->lineA, c->entityA, c->ptA, &dxA, &dyA);
857
EGetDirectionOrTangent(c->lineB, c->entityB, c->ptB, &dxB, &dyB);
860
// Rotate one of the vectors by the desired angle
861
double theta = (c->v)*PI/180;
863
dxr = EPlus(ETimes(EConstant( cos(theta)), dxA),
864
ETimes(EConstant( sin(theta)), dyA));
865
dyr = EPlus(ETimes(EConstant(-sin(theta)), dxA),
866
ETimes(EConstant( cos(theta)), dyA));
868
// And we are trying to make them parallel, i.e. to cause
870
// det [ dxB dyB ] = 0
872
// It's important to normalize by the lengths of our direction vectors.
873
// If we don't, then the solver can satisfy our constraint by driving
874
// the length of the line to zero, at which point it is parallel to
877
// The constant is to get it on to the right order, so that the tolerances
878
// for singularity and stuff later are reasonable.
879
AddEquation(c->id, 0,
880
EDiv(EMinus(ETimes(dxr, dyB), ETimes(dyr, dxB)),
881
ETimes(ESqrt(EPlus(ESquare(dxB), ESquare(dyB))),
882
ESqrt(EPlus(ESquare(dxA), ESquare(dyA))))));
885
static void Make_EqualLength(SketchConstraint *c)
887
hPoint ptA0 = POINT_FOR_ENTITY(c->entityA, 0);
888
hPoint ptA1 = POINT_FOR_ENTITY(c->entityA, 1);
890
hPoint ptB0 = POINT_FOR_ENTITY(c->entityB, 0);
891
hPoint ptB1 = POINT_FOR_ENTITY(c->entityB, 1);
893
AddEquation(c->id, 0, EMinus(
894
EDistance(ptA0, ptA1),
895
EDistance(ptB0, ptB1)));
898
static void Make_AtMidpoint(SketchConstraint *c)
900
hPoint ptA = POINT_FOR_ENTITY(c->entityA, 0);
901
hPoint ptB = POINT_FOR_ENTITY(c->entityA, 1);
903
hParam xA = X_COORD_FOR_PT(ptA);
904
hParam yA = Y_COORD_FOR_PT(ptA);
905
hParam xB = X_COORD_FOR_PT(ptB);
906
hParam yB = Y_COORD_FOR_PT(ptB);
908
hParam xp = X_COORD_FOR_PT(c->ptA);
909
hParam yp = Y_COORD_FOR_PT(c->ptA);
911
AddEquation(c->id, 0, EMinus(
912
EDiv(EPlus(EParam(xA), EParam(xB)), EConstant(2)),
915
AddEquation(c->id, 1, EMinus(
916
EDiv(EPlus(EParam(yA), EParam(yB)), EConstant(2)),
920
static Expr *RadiusForEntity(hEntity he)
922
int type = EntityById(he)->type;
924
if(type == ENTITY_CIRCLE) {
925
// Straightforward, radius is the parameter of the circle.
926
hParam r = PARAM_FOR_ENTITY(he, 0);
928
} else if(type == ENTITY_CIRCULAR_ARC) {
929
// Radius is the distance from either on-curve point to the center
931
return EDistance(POINT_FOR_ENTITY(he, 0), POINT_FOR_ENTITY(he, 2));
937
static void Make_Radius(SketchConstraint *c)
939
hParam he = c->entityA;
941
Expr *r = RadiusForEntity(he);
943
// We work with the radius internally, but the user enters the diameter;
944
// so the radii that we get from the sketch get multiplied by two before
946
AddEquation(c->id, 0, EMinus(ETimes(EConstant(2), r), EConstant(c->v)));
949
static void Make_EqualRadius(SketchConstraint *c)
951
Expr *rA = RadiusForEntity(c->entityA);
952
Expr *rB = RadiusForEntity(c->entityB);
954
// This should be written simply, so that equal radius for circles will
955
// get solved by forward-substitution.
956
AddEquation(c->id, 0, EMinus(rA, rB));
959
static void Make_OnCircle(SketchConstraint *c)
961
SketchEntity *e = EntityById(c->entityA);
963
if(e->type == ENTITY_CIRCLE) {
964
hParam r = PARAM_FOR_ENTITY(c->entityA, 0);
965
hPoint cntr = POINT_FOR_ENTITY(c->entityA, 0);
967
Expr *d = EDistance(cntr, c->ptA);
969
AddEquation(c->id, 0, EMinus(d, EParam(r)));
970
} else if(e->type == ENTITY_CIRCULAR_ARC) {
971
hPoint cntr = POINT_FOR_ENTITY(c->entityA, 2);
972
hPoint onArc = POINT_FOR_ENTITY(c->entityA, 0); // or 1, either way
974
Expr *r = EDistance(cntr, onArc);
975
Expr *d = EDistance(cntr, c->ptA);
977
AddEquation(c->id, 0, EMinus(d, r));
983
static void Make_Symmetric(SketchConstraint *c)
985
Expr *dA = EDistanceFromPointToLine(c->ptA, c->lineA, 0);
986
Expr *dB = EDistanceFromPointToLine(c->ptB, c->lineA, 0);
988
// These are signed distances, and the symmetric points should be on
989
// opposite sides of the line, so they should be equal and magnitude,
990
// and opposite in sign.
991
AddEquation(c->id, 0, EPlus(dA, dB));
993
Expr *xA = EParam(X_COORD_FOR_PT(c->ptA));
994
Expr *yA = EParam(Y_COORD_FOR_PT(c->ptA));
995
Expr *xB = EParam(X_COORD_FOR_PT(c->ptB));
996
Expr *yB = EParam(Y_COORD_FOR_PT(c->ptB));
998
hParam theta = THETA_FOR_LINE(c->lineA);
999
Expr *dx = ECos(EParam(theta));
1000
Expr *dy = ESin(EParam(theta));
1002
// We want the line between the two points to be perpendicular to the
1003
// datum, so (xA - xB, yA - yB) dot (cos theta, sin theta) = 0.
1004
AddEquation(c->id, 1, EPlus(ETimes(EMinus(xA, xB), dx),
1005
ETimes(EMinus(yA, yB), dy)));
1008
static void Make_HorizontalVertical(SketchConstraint *c)
1012
if(c->type == CONSTRAINT_HORIZONTAL) {
1013
// Horizontal lines have equal y coordinates for their endpoints.
1014
pA = Y_COORD_FOR_PT(c->ptA);
1015
pB = Y_COORD_FOR_PT(c->ptB);
1017
pA = X_COORD_FOR_PT(c->ptA);
1018
pB = X_COORD_FOR_PT(c->ptB);
1021
// A simple equation of this form will be solved by substitution, and
1022
// will therefore solve very quickly.
1023
AddEquation(c->id, 0, EMinus(EParam(pA), EParam(pB)));
1026
static void Make_ForceParam(SketchConstraint *c)
1028
hParam hp = c->paramA;
1030
// This is a weird one. It's basically doing the same job as the
1031
// solver does when it makes assumptions, fixing an unknown wherever
1032
// it has been dragged. A bit silly to write an equation, perhaps,
1033
// but this will solve very easily.
1034
AddEquation(c->id, 0,
1035
EMinus(EParam(hp), EConstant(EvalParam(hp))));
1038
static void Make_ForceAngle(SketchConstraint *c)
1040
hParam xA = X_COORD_FOR_PT(c->ptA);
1041
hParam yA = Y_COORD_FOR_PT(c->ptA);
1043
hParam xB = X_COORD_FOR_PT(c->ptB);
1044
hParam yB = Y_COORD_FOR_PT(c->ptB);
1046
// We want to fix the angle of the line connecting points A and B, to
1047
// be whatever it is right now in the initial numerical guess. Write
1048
// this in the usual dot/cross product form,
1049
// (xA - xB, yA - yB) dot (-(yA0 - yB0), xA0 - xB0) = 0
1051
AddEquation(c->id, 0,
1053
ETimes(EMinus(EParam(xA), EParam(xB)),
1054
EConstant(-(EvalParam(yA) - EvalParam(yB)))),
1055
ETimes(EMinus(EParam(yA), EParam(yB)),
1056
EConstant(EvalParam(xA) - EvalParam(xB)))));
1059
static void Make_Scale(SketchConstraint *c)
1061
SketchEntity *e = EntityById(c->entityA);
1066
char *sought = "so dy = ";
1067
char *s = strstr(e->text, sought);
1069
s += strlen(sought);
1071
double dy = atof(s);
1072
if(c->type == CONSTRAINT_SCALE_MM) {
1074
} else if(c->type == CONSTRAINT_SCALE_INCH) {
1082
hPoint pA = POINT_FOR_ENTITY(c->entityA, 0);
1083
hPoint pB = POINT_FOR_ENTITY(c->entityA, 1);
1085
AddEquation(c->id, 0, EMinus(EDistance(pA, pB), EConstant(dy)));
1088
void MakeConstraintEquations(SketchConstraint *c)
1091
case CONSTRAINT_POINTS_COINCIDENT:
1092
case CONSTRAINT_PT_PT_DISTANCE:
1093
Make_PtPtDistance(c);
1096
case CONSTRAINT_POINT_ON_LINE:
1097
case CONSTRAINT_PT_LINE_DISTANCE:
1098
Make_PtLineDistance(c);
1101
case CONSTRAINT_LINE_LINE_DISTANCE:
1102
Make_LineLineDistance(c);
1105
case CONSTRAINT_ON_CIRCLE:
1109
case CONSTRAINT_RADIUS:
1113
case CONSTRAINT_PARALLEL:
1114
case CONSTRAINT_PERPENDICULAR:
1115
case CONSTRAINT_LINE_LINE_ANGLE:
1119
case CONSTRAINT_EQUAL_LENGTH:
1120
Make_EqualLength(c);
1123
case CONSTRAINT_EQUAL_RADIUS:
1124
Make_EqualRadius(c);
1127
case CONSTRAINT_AT_MIDPOINT:
1131
case CONSTRAINT_SYMMETRIC:
1135
case CONSTRAINT_HORIZONTAL:
1136
case CONSTRAINT_VERTICAL:
1137
Make_HorizontalVertical(c);
1140
case CONSTRAINT_FORCE_PARAM:
1144
case CONSTRAINT_FORCE_ANGLE:
1148
case CONSTRAINT_SCALE_MM:
1149
case CONSTRAINT_SCALE_INCH:
1158
void MakeEntityEquations(SketchEntity *e)
1161
case ENTITY_CIRCULAR_ARC: {
1162
Expr *d0 = EDistance(POINT_FOR_ENTITY(e->id, 0),
1163
POINT_FOR_ENTITY(e->id, 2));
1164
Expr *d1 = EDistance(POINT_FOR_ENTITY(e->id, 1),
1165
POINT_FOR_ENTITY(e->id, 2));
1166
// Make sure it gets an hEquation that won't conflict with
1167
// some constraint's equation.
1168
AddEquation(CONSTRAINT_FOR_ENTITY(e->id), 0, EMinus(d0, d1));
1173
// Most entities don't generate any equations. Ideally, none
1179
static void ModifyConstraintToReflectSketch(SketchConstraint *c)
1182
case CONSTRAINT_PT_PT_DISTANCE: {
1183
// Stupid special case. The point-to-point distance constraint
1184
// becomes a point-on-point constraint when that distance is
1185
// zero, and at that point it restricts two DOF, not one, so
1186
// I have to write two equations. That means that it breaks from
1187
// the usual form for "dimension"-type constraints.
1188
double xA, yA, xB, yB;
1189
EvalPoint(c->ptA, &xA, &yA);
1190
EvalPoint(c->ptB, &xB, &yB);
1191
c->v = Distance(xA, yA, xB, yB);
1194
case CONSTRAINT_LINE_LINE_ANGLE: {
1195
// Another special case; this equation has the form of a dot
1196
// product, not so easy to derive present angle from that.
1198
double x0[2], y0[2];
1199
double dx[2], dy[2];
1200
LineOrLineSegment(c->lineA, c->entityA,
1201
&(x0[A]), &(y0[A]), &(dx[A]), &(dy[A]));
1202
LineOrLineSegment(c->lineB, c->entityB,
1203
&(x0[B]), &(y0[B]), &(dx[B]), &(dy[B]));
1205
// A special case for the special case. If these are two line
1206
// segments that share an endpoint, then we clearly should be
1207
// dimensioning the angle between the lines segments, not the
1208
// angle that's hanging in thin air.
1209
if(c->entityA && c->entityB) {
1211
double x0A, y0A, x1A, y1A;
1212
double x0B, y0B, x1B, y1B;
1213
EvalPoint(POINT_FOR_ENTITY(c->entityA, 0), &x0A, &y0A);
1214
EvalPoint(POINT_FOR_ENTITY(c->entityA, 1), &x1A, &y1A);
1215
EvalPoint(POINT_FOR_ENTITY(c->entityB, 0), &x0B, &y0B);
1216
EvalPoint(POINT_FOR_ENTITY(c->entityB, 1), &x1B, &y1B);
1218
// Four possible cases for which points are coincident
1219
if(tol(x0A, x0B) && tol(y0A, y0B)) {
1224
} else if(tol(x0A, x1B) && tol(y0A, y1B)) {
1229
} else if(tol(x1A, x0B) && tol(y1A, y0B)) {
1234
} else if(tol(x1A, x1B) && tol(y1A, y1B)) {
1242
double thetaA = atan2(dy[A], dx[A]);
1243
double thetaB = atan2(dy[B], dx[B]);
1244
double dtheta = thetaA - thetaB;
1245
while(dtheta < PI) dtheta += 2*PI;
1246
while(dtheta > PI) dtheta -= 2*PI;
1247
c->v = dtheta*180/PI;
1250
case CONSTRAINT_PT_LINE_DISTANCE:
1251
case CONSTRAINT_LINE_LINE_DISTANCE:
1252
case CONSTRAINT_RADIUS: {
1253
// These constraints all have a number associated with them;
1254
// they are trying to make some measurement equal to that
1255
// number. The constraint equation f = 0 is written in the
1258
// actual - desired = 0
1260
// and the desired value is stored in c->v.
1262
MakeConstraintEquations(c);
1263
if(EQ->eqns != 1) oops();
1265
double actualMinusDesired = EEval(EQ->eqn[0].e);
1267
c->v += actualMinusDesired;
1270
case CONSTRAINT_SCALE_MM:
1271
case CONSTRAINT_SCALE_INCH: {
1272
// These constraints are in the form
1274
// actual - desired = 0
1276
// and the desired has the form (c->v)*k, for some constant k.
1279
MakeConstraintEquations(c);
1281
// The dimensions of the imported file might not yet be
1282
// calculated, in which case we can't do the constraint yet,
1283
// but it's not an error.
1287
double actual = EEval(EQ->eqn[0].e);
1290
MakeConstraintEquations(c);
1291
if(EQ->eqns != 1) oops();
1292
double actualMinusK = EEval(EQ->eqn[0].e);
1293
double k = actual - actualMinusK;
1300
// These constraints have no parameter. Either they hold, or
1301
// they don't, no way to adjust the constraint to make it go.