Solvespace
1380 строк · 53.6 Кб
1//-----------------------------------------------------------------------------
2// Given a constraint, draw a graphical and user-selectable representation
3// of that constraint on-screen. We can either draw with gl, or compute the
4// distance from a point (the location of the mouse pointer) to the lines
5// that we would have drawn, for selection.
6//
7// Copyright 2008-2013 Jonathan Westhues.
8//-----------------------------------------------------------------------------
9#include "solvespace.h"10
11std::string Constraint::Label() const {12std::string result;13if(type == Type::ANGLE) {14result = SS.DegreeToString(valA) + "°";15} else if(type == Type::LENGTH_RATIO || type == Type::ARC_ARC_LEN_RATIO || type == Type::ARC_LINE_LEN_RATIO) {16result = ssprintf("%.3f:1", valA);17} else if(type == Type::COMMENT) {18result = comment;19} else if(type == Type::DIAMETER) {20if(!other) {21result = "⌀" + SS.MmToStringSI(valA);22} else {23result = "R" + SS.MmToStringSI(valA / 2);24}25} else {26// valA has units of distance27result = SS.MmToStringSI(fabs(valA));28}29if(reference) {30result += " REF";31}32return result;33}
34
35void Constraint::DoLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) {36const Camera &camera = canvas->GetCamera();37
38a = camera.AlignToPixelGrid(a);39b = camera.AlignToPixelGrid(b);40canvas->DrawLine(a, b, hcs);41}
42
43void Constraint::DoStippledLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) {44Canvas::Stroke strokeStippled = *canvas->strokes.FindById(hcs);45strokeStippled.stipplePattern = StipplePattern::SHORT_DASH;46strokeStippled.stippleScale = 4.0;47Canvas::hStroke hcsStippled = canvas->GetStroke(strokeStippled);48DoLine(canvas, hcsStippled, a, b);49}
50
51void Constraint::DoLabel(Canvas *canvas, Canvas::hStroke hcs,52Vector ref, Vector *labelPos, Vector gr, Vector gu) {53const Camera &camera = canvas->GetCamera();54
55std::string s = Label();56double textHeight = Style::TextHeight(GetStyle()) / camera.scale;57double swidth = VectorFont::Builtin()->GetWidth(textHeight, s),58sheight = VectorFont::Builtin()->GetCapHeight(textHeight);59
60// By default, the reference is from the center; but the style could61// specify otherwise if one is present, and it could also specify a62// rotation.63if(type == Type::COMMENT && disp.style.v) {64Style *st = Style::Get(disp.style);65// rotation first66double rads = st->textAngle*PI/180;67double c = cos(rads), s = sin(rads);68Vector pr = gr, pu = gu;69gr = pr.ScaledBy( c).Plus(pu.ScaledBy(s));70gu = pr.ScaledBy(-s).Plus(pu.ScaledBy(c));71// then origin72uint32_t o = (uint32_t)st->textOrigin;73if(o & (uint32_t)Style::TextOrigin::LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2));74if(o & (uint32_t)Style::TextOrigin::RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2));75if(o & (uint32_t)Style::TextOrigin::BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2));76if(o & (uint32_t)Style::TextOrigin::TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2));77}78
79Vector o = ref.Minus(gr.WithMagnitude(swidth/2)).Minus(80gu.WithMagnitude(sheight/2));81canvas->DrawVectorText(s, textHeight, o, gr.WithMagnitude(1), gu.WithMagnitude(1), hcs);82if(labelPos) *labelPos = o;83}
84
85void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs,86Vector *r, Vector n, Vector o) {87double d = r->DistanceToPlane(n, o);88Vector p = r->Minus(n.ScaledBy(d));89DoStippledLine(canvas, hcs, p, *r);90*r = p;91}
92
93void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *r) {94Vector p = r->ProjectInto(workplane);95DoStippledLine(canvas, hcs, p, *r);96*r = p;97}
98
99//-----------------------------------------------------------------------------
100// There is a rectangular box, aligned to our display axes (projRight, projUp)
101// centered at ref. This is where a dimension label will be drawn. We want to
102// draw a line from A to B. If that line would intersect the label box, then
103// trim the line to leave a gap for it, and return zero. If not, then extend
104// the line to almost meet the box, and return either positive or negative,
105// depending whether that extension was from A or from B.
106//-----------------------------------------------------------------------------
107int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,108Vector ref, Vector a, Vector b, bool extend) {109const Camera &camera = canvas->GetCamera();110double th = Style::TextHeight(GetStyle()) / camera.scale;111double pixels = 1.0 / camera.scale;112double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 8 * pixels,113sheight = VectorFont::Builtin()->GetCapHeight(th) + 8 * pixels;114Vector gu = camera.projUp.WithMagnitude(1),115gr = camera.projRight.WithMagnitude(1);116return DoLineTrimmedAgainstBox(canvas, hcs, ref, a, b, extend, gr, gu, swidth, sheight);117}
118
119int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,120Vector ref, Vector a, Vector b, bool extend,121Vector gr, Vector gu, double swidth, double sheight) {122struct {123Vector n;124double d;125} planes[4];126// reference pos is the center of box occupied by text; build a rectangle127// around that, aligned to axes gr and gu, from four planes will all four128// normals pointing inward129planes[0].n = gu.ScaledBy(-1); planes[0].d = -(gu.Dot(ref) + sheight/2);130planes[1].n = gu; planes[1].d = gu.Dot(ref) - sheight/2;131planes[2].n = gr; planes[2].d = gr.Dot(ref) - swidth/2;132planes[3].n = gr.ScaledBy(-1); planes[3].d = -(gr.Dot(ref) + swidth/2);133
134double tmin = VERY_POSITIVE, tmax = VERY_NEGATIVE;135Vector dl = b.Minus(a);136
137for(int i = 0; i < 4; i++) {138bool parallel;139Vector p = Vector::AtIntersectionOfPlaneAndLine(140planes[i].n, planes[i].d,141a, b, ¶llel);142if(parallel) continue;143
144int j;145for(j = 0; j < 4; j++) {146double d = (planes[j].n).Dot(p) - planes[j].d;147if(d < -LENGTH_EPS) break;148}149if(j < 4) continue;150
151double t = (p.Minus(a)).DivProjected(dl);152tmin = min(t, tmin);153tmax = max(t, tmax);154}155
156// Both in range; so there's pieces of the line on both sides of the label box.157if(tmin >= 0.0 && tmin <= 1.0 && tmax >= 0.0 && tmax <= 1.0) {158DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin)));159DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b);160return 0;161}162
163// Only one intersection in range; so the box is right on top of the endpoint164if(tmin >= 0.0 && tmin <= 1.0) {165DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin)));166return 0;167}168
169// Likewise.170if(tmax >= 0.0 && tmax <= 1.0) {171DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b);172return 0;173}174
175// The line does not intersect the label; so the line should get176// extended to just barely meet the label.177// 0 means the label lies within the line, negative means it's outside178// and closer to b, positive means outside and closer to a.179if(tmax < 0.0) {180if(extend) a = a.Plus(dl.ScaledBy(tmax));181DoLine(canvas, hcs, a, b);182return 1;183}184
185if(tmin > 1.0) {186if(extend) b = a.Plus(dl.ScaledBy(tmin));187DoLine(canvas, hcs, a, b);188return -1;189}190
191// This will happen if the entire line lies within the box.192return 0;193}
194
195void Constraint::DoArrow(Canvas *canvas, Canvas::hStroke hcs,196Vector p, Vector dir, Vector n, double width, double angle, double da) {197dir = dir.WithMagnitude(width / cos(angle));198dir = dir.RotatedAbout(n, da);199DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n, angle)));200DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n, -angle)));201}
202
203//-----------------------------------------------------------------------------
204// Draw a line with arrows on both ends, and possibly a gap in the middle for
205// the dimension. We will use these for most length dimensions. The length
206// being dimensioned is from A to B; but those points get extended perpendicular
207// to the line AB, until the line between the extensions crosses ref (the
208// center of the label).
209//-----------------------------------------------------------------------------
210void Constraint::DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,211Vector ref, Vector a, Vector b,212bool onlyOneExt)213{
214const Camera &camera = canvas->GetCamera();215double pixels = 1.0 / camera.scale;216
217Vector ab = a.Minus(b);218Vector ar = a.Minus(ref);219// Normal to a plane containing the line and the label origin.220Vector n = ab.Cross(ar);221// Within that plane, and normal to the line AB; so that's our extension222// line.223Vector out = ab.Cross(n).WithMagnitude(1);224out = out.ScaledBy(-out.Dot(ar));225
226Vector ae = a.Plus(out), be = b.Plus(out);227
228// Extension lines extend 10 pixels beyond where the arrows get229// drawn (which is at the same offset perpendicular from AB as the230// label).231DoLine(canvas, hcs, a, ae.Plus(out.WithMagnitude(10*pixels)));232if(!onlyOneExt) {233DoLine(canvas, hcs, b, be.Plus(out.WithMagnitude(10*pixels)));234}235
236int within = DoLineTrimmedAgainstBox(canvas, hcs, ref, ae, be);237
238// Arrow heads are 13 pixels long, with an 18 degree half-angle.239double theta = 18*PI/180;240Vector arrow = (be.Minus(ae)).WithMagnitude(13*pixels);241
242if(within != 0) {243arrow = arrow.ScaledBy(-1);244Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels);245if(within < 0) DoLine(canvas, hcs, ae, ae.Minus(seg));246if(within > 0) DoLine(canvas, hcs, be, be.Plus(seg));247}248
249DoArrow(canvas, hcs, ae, arrow, n, 13.0 * pixels, theta, 0.0);250DoArrow(canvas, hcs, be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0);251}
252
253void Constraint::DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs,254Vector a, Vector b, Vector gn, Vector *refp) {255const Camera &camera = canvas->GetCamera();256
257Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3));258if(refp) *refp = m;259Vector ab = a.Minus(b);260Vector n = (gn.Cross(ab)).WithMagnitude(10/camera.scale);261
262DoLine(canvas, hcs, m.Minus(n), m.Plus(n));263}
264
265void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,266hEntity he, Vector *refp) {267const Camera &camera = canvas->GetCamera();268Entity *circ = SK.GetEntity(he);269
270Vector center = SK.GetEntity(circ->point[0])->PointGetDrawNum();271double r = circ->CircleGetRadiusNum();272Quaternion q = circ->Normal()->NormalGetNum();273Vector u = q.RotationU(), v = q.RotationV();274
275double theta;276if(circ->type == Entity::Type::CIRCLE) {277theta = PI/2;278} else if(circ->type == Entity::Type::ARC_OF_CIRCLE) {279double thetaa, thetab, dtheta;280circ->ArcGetAngles(&thetaa, &thetab, &dtheta);281theta = thetaa + dtheta/2;282} else ssassert(false, "Unexpected entity type");283
284Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta)));285d = d.ScaledBy(r);286Vector p = center.Plus(d);287if(refp) *refp = p;288Vector tick = d.WithMagnitude(10/camera.scale);289DoLine(canvas, hcs, p.Plus(tick), p.Minus(tick));290}
291
292void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,293Vector a0, Vector da, Vector b0, Vector db,294Vector offset, Vector *ref, bool trim,295Vector explodeOffset)296{
297const Camera &camera = canvas->GetCamera();298double pixels = 1.0 / camera.scale;299Vector gr = camera.projRight.ScaledBy(1.0);300Vector gu = camera.projUp.ScaledBy(1.0);301
302if(workplane != Entity::FREE_IN_3D) {303a0 = a0.ProjectInto(workplane);304b0 = b0.ProjectInto(workplane);305da = da.ProjectVectorInto(workplane);306db = db.ProjectVectorInto(workplane);307}308
309a0 = a0.Plus(explodeOffset);310b0 = b0.Plus(explodeOffset);311
312Vector a1 = a0.Plus(da);313Vector b1 = b0.Plus(db);314
315bool skew;316Vector pi = Vector::AtIntersectionOfLines(a0, a0.Plus(da),317b0, b0.Plus(db), &skew);318
319if(!skew) {320*ref = pi.Plus(offset);321// We draw in a coordinate system centered at the intersection point.322// One basis vector is da, and the other is normal to da and in323// the plane that contains our lines (so normal to its normal).324da = da.WithMagnitude(1);325db = db.WithMagnitude(1);326
327Vector norm = da.Cross(db);328
329Vector dna = norm.Cross(da).WithMagnitude(1.0);330Vector dnb = norm.Cross(db).WithMagnitude(1.0);331
332// da and db magnitudes are 1.0333double thetaf = acos(da.Dot(db));334
335// Calculate median336Vector m = da.ScaledBy(cos(thetaf/2)).Plus(337dna.ScaledBy(sin(thetaf/2)));338Vector rm = (*ref).Minus(pi);339
340// Test which side we have to place an arc341if(m.Dot(rm) < 0) {342da = da.ScaledBy(-1); dna = dna.ScaledBy(-1);343db = db.ScaledBy(-1); dnb = dnb.ScaledBy(-1);344}345
346double rda = rm.Dot(da), rdna = rm.Dot(dna);347
348// Introduce minimal arc radius in pixels349double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * pixels);350
351double th = Style::TextHeight(GetStyle()) / camera.scale;352double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 8*pixels,353sheight = VectorFont::Builtin()->GetCapHeight(th) + 6*pixels;354double textR = sqrt(swidth * swidth + sheight * sheight) / 2.0;355*ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * pixels + textR)));356
357// Arrow points358Vector apa = da. ScaledBy(r).Plus(pi);359Vector apb = da. ScaledBy(r*cos(thetaf)).Plus(360dna.ScaledBy(r*sin(thetaf))).Plus(pi);361
362double arrowW = 13 * pixels;363double arrowA = 18.0 * PI / 180.0;364bool arrowVisible = apb.Minus(apa).Magnitude() > 2.5 * arrowW;365// Arrow reversing indicator366bool arrowRev = false;367
368// The minimal extension length in angular representation369double extAngle = 18 * pixels / r;370
371// Arc additional angle372double addAngle = 0.0;373// Arc start angle374double startAngle = 0.0;375
376// Arc extension to db.377// We have just enlarge angle value.378if(HasLabel() && rm.Dot(dnb) > 0.0) {379// rm direction projected to plane with u = da, v = dna380Vector rmp = da.ScaledBy(rda).Plus(dna.ScaledBy(rdna)).WithMagnitude(1.0);381// rmp and db magnitudes are 1.0382addAngle = std::max(acos(rmp.Dot(db)), extAngle);383
384if(arrowVisible) {385startAngle = -extAngle;386addAngle += extAngle;387arrowRev = true;388}389}390
391// Arc extension to da.392// We are enlarge angle value and rewrite basis to align along rm projection.393if(HasLabel() && rm.Dot(dna) < 0.0) {394// rm direction projected to plane with u = da, v = dna395Vector rmp = da.ScaledBy(rda).Plus(dna.ScaledBy(rdna)).WithMagnitude(1.0);396// rmp and da magnitudes are 1.0397startAngle = -std::max(acos(rmp.Dot(da)), extAngle);398addAngle = -startAngle;399
400if(arrowVisible) {401addAngle += extAngle;402arrowRev = true;403}404}405
406Vector prev;407int n = 30;408for(int i = 0; i <= n; i++) {409double theta = startAngle + (i*(thetaf + addAngle))/n;410Vector p = da.ScaledBy(r*cos(theta)).Plus(411dna.ScaledBy(r*sin(theta))).Plus(pi);412if(i > 0) {413if(trim) {414DoLineTrimmedAgainstBox(canvas, hcs, *ref, prev, p,415/*extend=*/false, gr, gu, swidth, sheight + 2*pixels);416} else {417DoLine(canvas, hcs, prev, p);418}419}420prev = p;421}422
423DoLineExtend(canvas, hcs, a0, a1, apa, 5.0 * pixels);424DoLineExtend(canvas, hcs, b0, b1, apb, 5.0 * pixels);425
426// Draw arrows only when we have enough space.427if(arrowVisible) {428double angleCorr = arrowW / (2.0 * r);429if(arrowRev) {430dna = dna.ScaledBy(-1.0);431angleCorr = -angleCorr;432}433DoArrow(canvas, hcs, apa, dna, norm, arrowW, arrowA, angleCorr);434DoArrow(canvas, hcs, apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr);435}436} else {437// The lines are skew; no wonderful way to illustrate that.438
439*ref = a0.Plus(b0);440*ref = (*ref).ScaledBy(0.5).Plus(disp.offset);441gu = gu.WithMagnitude(1);442double textHeight = Style::TextHeight(GetStyle()) / camera.scale;443Vector trans =444(*ref).Plus(gu.ScaledBy(-1.5*VectorFont::Builtin()->GetCapHeight(textHeight)));445canvas->DrawVectorText("angle between skew lines", textHeight,446trans, gr.WithMagnitude(1), gu.WithMagnitude(1),447hcs);448}449}
450
451bool Constraint::IsVisible() const {452if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW)453return false;454bool isDim = false;455
456if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM)457switch(type) {458case ConstraintBase::Type::ANGLE:459case ConstraintBase::Type::DIAMETER:460case ConstraintBase::Type::PT_PT_DISTANCE:461case ConstraintBase::Type::PT_FACE_DISTANCE:462case ConstraintBase::Type::PT_LINE_DISTANCE:463case ConstraintBase::Type::PT_PLANE_DISTANCE: isDim = true; break;464default:;465}466
467if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL || isDim ) {468Group *g = SK.GetGroup(group);469// If the group is hidden, then the constraints are hidden and not470// able to be selected.471if(!(g->visible)) return false;472// And likewise if the group is not the active group; except for comments473// with an assigned style.474if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) {475return false;476}477if(disp.style.v) {478Style *s = Style::Get(disp.style);479if(!s->visible) return false;480}481return true;482}483return false;484}
485
486bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,487Vector p0, Vector p1, Vector pt, double salient) {488Vector dir = p1.Minus(p0);489double k = dir.Dot(pt.Minus(p0)) / dir.Dot(dir);490Vector ptOnLine = p0.Plus(dir.ScaledBy(k));491
492// Draw projection line.493DoLine(canvas, hcs, pt, ptOnLine);494
495// Calculate salient direction.496Vector sd = dir.WithMagnitude(1.0).ScaledBy(salient);497
498Vector from;499Vector to;500
501if(k < 0.0) {502from = p0;503to = ptOnLine.Minus(sd);504} else if(k > 1.0) {505from = p1;506to = ptOnLine.Plus(sd);507} else {508return false;509}510
511// Draw extension line.512DoLine(canvas, hcs, from, to);513return true;514}
515
516void Constraint::DoLayout(DrawAs how, Canvas *canvas,517Vector *labelPos, std::vector<Vector> *refs) {518if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&519!IsVisible()) return;520
521// Unit vectors that describe our current view of the scene. One pixel522// long, not one actual unit.523const Camera &camera = canvas->GetCamera();524Vector gr = camera.projRight.ScaledBy(1/camera.scale);525Vector gu = camera.projUp.ScaledBy(1/camera.scale);526Vector gn = (gr.Cross(gu)).WithMagnitude(1/camera.scale);527
528double textHeight = Style::TextHeight(GetStyle()) / camera.scale;529
530RgbaColor color = {};531switch(how) {532case DrawAs::DEFAULT: color = Style::Color(GetStyle()); break;533case DrawAs::HOVERED: color = Style::Color(Style::HOVERED); break;534case DrawAs::SELECTED: color = Style::Color(Style::SELECTED); break;535}536Canvas::Stroke stroke = Style::Stroke(GetStyle());537stroke.layer = Canvas::Layer::FRONT;538stroke.color = color;539stroke.zIndex = 4;540Canvas::hStroke hcs = canvas->GetStroke(stroke);541
542Canvas::Fill fill = {};543fill.layer = Canvas::Layer::FRONT;544fill.color = color;545fill.zIndex = stroke.zIndex;546Canvas::hFill hcf = canvas->GetFill(fill);547
548switch(type) {549case Type::PT_PT_DISTANCE: {550Vector ap = SK.GetEntity(ptA)->PointGetNum();551Vector bp = SK.GetEntity(ptB)->PointGetNum();552
553if(workplane != Entity::FREE_IN_3D) {554DoProjectedPoint(canvas, hcs, &ap);555DoProjectedPoint(canvas, hcs, &bp);556}557
558if(ShouldDrawExploded()) {559// Offset A and B by the same offset so the constraint is drawn560// in the plane of one of the exploded points (rather than at an561// angle)562Vector offset = SK.GetEntity(ptA)->ExplodeOffset();563ap = ap.Plus(offset);564bp = bp.Plus(offset);565}566
567Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);568if(refs) refs->push_back(ref);569
570DoLineWithArrows(canvas, hcs, ref, ap, bp, /*onlyOneExt=*/false);571DoLabel(canvas, hcs, ref, labelPos, gr, gu);572return;573}574
575case Type::PROJ_PT_DISTANCE: {576Vector ap = SK.GetEntity(ptA)->PointGetNum(),577bp = SK.GetEntity(ptB)->PointGetNum(),578dp = (bp.Minus(ap)),579pp = SK.GetEntity(entityA)->VectorGetNum();580
581if(ShouldDrawExploded()) {582// explode for whichever point is in the workplane (or the first if both are)583Entity *pt = SK.GetEntity(ptA);584if(pt->group != group) {585pt = SK.GetEntity(ptB);586}587if(pt->group == group) {588Vector offset = pt->ExplodeOffset();589ap = ap.Plus(offset);590bp = bp.Plus(offset);591}592}593
594Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);595if(refs) refs->push_back(ref);596
597pp = pp.WithMagnitude(1);598double d = dp.Dot(pp);599Vector bpp = ap.Plus(pp.ScaledBy(d));600DoStippledLine(canvas, hcs, ap, bpp);601DoStippledLine(canvas, hcs, bp, bpp);602
603DoLineWithArrows(canvas, hcs, ref, ap, bpp, /*onlyOneExt=*/false);604DoLabel(canvas, hcs, ref, labelPos, gr, gu);605return;606}607
608case Type::PT_FACE_DISTANCE:609case Type::PT_PLANE_DISTANCE: {610Vector pt = SK.GetEntity(ptA)->PointGetDrawNum();611Entity *enta = SK.GetEntity(entityA);612Vector n, p;613if(type == Type::PT_PLANE_DISTANCE) {614n = enta->Normal()->NormalN();615p = enta->WorkplaneGetOffset();616} else {617n = enta->FaceGetNormalNum();618p = enta->FaceGetPointNum();619}620
621double d = (p.Minus(pt)).Dot(n);622Vector closest = pt.Plus(n.WithMagnitude(d));623
624Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);625if(refs) refs->push_back(ref);626
627if(!pt.Equals(closest)) {628DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true);629}630
631DoLabel(canvas, hcs, ref, labelPos, gr, gu);632return;633}634
635case Type::PT_LINE_DISTANCE: {636Entity *ptEntity = SK.GetEntity(ptA);637Vector pt = ptEntity->PointGetNum();638Entity *line = SK.GetEntity(entityA);639Vector lA = SK.GetEntity(line->point[0])->PointGetNum();640Vector lB = SK.GetEntity(line->point[1])->PointGetNum();641Vector dl = lB.Minus(lA);642
643if(workplane != Entity::FREE_IN_3D) {644lA = lA.ProjectInto(workplane);645lB = lB.ProjectInto(workplane);646DoProjectedPoint(canvas, hcs, &pt);647}648
649// Only explode if the point and line are in the same group (and that group is a sketch650// with explode enabled) otherwise it's too visually confusing to figure out what the651// correct projections should be.652bool shouldExplode = ShouldDrawExploded()653&& ptEntity->group == group654&& line->group == group;655if(shouldExplode) {656Vector explodeOffset = ptEntity->ExplodeOffset();657pt = pt.Plus(explodeOffset);658lA = lA.Plus(explodeOffset);659lB = lB.Plus(explodeOffset);660}661
662// Find the closest point on the line663Vector closest = pt.ClosestPointOnLine(lA, dl);664
665Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset);666if(refs) refs->push_back(ref);667DoLabel(canvas, hcs, ref, labelPos, gr, gu);668
669if(!pt.Equals(closest)) {670DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true);671
672// Draw projected point673Vector a = pt;674Vector b = closest;675Vector ab = a.Minus(b);676Vector ar = a.Minus(ref);677Vector n = ab.Cross(ar);678Vector out = ab.Cross(n).WithMagnitude(1);679out = out.ScaledBy(-out.Dot(ar));680Vector be = b.Plus(out);681Vector np = lA.Minus(pt).Cross(lB.Minus(pt)).WithMagnitude(1.0);682DoProjectedPoint(canvas, hcs, &be, np, pt);683
684// Extensions to line685double pixels = 1.0 / camera.scale;686Vector refClosest = ref.ClosestPointOnLine(lA, dl);687double ddl = dl.Dot(dl);688if(fabs(ddl) > LENGTH_EPS * LENGTH_EPS) {689double t = refClosest.Minus(lA).Dot(dl) / ddl;690if(t < 0.0) {691DoLine(canvas, hcs, refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA);692} else if(t > 1.0) {693DoLine(canvas, hcs, refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB);694}695}696}697
698if(workplane != Entity::FREE_IN_3D) {699// Draw the projection marker from the closest point on the700// projected line to the projected point on the real line.701Vector lAB = (lA.Minus(lB));702double t = (lA.Minus(closest)).DivProjected(lAB);703
704Vector lA = SK.GetEntity(line->point[0])->PointGetNum();705Vector lB = SK.GetEntity(line->point[1])->PointGetNum();706
707Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t));708DoProjectedPoint(canvas, hcs, &c2);709}710return;711}712
713case Type::DIAMETER: {714Entity *circle = SK.GetEntity(entityA);715Vector center = SK.GetEntity(circle->point[0])->PointGetDrawNum();716Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();717Vector n = q.RotationN().WithMagnitude(1);718double r = circle->CircleGetRadiusNum();719
720Vector ref = center.Plus(disp.offset);721// Force the label into the same plane as the circle.722ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));723if(refs) refs->push_back(ref);724
725Vector mark = ref.Minus(center);726mark = mark.WithMagnitude(mark.Magnitude()-r);727DoLineTrimmedAgainstBox(canvas, hcs, ref, ref, ref.Minus(mark));728
729Vector topLeft;730DoLabel(canvas, hcs, ref, &topLeft, gr, gu);731if(labelPos) *labelPos = topLeft;732return;733}734
735case Type::POINTS_COINCIDENT: {736if(how == DrawAs::DEFAULT) {737// Let's adjust the color of this constraint to have the same738// rough luma as the point color, so that the constraint does not739// stand out in an ugly way.740RgbaColor cd = Style::Color(Style::DATUM),741cc = Style::Color(Style::CONSTRAINT);742// convert from 8-bit color to a vector743Vector vd = Vector::From(cd.redF(), cd.greenF(), cd.blueF()),744vc = Vector::From(cc.redF(), cc.greenF(), cc.blueF());745// and scale the constraint color to have the same magnitude as746// the datum color, maybe a bit dimmer747vc = vc.WithMagnitude(vd.Magnitude()*0.9);748// and set the color to that.749fill.color = RGBf(vc.x, vc.y, vc.z);750hcf = canvas->GetFill(fill);751}752
753for(int a = 0; a < 2; a++) {754Vector r = camera.projRight.ScaledBy((a+1)/camera.scale);755Vector d = camera.projUp.ScaledBy((2-a)/camera.scale);756for(int i = 0; i < 2; i++) {757Vector p = SK.GetEntity(i == 0 ? ptA : ptB)->PointGetDrawNum();758if(refs) refs->push_back(p);759canvas->DrawQuad(p.Plus (r).Plus (d),760p.Plus (r).Minus(d),761p.Minus(r).Minus(d),762p.Minus(r).Plus (d),763hcf);764}765
766}767return;768}769
770case Type::PT_ON_CIRCLE:771case Type::PT_ON_LINE:772case Type::PT_ON_FACE:773case Type::PT_IN_PLANE: {774double s = 8/camera.scale;775Vector p = SK.GetEntity(ptA)->PointGetDrawNum();776if(refs) refs->push_back(p);777Vector r, d;778if(type == Type::PT_ON_FACE) {779Vector n = SK.GetEntity(entityA)->FaceGetNormalNum();780r = n.Normal(0);781d = n.Normal(1);782} else if(type == Type::PT_IN_PLANE) {783EntityBase *n = SK.GetEntity(entityA)->Normal();784r = n->NormalU();785d = n->NormalV();786} else {787r = gr;788d = gu;789s *= (6.0/8); // draw these a little smaller790}791r = r.WithMagnitude(s); d = d.WithMagnitude(s);792DoLine(canvas, hcs, p.Plus (r).Plus (d), p.Plus (r).Minus(d));793DoLine(canvas, hcs, p.Plus (r).Minus(d), p.Minus(r).Minus(d));794DoLine(canvas, hcs, p.Minus(r).Minus(d), p.Minus(r).Plus (d));795DoLine(canvas, hcs, p.Minus(r).Plus (d), p.Plus (r).Plus (d));796return;797}798
799case Type::WHERE_DRAGGED: {800Vector p = SK.GetEntity(ptA)->PointGetDrawNum();801if(refs) refs->push_back(p);802Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus(803gr.WithMagnitude(8/camera.scale)),804uu = u.Minus(gu.WithMagnitude(5/camera.scale)),805ur = u.Minus(gr.WithMagnitude(5/camera.scale));806// Draw four little crop marks, uniformly spaced (by ninety807// degree rotations) around the point.808int i;809for(i = 0; i < 4; i++) {810DoLine(canvas, hcs, u, uu);811DoLine(canvas, hcs, u, ur);812u = u.RotatedAbout(p, gn, PI/2);813ur = ur.RotatedAbout(p, gn, PI/2);814uu = uu.RotatedAbout(p, gn, PI/2);815}816return;817}818
819case Type::SAME_ORIENTATION: {820for(int i = 0; i < 2; i++) {821Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);822Quaternion q = e->NormalGetNum();823Vector n = q.RotationN().WithMagnitude(25/camera.scale);824Vector u = q.RotationU().WithMagnitude(6/camera.scale);825Vector p = SK.GetEntity(e->point[0])->PointGetNum();826p = p.Plus(n.WithMagnitude(10/camera.scale));827if(refs) refs->push_back(p);828
829DoLine(canvas, hcs, p.Plus(u), p.Minus(u).Plus(n));830DoLine(canvas, hcs, p.Minus(u), p.Plus(u).Plus(n));831}832return;833}834
835case Type::EQUAL_ANGLE: {836Vector ref;837Entity *a = SK.GetEntity(entityA);838Entity *b = SK.GetEntity(entityB);839Entity *c = SK.GetEntity(entityC);840Entity *d = SK.GetEntity(entityD);841
842Vector a0 = a->VectorGetStartPoint();843Vector b0 = b->VectorGetStartPoint();844Vector c0 = c->VectorGetStartPoint();845Vector d0 = d->VectorGetStartPoint();846Vector da = a->VectorGetNum();847Vector db = b->VectorGetNum();848Vector dc = c->VectorGetNum();849Vector dd = d->VectorGetNum();850
851if(other) {852a0 = a0.Plus(da);853da = da.ScaledBy(-1);854}855
856DoArcForAngle(canvas, hcs, a0, da, b0, db,857da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, a->ExplodeOffset());858if(refs) refs->push_back(ref);859DoArcForAngle(canvas, hcs, c0, dc, d0, dd,860dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, c->ExplodeOffset());861if(refs) refs->push_back(ref);862
863return;864}865
866case Type::ANGLE: {867Entity *a = SK.GetEntity(entityA);868Entity *b = SK.GetEntity(entityB);869
870Vector a0 = a->VectorGetStartPoint();871Vector b0 = b->VectorGetStartPoint();872Vector da = a->VectorGetNum();873Vector db = b->VectorGetNum();874if(other) {875a0 = a0.Plus(da);876da = da.ScaledBy(-1);877}878
879Vector ref;880DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true, a->ExplodeOffset());881DoLabel(canvas, hcs, ref, labelPos, gr, gu);882if(refs) refs->push_back(ref);883return;884}885
886case Type::PERPENDICULAR: {887Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0);888Vector rn, ru;889if(workplane == Entity::FREE_IN_3D) {890rn = gn;891ru = gu;892} else {893EntityBase *normal = SK.GetEntity(workplane)->Normal();894rn = normal->NormalN();895ru = normal->NormalV(); // ru meaning r_up, not u/v896}897
898for(int i = 0; i < 2; i++) {899Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);900
901if(i == 0) {902// Calculate orientation of perpendicular sign only903// once, so that it's the same both times it's drawn904u = e->VectorGetNum();905u = u.WithMagnitude(16/camera.scale);906v = (rn.Cross(u)).WithMagnitude(16/camera.scale);907// a bit of bias to stop it from flickering between the908// two possibilities909if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) {910swap(u, v);911}912if(u.Dot(ru) < 0) u = u.ScaledBy(-1);913}914
915Vector p = e->VectorGetRefPoint().Plus(e->ExplodeOffset());916Vector s = p.Plus(u).Plus(v);917DoLine(canvas, hcs, s, s.Plus(v));918Vector m = s.Plus(v.ScaledBy(0.5));919DoLine(canvas, hcs, m, m.Plus(u));920if(refs) refs->push_back(m);921}922return;923}924
925case Type::CURVE_CURVE_TANGENT:926case Type::CUBIC_LINE_TANGENT:927case Type::ARC_LINE_TANGENT: {928Vector textAt, u, v;929
930if(type == Type::ARC_LINE_TANGENT) {931Entity *arc = SK.GetEntity(entityA);932Entity *norm = SK.GetEntity(arc->normal);933Vector c = SK.GetEntity(arc->point[0])->PointGetDrawNum();934Vector p =935SK.GetEntity(arc->point[other ? 2 : 1])->PointGetDrawNum();936Vector r = p.Minus(c);937textAt = p.Plus(r.WithMagnitude(14/camera.scale));938u = norm->NormalU();939v = norm->NormalV();940} else if(type == Type::CUBIC_LINE_TANGENT) {941Vector n;942if(workplane == Entity::FREE_IN_3D) {943u = gr;944v = gu;945n = gn;946} else {947EntityBase *wn = SK.GetEntity(workplane)->Normal();948u = wn->NormalU();949v = wn->NormalV();950n = wn->NormalN();951}952
953Entity *cubic = SK.GetEntity(entityA);954Vector p = other ? cubic->CubicGetFinishNum() :955cubic->CubicGetStartNum();956p = p.Plus(cubic->ExplodeOffset());957Vector dir = SK.GetEntity(entityB)->VectorGetNum();958Vector out = n.Cross(dir);959textAt = p.Plus(out.WithMagnitude(14/camera.scale));960} else {961Vector n, dir;962EntityBase *wn = SK.GetEntity(workplane)->Normal();963u = wn->NormalU();964v = wn->NormalV();965n = wn->NormalN();966Entity *eA = SK.GetEntity(entityA);967// Big pain; we have to get a vector tangent to the curve968// at the shared point, which could be from either a cubic969// or an arc.970if(other) {971textAt = eA->EndpointFinish().Plus(eA->ExplodeOffset());972if(eA->type == Entity::Type::CUBIC) {973dir = eA->CubicGetFinishTangentNum();974} else {975dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus(976SK.GetEntity(eA->point[2])->PointGetNum());977dir = n.Cross(dir);978}979} else {980textAt = eA->EndpointStart().Plus(eA->ExplodeOffset());981if(eA->type == Entity::Type::CUBIC) {982dir = eA->CubicGetStartTangentNum();983} else {984dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus(985SK.GetEntity(eA->point[1])->PointGetNum());986dir = n.Cross(dir);987}988}989dir = n.Cross(dir);990textAt = textAt.Plus(dir.WithMagnitude(14/camera.scale));991}992
993Vector ex = VectorFont::Builtin()->GetExtents(textHeight, "T");994canvas->DrawVectorText("T", textHeight, textAt.Minus(ex.ScaledBy(0.5)),995u.WithMagnitude(1), v.WithMagnitude(1), hcs);996if(refs) refs->push_back(textAt);997return;998}999
1000case Type::PARALLEL: {1001for(int i = 0; i < 2; i++) {1002Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);1003Vector n = e->VectorGetNum();1004n = n.WithMagnitude(25/camera.scale);1005Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale);1006Vector p = e->VectorGetRefPoint();1007
1008if(ShouldDrawExploded()) {1009p = p.Plus(e->ExplodeOffset());1010}1011
1012DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n));1013DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n));1014if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5)));1015}1016return;1017}1018
1019case Type::EQUAL_RADIUS: {1020for(int i = 0; i < 2; i++) {1021Vector ref;1022DoEqualRadiusTicks(canvas, hcs, i == 0 ? entityA : entityB, &ref);1023if(refs) refs->push_back(ref);1024}1025return;1026}1027
1028case Type::EQUAL_LINE_ARC_LEN: {1029Entity *line = SK.GetEntity(entityA);1030Vector ref;1031DoEqualLenTicks(canvas, hcs,1032SK.GetEntity(line->point[0])->PointGetDrawNum(),1033SK.GetEntity(line->point[1])->PointGetDrawNum(),1034gn, &ref);1035if(refs) refs->push_back(ref);1036DoEqualRadiusTicks(canvas, hcs, entityB, &ref);1037if(refs) refs->push_back(ref);1038return;1039}1040
1041case Type::LENGTH_RATIO:1042case Type::LENGTH_DIFFERENCE:1043case Type::EQUAL_LENGTH_LINES: {1044Vector a, b = Vector::From(0, 0, 0);1045for(int i = 0; i < 2; i++) {1046Entity *e = SK.GetEntity(i == 0 ? entityA : entityB);1047a = SK.GetEntity(e->point[0])->PointGetNum();1048b = SK.GetEntity(e->point[1])->PointGetNum();1049
1050if(workplane != Entity::FREE_IN_3D) {1051DoProjectedPoint(canvas, hcs, &a);1052DoProjectedPoint(canvas, hcs, &b);1053}1054
1055if(ShouldDrawExploded()) {1056Vector offset = e->ExplodeOffset();1057a = a.Plus(offset);1058b = b.Plus(offset);1059}1060
1061Vector ref;1062DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);1063if(refs) refs->push_back(ref);1064}1065if((type == Type::LENGTH_RATIO) || (type == Type::LENGTH_DIFFERENCE)) {1066Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset);1067DoLabel(canvas, hcs, ref, labelPos, gr, gu);1068}1069return;1070}1071case Type::ARC_ARC_LEN_RATIO:1072case Type::ARC_ARC_DIFFERENCE: {1073Entity *circle = SK.GetEntity(entityA);1074Vector center = SK.GetEntity(circle->point[0])->PointGetNum();1075Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();1076Vector n = q.RotationN().WithMagnitude(1);1077
1078Vector ref2;1079DoEqualRadiusTicks(canvas, hcs, entityA, &ref2);1080DoEqualRadiusTicks(canvas, hcs, entityB, &ref2);1081
1082Vector ref = center.Plus(disp.offset);1083// Force the label into the same plane as the circle.1084ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));1085if(refs) refs->push_back(ref);1086Vector topLeft;1087DoLabel(canvas, hcs, ref, &topLeft, gr, gu);1088if(labelPos) *labelPos = topLeft;1089return;1090}1091case Type::ARC_LINE_LEN_RATIO:1092case Type::ARC_LINE_DIFFERENCE: {1093Vector a, b = Vector::From(0, 0, 0);1094Vector ref;1095Entity *e = SK.GetEntity(entityA);1096a = SK.GetEntity(e->point[0])->PointGetNum();1097b = SK.GetEntity(e->point[1])->PointGetNum();1098DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);1099if(refs) refs->push_back(ref);1100DoEqualRadiusTicks(canvas, hcs, entityB, &ref);1101if(refs) refs->push_back(ref);1102ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset);1103DoLabel(canvas, hcs, ref, labelPos, gr, gu);1104return;1105}1106
1107case Type::EQ_LEN_PT_LINE_D: {1108Entity *forLen = SK.GetEntity(entityA);1109Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(),1110b = SK.GetEntity(forLen->point[1])->PointGetNum();1111if(workplane != Entity::FREE_IN_3D) {1112DoProjectedPoint(canvas, hcs, &a);1113DoProjectedPoint(canvas, hcs, &b);1114}1115if(ShouldDrawExploded()) {1116Vector offset = forLen->ExplodeOffset();1117a = a.Plus(offset);1118b = b.Plus(offset);1119}1120Vector refa;1121DoEqualLenTicks(canvas, hcs, a, b, gn, &refa);1122if(refs) refs->push_back(refa);1123
1124Entity *ln = SK.GetEntity(entityB);1125Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),1126lb = SK.GetEntity(ln->point[1])->PointGetNum();1127Vector pt = SK.GetEntity(ptA)->PointGetNum();1128if(workplane != Entity::FREE_IN_3D) {1129DoProjectedPoint(canvas, hcs, &pt);1130la = la.ProjectInto(workplane);1131lb = lb.ProjectInto(workplane);1132}1133
1134Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));1135if(ShouldDrawExploded()) {1136Vector offset = SK.GetEntity(ptA)->ExplodeOffset();1137pt = pt.Plus(offset);1138closest = closest.Plus(offset);1139}1140DoLine(canvas, hcs, pt, closest);1141Vector refb;1142DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb);1143if(refs) refs->push_back(refb);1144return;1145}1146
1147case Type::EQ_PT_LN_DISTANCES: {1148for(int i = 0; i < 2; i++) {1149Entity *ln = SK.GetEntity(i == 0 ? entityA : entityB);1150Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),1151lb = SK.GetEntity(ln->point[1])->PointGetNum();1152Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB);1153Vector pt = pte->PointGetNum();1154
1155if(workplane != Entity::FREE_IN_3D) {1156DoProjectedPoint(canvas, hcs, &pt);1157la = la.ProjectInto(workplane);1158lb = lb.ProjectInto(workplane);1159}1160
1161Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));1162if(ShouldDrawExploded()) {1163Vector offset = pte->ExplodeOffset();1164pt = pt.Plus(offset);1165closest = closest.Plus(offset);1166}1167DoLine(canvas, hcs, pt, closest);1168
1169Vector ref;1170DoEqualLenTicks(canvas, hcs, pt, closest, gn, &ref);1171if(refs) refs->push_back(ref);1172}1173return;1174}1175
1176{1177case Type::SYMMETRIC:1178Vector n;1179n = SK.GetEntity(entityA)->Normal()->NormalN(); goto s;1180case Type::SYMMETRIC_HORIZ:1181n = SK.GetEntity(workplane)->Normal()->NormalU(); goto s;1182case Type::SYMMETRIC_VERT:1183n = SK.GetEntity(workplane)->Normal()->NormalV(); goto s;1184case Type::SYMMETRIC_LINE: {1185Entity *ln = SK.GetEntity(entityA);1186Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),1187lb = SK.GetEntity(ln->point[1])->PointGetNum();1188la = la.ProjectInto(workplane);1189lb = lb.ProjectInto(workplane);1190n = lb.Minus(la);1191Vector nw = SK.GetEntity(workplane)->Normal()->NormalN();1192n = n.RotatedAbout(nw, PI/2);1193goto s;1194}1195s:1196Vector a = SK.GetEntity(ptA)->PointGetDrawNum();1197Vector b = SK.GetEntity(ptB)->PointGetDrawNum();1198
1199for(int i = 0; i < 2; i++) {1200Vector tail = (i == 0) ? a : b;1201Vector d = (i == 0) ? b : a;1202d = d.Minus(tail);1203// Project the direction in which the arrow is drawn normal1204// to the symmetry plane; for projected symmetry constraints,1205// they might not be in the same direction, even when the1206// constraint is fully solved.1207d = n.ScaledBy(d.Dot(n));1208d = d.WithMagnitude(20/camera.scale);1209Vector tip = tail.Plus(d);1210
1211DoLine(canvas, hcs, tail, tip);1212d = d.WithMagnitude(9/camera.scale);1213DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn, 0.6)));1214DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn, -0.6)));1215if(refs) refs->push_back(tip);1216}1217return;1218}1219
1220case Type::AT_MIDPOINT:1221case Type::HORIZONTAL:1222case Type::VERTICAL:1223if(entityA.v) {1224Vector r, u, n;1225if(workplane == Entity::FREE_IN_3D) {1226r = gr; u = gu; n = gn;1227} else {1228r = SK.GetEntity(workplane)->Normal()->NormalU();1229u = SK.GetEntity(workplane)->Normal()->NormalV();1230n = r.Cross(u);1231}1232// For "at midpoint", this branch is always taken.1233Entity *e = SK.GetEntity(entityA);1234Vector a = SK.GetEntity(e->point[0])->PointGetDrawNum();1235Vector b = SK.GetEntity(e->point[1])->PointGetDrawNum();1236Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));1237Vector offset = (a.Minus(b)).Cross(n);1238offset = offset.WithMagnitude(textHeight);1239// Draw midpoint constraint on other side of line, so that1240// a line can be midpoint and horizontal at same time.1241if(type == Type::AT_MIDPOINT) offset = offset.ScaledBy(-1);1242
1243std::string s;1244switch(type) {1245case Type::HORIZONTAL: s = "H"; break;1246case Type::VERTICAL: s = "V"; break;1247case Type::AT_MIDPOINT: s = "M"; break;1248default: ssassert(false, "Unexpected constraint type");1249}1250Vector o = m.Plus(offset).Plus(u.WithMagnitude(textHeight/5)),1251ex = VectorFont::Builtin()->GetExtents(textHeight, s);1252Vector shift = r.WithMagnitude(ex.x).Plus(1253u.WithMagnitude(ex.y));1254
1255canvas->DrawVectorText(s, textHeight, o.Minus(shift.ScaledBy(0.5)),1256r.WithMagnitude(1), u.WithMagnitude(1), hcs);1257if(refs) refs->push_back(o);1258} else {1259Vector a = SK.GetEntity(ptA)->PointGetDrawNum();1260Vector b = SK.GetEntity(ptB)->PointGetDrawNum();1261
1262Entity *w = SK.GetEntity(workplane);1263Vector cu = w->Normal()->NormalU();1264Vector cv = w->Normal()->NormalV();1265Vector cn = w->Normal()->NormalN();1266
1267int i;1268for(i = 0; i < 2; i++) {1269Vector o = (i == 0) ? a : b;1270Vector oo = (i == 0) ? a.Minus(b) : b.Minus(a);1271Vector d = (type == Type::HORIZONTAL) ? cu : cv;1272if(oo.Dot(d) < 0) d = d.ScaledBy(-1);1273
1274Vector dp = cn.Cross(d);1275d = d.WithMagnitude(14/camera.scale);1276Vector c = o.Minus(d);1277DoLine(canvas, hcs, o, c);1278d = d.WithMagnitude(3/camera.scale);1279dp = dp.WithMagnitude(2/camera.scale);1280canvas->DrawQuad((c.Plus(d)).Plus(dp),1281(c.Minus(d)).Plus(dp),1282(c.Minus(d)).Minus(dp),1283(c.Plus(d)).Minus(dp),1284hcf);1285if(refs) refs->push_back(c);1286}1287}1288return;1289
1290case Type::COMMENT: {1291Vector u, v;1292if(workplane == Entity::FREE_IN_3D) {1293u = gr;1294v = gu;1295} else {1296EntityBase *norm = SK.GetEntity(workplane)->Normal();1297u = norm->NormalU();1298v = norm->NormalV();1299}1300
1301if(disp.style.v != 0) {1302RgbaColor color = stroke.color;1303stroke = Style::Stroke(disp.style);1304stroke.layer = Canvas::Layer::FRONT;1305if(how != DrawAs::DEFAULT) {1306stroke.color = color;1307}1308hcs = canvas->GetStroke(stroke);1309}1310Vector ref = disp.offset;1311if(ptA.v) {1312Vector a = SK.GetEntity(ptA)->PointGetNum();1313ref = a.Plus(disp.offset);1314}1315DoLabel(canvas, hcs, ref, labelPos, u, v);1316if(refs) refs->push_back(ref);1317return;1318}1319}1320ssassert(false, "Unexpected constraint type");1321}
1322
1323void Constraint::Draw(DrawAs how, Canvas *canvas) {1324DoLayout(how, canvas, NULL, NULL);1325}
1326
1327Vector Constraint::GetLabelPos(const Camera &camera) {1328Vector p;1329
1330ObjectPicker canvas = {};1331canvas.camera = camera;1332DoLayout(DrawAs::DEFAULT, &canvas, &p, NULL);1333canvas.Clear();1334
1335return p;1336}
1337
1338void Constraint::GetReferencePoints(const Camera &camera, std::vector<Vector> *refs) {1339ObjectPicker canvas = {};1340canvas.camera = camera;1341DoLayout(DrawAs::DEFAULT, &canvas, NULL, refs);1342canvas.Clear();1343}
1344
1345bool Constraint::IsStylable() const {1346if(type == Type::COMMENT) return true;1347return false;1348}
1349
1350hStyle Constraint::GetStyle() const {1351if(disp.style.v != 0) return disp.style;1352return { Style::CONSTRAINT };1353}
1354
1355bool Constraint::HasLabel() const {1356switch(type) {1357case Type::COMMENT:1358case Type::PT_PT_DISTANCE:1359case Type::PT_PLANE_DISTANCE:1360case Type::PT_LINE_DISTANCE:1361case Type::PT_FACE_DISTANCE:1362case Type::PROJ_PT_DISTANCE:1363case Type::LENGTH_RATIO:1364case Type::ARC_ARC_LEN_RATIO:1365case Type::ARC_LINE_LEN_RATIO:1366case Type::LENGTH_DIFFERENCE:1367case Type::ARC_ARC_DIFFERENCE:1368case Type::ARC_LINE_DIFFERENCE:1369case Type::DIAMETER:1370case Type::ANGLE:1371return true;1372
1373default:1374return false;1375}1376}
1377
1378bool Constraint::ShouldDrawExploded() const {1379return SK.GetGroup(group)->ShouldDrawExploded();1380}
1381