//----------------------------------------------------------------------------- // A curve by its parametric equation, helper functions for computing tangent // arcs by a numerical method. //----------------------------------------------------------------------------- void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) { ZERO(this); Entity *e = SK.GetEntity(he); if(e->type == Entity::LINE_SEGMENT) { isLine = true; p0 = e->EndpointStart(), p1 = e->EndpointFinish(); if(reverse) { SWAP(Vector, p0, p1); } } else if(e->type == Entity::ARC_OF_CIRCLE) { isLine = false; p0 = SK.GetEntity(e->point[0])->PointGetNum(); Vector pe = SK.GetEntity(e->point[1])->PointGetNum(); r = (pe.Minus(p0)).Magnitude(); e->ArcGetAngles(&theta0, &theta1, &dtheta); if(reverse) { SWAP(double, theta0, theta1); dtheta = -dtheta; } EntityBase *wrkpln = SK.GetEntity(e->workplane)->Normal(); u = wrkpln->NormalU(); v = wrkpln->NormalV(); } else { oops(); } }
void Constraint::DrawOrGetDistance(Vector *labelPos) { if(!SS.GW.showConstraints) return; Group *g = SK.GetGroup(group); // If the group is hidden, then the constraints are hidden and not // able to be selected. if(!(g->visible)) return; // And likewise if the group is not the active group; except for comments // with an assigned style. if(g->h.v != SS.GW.activeGroup.v && !(type == COMMENT && disp.style.v)) { return; } if(disp.style.v) { Style *s = Style::Get(disp.style); if(!s->visible) return; } // Unit vectors that describe our current view of the scene. One pixel // long, not one actual unit. Vector gr = SS.GW.projRight.ScaledBy(1/SS.GW.scale); Vector gu = SS.GW.projUp.ScaledBy(1/SS.GW.scale); Vector gn = (gr.Cross(gu)).WithMagnitude(1/SS.GW.scale); switch(type) { case PT_PT_DISTANCE: { Vector ap = SK.GetEntity(ptA)->PointGetNum(); Vector bp = SK.GetEntity(ptB)->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&ap); DoProjectedPoint(&bp); } Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); DoLineWithArrows(ref, ap, bp, false); DoLabel(ref, labelPos, gr, gu); break; } case PROJ_PT_DISTANCE: { Vector ap = SK.GetEntity(ptA)->PointGetNum(), bp = SK.GetEntity(ptB)->PointGetNum(), dp = (bp.Minus(ap)), pp = SK.GetEntity(entityA)->VectorGetNum(); Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); pp = pp.WithMagnitude(1); double d = dp.Dot(pp); Vector bpp = ap.Plus(pp.ScaledBy(d)); StippledLine(ap, bpp); StippledLine(bp, bpp); DoLineWithArrows(ref, ap, bpp, false); DoLabel(ref, labelPos, gr, gu); break; } case PT_FACE_DISTANCE: case PT_PLANE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *enta = SK.GetEntity(entityA); Vector n, p; if(type == PT_PLANE_DISTANCE) { n = enta->Normal()->NormalN(); p = enta->WorkplaneGetOffset(); } else { n = enta->FaceGetNormalNum(); p = enta->FaceGetPointNum(); } double d = (p.Minus(pt)).Dot(n); Vector closest = pt.Plus(n.WithMagnitude(d)); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); if(!pt.Equals(closest)) { DoLineWithArrows(ref, pt, closest, true); } DoLabel(ref, labelPos, gr, gu); break; } case PT_LINE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *line = SK.GetEntity(entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector dl = lB.Minus(lA); if(workplane.v != Entity::FREE_IN_3D.v) { lA = lA.ProjectInto(workplane); lB = lB.ProjectInto(workplane); DoProjectedPoint(&pt); } // Find the closest point on the line Vector closest = pt.ClosestPointOnLine(lA, dl); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); DoLabel(ref, labelPos, gr, gu); if(!pt.Equals(closest)) { DoLineWithArrows(ref, pt, closest, true); } if(workplane.v != Entity::FREE_IN_3D.v) { // Draw the projection marker from the closest point on the // projected line to the projected point on the real line. Vector lAB = (lA.Minus(lB)); double t = (lA.Minus(closest)).DivPivoting(lAB); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t)); DoProjectedPoint(&c2); } break; } case DIAMETER: { Entity *circle = SK.GetEntity(entityA); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); Vector n = q.RotationN().WithMagnitude(1); double r = circle->CircleGetRadiusNum(); Vector ref = center.Plus(disp.offset); // Force the label into the same plane as the circle. ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center))); Vector mark = ref.Minus(center); mark = mark.WithMagnitude(mark.Magnitude()-r); DoLineTrimmedAgainstBox(ref, ref, ref.Minus(mark)); Vector topLeft; DoLabel(ref, &topLeft, gr, gu); if(labelPos) *labelPos = topLeft; // Show this as diameter or radius? if(!other) { // Draw the diameter symbol Vector dc = topLeft; dc = dc.Plus(gu.WithMagnitude(5/SS.GW.scale)); dc = dc.Plus(gr.WithMagnitude(9/SS.GW.scale)); double dr = 5/SS.GW.scale; double theta, dtheta = (2*PI)/12; for(theta = 0; theta < 2*PI-0.01; theta += dtheta) { LineDrawOrGetDistance( dc.Plus(gu.WithMagnitude(cos(theta)*dr)).Plus( gr.WithMagnitude(sin(theta)*dr)), dc.Plus(gu.WithMagnitude(cos(theta+dtheta)*dr)).Plus( gr.WithMagnitude(sin(theta+dtheta)*dr))); } theta = 25*(PI/180); dr *= 1.7; dtheta = PI; LineDrawOrGetDistance( dc.Plus(gu.WithMagnitude(cos(theta)*dr)).Plus( gr.WithMagnitude(sin(theta)*dr)), dc.Plus(gu.WithMagnitude(cos(theta+dtheta)*dr)).Plus( gr.WithMagnitude(sin(theta+dtheta)*dr))); } break; } case POINTS_COINCIDENT: { if(!dogd.drawing) { for(int i = 0; i < 2; i++) { Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); Point2d pp = SS.GW.ProjectPoint(p); // The point is selected within a radius of 7, from the // same center; so if the point is visible, then this // constraint cannot be selected. But that's okay. dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 3); dogd.refp = p; } break; } if(dogd.drawing) { // Let's adjust the color of this constraint to have the same // rough luma as the point color, so that the constraint does not // stand out in an ugly way. RgbaColor cd = Style::Color(Style::DATUM), cc = Style::Color(Style::CONSTRAINT); // convert from 8-bit color to a vector Vector vd = Vector::From(cd.redF(), cd.greenF(), cd.blueF()), vc = Vector::From(cc.redF(), cc.greenF(), cc.blueF()); // and scale the constraint color to have the same magnitude as // the datum color, maybe a bit dimmer vc = vc.WithMagnitude(vd.Magnitude()*0.9); // and set the color to that. ssglColorRGB(RGBf(vc.x, vc.y, vc.z)); for(int a = 0; a < 2; a++) { Vector r = SS.GW.projRight.ScaledBy((a+1)/SS.GW.scale); Vector d = SS.GW.projUp.ScaledBy((2-a)/SS.GW.scale); for(int i = 0; i < 2; i++) { Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); glBegin(GL_QUADS); ssglVertex3v(p.Plus (r).Plus (d)); ssglVertex3v(p.Plus (r).Minus(d)); ssglVertex3v(p.Minus(r).Minus(d)); ssglVertex3v(p.Minus(r).Plus (d)); glEnd(); } } } break; } case PT_ON_CIRCLE: case PT_ON_LINE: case PT_ON_FACE: case PT_IN_PLANE: { double s = 8/SS.GW.scale; Vector p = SK.GetEntity(ptA)->PointGetNum(); Vector r, d; if(type == PT_ON_FACE) { Vector n = SK.GetEntity(entityA)->FaceGetNormalNum(); r = n.Normal(0); d = n.Normal(1); } else if(type == PT_IN_PLANE) { EntityBase *n = SK.GetEntity(entityA)->Normal(); r = n->NormalU(); d = n->NormalV(); } else { r = gr; d = gu; s *= (6.0/8); // draw these a little smaller } r = r.WithMagnitude(s); d = d.WithMagnitude(s); LineDrawOrGetDistance(p.Plus (r).Plus (d), p.Plus (r).Minus(d)); LineDrawOrGetDistance(p.Plus (r).Minus(d), p.Minus(r).Minus(d)); LineDrawOrGetDistance(p.Minus(r).Minus(d), p.Minus(r).Plus (d)); LineDrawOrGetDistance(p.Minus(r).Plus (d), p.Plus (r).Plus (d)); break; } case WHERE_DRAGGED: { Vector p = SK.GetEntity(ptA)->PointGetNum(), u = p.Plus(gu.WithMagnitude(8/SS.GW.scale)).Plus( gr.WithMagnitude(8/SS.GW.scale)), uu = u.Minus(gu.WithMagnitude(5/SS.GW.scale)), ur = u.Minus(gr.WithMagnitude(5/SS.GW.scale)); // Draw four little crop marks, uniformly spaced (by ninety // degree rotations) around the point. int i; for(i = 0; i < 4; i++) { LineDrawOrGetDistance(u, uu); LineDrawOrGetDistance(u, ur); u = u.RotatedAbout(p, gn, PI/2); ur = ur.RotatedAbout(p, gn, PI/2); uu = uu.RotatedAbout(p, gn, PI/2); } break; } case SAME_ORIENTATION: { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Quaternion q = e->NormalGetNum(); Vector n = q.RotationN().WithMagnitude(25/SS.GW.scale); Vector u = q.RotationU().WithMagnitude(6/SS.GW.scale); Vector p = SK.GetEntity(e->point[0])->PointGetNum(); p = p.Plus(n.WithMagnitude(10/SS.GW.scale)); LineDrawOrGetDistance(p.Plus(u), p.Minus(u).Plus(n)); LineDrawOrGetDistance(p.Minus(u), p.Plus(u).Plus(n)); } break; } case EQUAL_ANGLE: { Vector ref; Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); Entity *c = SK.GetEntity(entityC); Entity *d = SK.GetEntity(entityD); Vector a0 = a->VectorGetRefPoint(); Vector b0 = b->VectorGetRefPoint(); Vector c0 = c->VectorGetRefPoint(); Vector d0 = d->VectorGetRefPoint(); Vector da = a->VectorGetNum(); Vector db = b->VectorGetNum(); Vector dc = c->VectorGetNum(); Vector dd = d->VectorGetNum(); if(other) da = da.ScaledBy(-1); DoArcForAngle(a0, da, b0, db, da.WithMagnitude(40/SS.GW.scale), &ref); DoArcForAngle(c0, dc, d0, dd, dc.WithMagnitude(40/SS.GW.scale), &ref); break; } case ANGLE: { Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); Vector a0 = a->VectorGetRefPoint(); Vector b0 = b->VectorGetRefPoint(); Vector da = a->VectorGetNum(); Vector db = b->VectorGetNum(); if(other) da = da.ScaledBy(-1); Vector ref; DoArcForAngle(a0, da, b0, db, disp.offset, &ref); DoLabel(ref, labelPos, gr, gu); break; } case PERPENDICULAR: { Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0); Vector rn, ru; if(workplane.v == Entity::FREE_IN_3D.v) { rn = gn; ru = gu; } else { EntityBase *normal = SK.GetEntity(workplane)->Normal(); rn = normal->NormalN(); ru = normal->NormalV(); // ru meaning r_up, not u/v } for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); if(i == 0) { // Calculate orientation of perpendicular sign only // once, so that it's the same both times it's drawn u = e->VectorGetNum(); u = u.WithMagnitude(16/SS.GW.scale); v = (rn.Cross(u)).WithMagnitude(16/SS.GW.scale); // a bit of bias to stop it from flickering between the // two possibilities if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) { SWAP(Vector, u, v); } if(u.Dot(ru) < 0) u = u.ScaledBy(-1); } Vector p = e->VectorGetRefPoint(); Vector s = p.Plus(u).Plus(v); LineDrawOrGetDistance(s, s.Plus(v)); Vector m = s.Plus(v.ScaledBy(0.5)); LineDrawOrGetDistance(m, m.Plus(u)); } break; } case CURVE_CURVE_TANGENT: case CUBIC_LINE_TANGENT: case ARC_LINE_TANGENT: { Vector textAt, u, v; if(type == ARC_LINE_TANGENT) { Entity *arc = SK.GetEntity(entityA); Entity *norm = SK.GetEntity(arc->normal); Vector c = SK.GetEntity(arc->point[0])->PointGetNum(); Vector p = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum(); Vector r = p.Minus(c); textAt = p.Plus(r.WithMagnitude(14/SS.GW.scale)); u = norm->NormalU(); v = norm->NormalV(); } else if(type == CUBIC_LINE_TANGENT) { Vector n; if(workplane.v == Entity::FREE_IN_3D.v) { u = gr; v = gu; n = gn; } else { EntityBase *wn = SK.GetEntity(workplane)->Normal(); u = wn->NormalU(); v = wn->NormalV(); n = wn->NormalN(); } Entity *cubic = SK.GetEntity(entityA); Vector p = other ? cubic->CubicGetFinishNum() : cubic->CubicGetStartNum(); Vector dir = SK.GetEntity(entityB)->VectorGetNum(); Vector out = n.Cross(dir); textAt = p.Plus(out.WithMagnitude(14/SS.GW.scale)); } else { Vector n, dir; EntityBase *wn = SK.GetEntity(workplane)->Normal(); u = wn->NormalU(); v = wn->NormalV(); n = wn->NormalN(); EntityBase *eA = SK.GetEntity(entityA); // Big pain; we have to get a vector tangent to the curve // at the shared point, which could be from either a cubic // or an arc. if(other) { textAt = eA->EndpointFinish(); if(eA->type == Entity::CUBIC) { dir = eA->CubicGetFinishTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( SK.GetEntity(eA->point[2])->PointGetNum()); dir = n.Cross(dir); } } else { textAt = eA->EndpointStart(); if(eA->type == Entity::CUBIC) { dir = eA->CubicGetStartTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( SK.GetEntity(eA->point[1])->PointGetNum()); dir = n.Cross(dir); } } dir = n.Cross(dir); textAt = textAt.Plus(dir.WithMagnitude(14/SS.GW.scale)); } if(dogd.drawing) { ssglWriteTextRefCenter("T", DEFAULT_TEXT_HEIGHT, textAt, u, v, LineCallback, this); } else { dogd.refp = textAt; Point2d ref = SS.GW.ProjectPoint(dogd.refp); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); } break; } case PARALLEL: { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Vector n = e->VectorGetNum(); n = n.WithMagnitude(25/SS.GW.scale); Vector u = (gn.Cross(n)).WithMagnitude(4/SS.GW.scale); Vector p = e->VectorGetRefPoint(); LineDrawOrGetDistance(p.Plus(u), p.Plus(u).Plus(n)); LineDrawOrGetDistance(p.Minus(u), p.Minus(u).Plus(n)); } break; } case EQUAL_RADIUS: { for(int i = 0; i < 2; i++) { DoEqualRadiusTicks(i == 0 ? entityA : entityB); } break; } case EQUAL_LINE_ARC_LEN: { Entity *line = SK.GetEntity(entityA); DoEqualLenTicks( SK.GetEntity(line->point[0])->PointGetNum(), SK.GetEntity(line->point[1])->PointGetNum(), gn); DoEqualRadiusTicks(entityB); break; } case LENGTH_RATIO: case EQUAL_LENGTH_LINES: { Vector a, b = Vector::From(0, 0, 0); for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); a = SK.GetEntity(e->point[0])->PointGetNum(); b = SK.GetEntity(e->point[1])->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&a); DoProjectedPoint(&b); } DoEqualLenTicks(a, b, gn); } if(type == LENGTH_RATIO) { Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset); DoLabel(ref, labelPos, gr, gu); } break; } case EQ_LEN_PT_LINE_D: { Entity *forLen = SK.GetEntity(entityA); Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(), b = SK.GetEntity(forLen->point[1])->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&a); DoProjectedPoint(&b); } DoEqualLenTicks(a, b, gn); Entity *ln = SK.GetEntity(entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); Vector pt = SK.GetEntity(ptA)->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); LineDrawOrGetDistance(pt, closest); DoEqualLenTicks(pt, closest, gn); break; } case EQ_PT_LN_DISTANCES: { for(int i = 0; i < 2; i++) { Entity *ln = SK.GetEntity(i == 0 ? entityA : entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB); Vector pt = pte->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); LineDrawOrGetDistance(pt, closest); DoEqualLenTicks(pt, closest, gn); } break; } { case SYMMETRIC: Vector n; n = SK.GetEntity(entityA)->Normal()->NormalN(); goto s; case SYMMETRIC_HORIZ: n = SK.GetEntity(workplane)->Normal()->NormalU(); goto s; case SYMMETRIC_VERT: n = SK.GetEntity(workplane)->Normal()->NormalV(); goto s; case SYMMETRIC_LINE: { Entity *ln = SK.GetEntity(entityA); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); n = lb.Minus(la); Vector nw = SK.GetEntity(workplane)->Normal()->NormalN(); n = n.RotatedAbout(nw, PI/2); goto s; } s: Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); for(int i = 0; i < 2; i++) { Vector tail = (i == 0) ? a : b; Vector d = (i == 0) ? b : a; d = d.Minus(tail); // Project the direction in which the arrow is drawn normal // to the symmetry plane; for projected symmetry constraints, // they might not be in the same direction, even when the // constraint is fully solved. d = n.ScaledBy(d.Dot(n)); d = d.WithMagnitude(20/SS.GW.scale); Vector tip = tail.Plus(d); LineDrawOrGetDistance(tail, tip); d = d.WithMagnitude(9/SS.GW.scale); LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, 0.6))); LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, -0.6))); } break; } case AT_MIDPOINT: case HORIZONTAL: case VERTICAL: if(entityA.v) { Vector r, u, n; if(workplane.v == Entity::FREE_IN_3D.v) { r = gr; u = gu; n = gn; } else { r = SK.GetEntity(workplane)->Normal()->NormalU(); u = SK.GetEntity(workplane)->Normal()->NormalV(); n = r.Cross(u); } // For "at midpoint", this branch is always taken. Entity *e = SK.GetEntity(entityA); Vector a = SK.GetEntity(e->point[0])->PointGetNum(); Vector b = SK.GetEntity(e->point[1])->PointGetNum(); Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5)); Vector offset = (a.Minus(b)).Cross(n); offset = offset.WithMagnitude(13/SS.GW.scale); // Draw midpoint constraint on other side of line, so that // a line can be midpoint and horizontal at same time. if(type == AT_MIDPOINT) offset = offset.ScaledBy(-1); if(dogd.drawing) { const char *s = (type == HORIZONTAL) ? "H" : ( (type == VERTICAL) ? "V" : ( (type == AT_MIDPOINT) ? "M" : NULL)); ssglWriteTextRefCenter(s, DEFAULT_TEXT_HEIGHT, m.Plus(offset), r, u, LineCallback, this); } else { dogd.refp = m.Plus(offset); Point2d ref = SS.GW.ProjectPoint(dogd.refp); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); } } else { Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); Entity *w = SK.GetEntity(workplane); Vector cu = w->Normal()->NormalU(); Vector cv = w->Normal()->NormalV(); Vector cn = w->Normal()->NormalN(); int i; for(i = 0; i < 2; i++) { Vector o = (i == 0) ? a : b; Vector oo = (i == 0) ? a.Minus(b) : b.Minus(a); Vector d = (type == HORIZONTAL) ? cu : cv; if(oo.Dot(d) < 0) d = d.ScaledBy(-1); Vector dp = cn.Cross(d); d = d.WithMagnitude(14/SS.GW.scale); Vector c = o.Minus(d); LineDrawOrGetDistance(o, c); d = d.WithMagnitude(3/SS.GW.scale); dp = dp.WithMagnitude(2/SS.GW.scale); if(dogd.drawing) { glBegin(GL_QUADS); ssglVertex3v((c.Plus(d)).Plus(dp)); ssglVertex3v((c.Minus(d)).Plus(dp)); ssglVertex3v((c.Minus(d)).Minus(dp)); ssglVertex3v((c.Plus(d)).Minus(dp)); glEnd(); } else { Point2d ref = SS.GW.ProjectPoint(c); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-6); } } } break; case COMMENT: { if(dogd.drawing && disp.style.v) { ssglLineWidth(Style::Width(disp.style)); ssglColorRGB(Style::Color(disp.style)); } Vector u, v; if(workplane.v == Entity::FREE_IN_3D.v) { u = gr; v = gu; } else { EntityBase *norm = SK.GetEntity(workplane)->Normal(); u = norm->NormalU(); v = norm->NormalV(); } DoLabel(disp.offset, labelPos, u, v); break; } default: oops(); } }
void Constraint::MenuConstrain(int id) { Constraint c; ZERO(&c); c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { case GraphicsWindow::MNU_DISTANCE_DIA: { if(gs.points == 2 && gs.n == 2) { c.type = PT_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.n == 1) { c.type = PT_PT_DISTANCE; Entity *e = SK.GetEntity(gs.entity[0]); c.ptA = e->point[0]; c.ptB = e->point[1]; } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) { c.type = PROJ_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.vector[0]; } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_PLANE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_LINE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_FACE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else if(gs.circlesOrArcs == 1 && gs.n == 1) { c.type = DIAMETER; c.entityA = gs.entity[0]; } else { Error( "Bad selection for distance / diameter constraint. This " "constraint can apply to:\n\n" " * two points (distance between points)\n" " * a line segment (length)\n" " * two points and a line segment or normal (projected distance)\n" " * a workplane and a point (minimum distance)\n" " * a line segment and a point (minimum distance)\n" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n"); return; } if(c.type == PT_PT_DISTANCE || c.type == PROJ_PT_DISTANCE) { Vector n = SS.GW.projRight.Cross(SS.GW.projUp); Vector a = SK.GetEntity(c.ptA)->PointGetNum(); Vector b = SK.GetEntity(c.ptB)->PointGetNum(); c.disp.offset = n.Cross(a.Minus(b)); c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale); } else { c.disp.offset = Vector::From(0, 0, 0); } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; } case GraphicsWindow::MNU_ON_ENTITY: if(gs.points == 2 && gs.n == 2) { c.type = POINTS_COINCIDENT; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = PT_IN_PLANE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { c.type = PT_ON_CIRCLE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) { c.type = PT_ON_FACE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else { Error("Bad selection for on point / curve / plane constraint. " "This constraint can apply to:\n\n" " * two points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" " * a point and a plane face (point on face)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_EQUAL: if(gs.lineSegments == 2 && gs.n == 2) { c.type = EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) { c.type = EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[1]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { // The same line segment for the distances, but different // points. c.type = EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) { c.type = EQ_LEN_PT_LINE_D; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; c.ptA = gs.point[0]; } else if(gs.vectors == 4 && gs.n == 4) { c.type = EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[2]; c.entityD = gs.vector[3]; } else if(gs.vectors == 3 && gs.n == 3) { c.type = EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[1]; c.entityD = gs.vector[2]; } else if(gs.circlesOrArcs == 2 && gs.n == 2) { c.type = EQUAL_RADIUS; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = EQUAL_LINE_ARC_LEN; if(SK.GetEntity(gs.entity[0])->type == Entity::ARC_OF_CIRCLE) { c.entityA = gs.entity[1]; c.entityB = gs.entity[0]; } else { c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } } else { Error("Bad selection for equal length / radius constraint. " "This constraint can apply to:\n\n" " * two line segments (equal length)\n" " * two line segments and two points " "(equal point-line distances)\n" " * a line segment and two points " "(equal point-line distances)\n" " * a line segment, and a point and line segment " "(point-line distance equals length)\n" " * four line segments or normals " "(equal angle between A,B and C,D)\n" " * three line segments or normals " "(equal angle between A,B and B,C)\n" " * two circles or arcs (equal radius)\n" " * a line segment and an arc " "(line segment length equals arc length)\n"); return; } if(c.type == EQUAL_ANGLE) { // Infer the nearest supplementary angle from the sketch. Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), b1 = SK.GetEntity(c.entityB)->VectorGetNum(), a2 = SK.GetEntity(c.entityC)->VectorGetNum(), b2 = SK.GetEntity(c.entityD)->VectorGetNum(); double d1 = a1.Dot(b1), d2 = a2.Dot(b2); if(d1*d2 < 0) { c.other = true; } } AddConstraint(&c); break; case GraphicsWindow::MNU_RATIO: if(gs.lineSegments == 2 && gs.n == 2) { c.type = LENGTH_RATIO; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { Error("Bad selection for length ratio constraint. This " "constraint can apply to:\n\n" " * two line segments\n"); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; case GraphicsWindow::MNU_AT_MIDPOINT: if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = AT_MIDPOINT; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; // If a point is at-midpoint, then no reason to also constrain // it on-line; so auto-remove that. DeleteAllConstraintsFor(PT_ON_LINE, c.entityA, c.ptA); } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = AT_MIDPOINT; int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; c.entityA = gs.entity[i]; c.entityB = gs.entity[1-i]; } else { Error("Bad selection for at midpoint constraint. This " "constraint can apply to:\n\n" " * a line segment and a point " "(point at midpoint)\n" " * a line segment and a workplane " "(line's midpoint on plane)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_SYMMETRIC: if(gs.points == 2 && ((gs.workplanes == 1 && gs.n == 3) || (gs.n == 2))) { c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && ((gs.workplanes == 1 && gs.n == 2) || (gs.n == 1))) { int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; Entity *line = SK.GetEntity(gs.entity[i]); c.entityA = gs.entity[1-i]; c.ptA = line->point[0]; c.ptB = line->point[1]; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 2 && gs.n == 2) { Entity *l0 = SK.GetEntity(gs.entity[0]), *l1 = SK.GetEntity(gs.entity[1]); if((l1->group.v != SS.GW.activeGroup.v) || (l1->construction && !(l0->construction))) { SWAP(Entity *, l0, l1); } c.ptA = l1->point[0]; c.ptB = l1->point[1]; c.entityA = l0->h; c.type = SYMMETRIC_LINE; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.entity[0]; c.type = SYMMETRIC_LINE; } else { Error("Bad selection for symmetric constraint. This constraint " "can apply to:\n\n" " * two points or a line segment " "(symmetric about workplane's coordinate axis)\n" " * line segment, and two points or a line segment " "(symmetric about line segment)\n" " * workplane, and two points or a line segment " "(symmetric about workplane)\n"); return; } if(c.type != 0) { // Already done, symmetry about a line segment in a workplane } else if(c.entityA.v == Entity::NO_ENTITY.v) { // Horizontal / vertical symmetry, implicit symmetry plane // normal to the workplane if(c.workplane.v == Entity::FREE_IN_3D.v) { Error("Must be locked in to workplane when constraining " "symmetric without an explicit symmetry plane."); return; } Vector pa = SK.GetEntity(c.ptA)->PointGetNum(); Vector pb = SK.GetEntity(c.ptB)->PointGetNum(); Vector dp = pa.Minus(pb); EntityBase *norm = SK.GetEntity(c.workplane)->Normal();; Vector u = norm->NormalU(), v = norm->NormalV(); if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) { c.type = SYMMETRIC_HORIZ; } else { c.type = SYMMETRIC_VERT; } if(gs.lineSegments == 1) { // If this line segment is already constrained horiz or // vert, then auto-remove that redundant constraint. DeleteAllConstraintsFor(HORIZONTAL, (gs.entity[0]), Entity::NO_ENTITY); DeleteAllConstraintsFor(VERTICAL, (gs.entity[0]), Entity::NO_ENTITY); } } else { // Symmetry with a symmetry plane specified explicitly. c.type = SYMMETRIC; } AddConstraint(&c); break; case GraphicsWindow::MNU_VERTICAL: case GraphicsWindow::MNU_HORIZONTAL: { hEntity ha, hb; if(c.workplane.v == Entity::FREE_IN_3D.v) { Error("Select workplane before constraining horiz/vert."); return; } if(gs.lineSegments == 1 && gs.n == 1) { c.entityA = gs.entity[0]; Entity *e = SK.GetEntity(c.entityA); ha = e->point[0]; hb = e->point[1]; } else if(gs.points == 2 && gs.n == 2) { ha = c.ptA = gs.point[0]; hb = c.ptB = gs.point[1]; } else { Error("Bad selection for horizontal / vertical constraint. " "This constraint can apply to:\n\n" " * two points\n" " * a line segment\n"); return; } if(id == GraphicsWindow::MNU_HORIZONTAL) { c.type = HORIZONTAL; } else { c.type = VERTICAL; } AddConstraint(&c); break; } case GraphicsWindow::MNU_ORIENTED_SAME: { if(gs.anyNormals == 2 && gs.n == 2) { c.type = SAME_ORIENTATION; c.entityA = gs.anyNormal[0]; c.entityB = gs.anyNormal[1]; } else { Error("Bad selection for same orientation constraint. This " "constraint can apply to:\n\n" " * two normals\n"); return; } SS.UndoRemember(); Entity *nfree = SK.GetEntity(c.entityA); Entity *nref = SK.GetEntity(c.entityB); if(nref->group.v == SS.GW.activeGroup.v) { SWAP(Entity *, nref, nfree); } if(nfree->group.v == SS.GW.activeGroup.v && nref ->group.v != SS.GW.activeGroup.v) { // nfree is free, and nref is locked (since it came from a // previous group); so let's force nfree aligned to nref, // and make convergence easy Vector ru = nref ->NormalU(), rv = nref ->NormalV(); Vector fu = nfree->NormalU(), fv = nfree->NormalV(); if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) { // There might be an odd*90 degree rotation about the // normal vector; allow that, since the numerical // constraint does SWAP(Vector, ru, rv); } fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1); fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1); nfree->NormalForceTo(Quaternion::From(fu, fv)); } AddConstraint(&c, false); break; } case GraphicsWindow::MNU_OTHER_ANGLE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->type == ANGLE) { SS.UndoRemember(); c->other = !(c->other); c->ModifyToSatisfy(); break; } if(c->type == EQUAL_ANGLE) { SS.UndoRemember(); c->other = !(c->other); SS.MarkGroupDirty(c->group); SS.ScheduleGenerateAll(); break; } } Error("Must select an angle constraint."); return; case GraphicsWindow::MNU_REFERENCE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != COMMENT) { (c->reference) = !(c->reference); SK.GetGroup(c->group)->clean = false; SS.GenerateAll(); break; } } Error("Must select a constraint with associated label."); return; case GraphicsWindow::MNU_ANGLE: { if(gs.vectors == 2 && gs.n == 2) { c.type = ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.valA = 0; } else { Error("Bad selection for angle constraint. This constraint " "can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n"); return; } Entity *ea = SK.GetEntity(c.entityA), *eb = SK.GetEntity(c.entityB); if(ea->type == Entity::LINE_SEGMENT && eb->type == Entity::LINE_SEGMENT) { Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(), a1 = SK.GetEntity(ea->point[1])->PointGetNum(), b0 = SK.GetEntity(eb->point[0])->PointGetNum(), b1 = SK.GetEntity(eb->point[1])->PointGetNum(); if(a0.Equals(b0) || a1.Equals(b1)) { // okay, vectors should be drawn in same sense } else if(a0.Equals(b1) || a1.Equals(b0)) { // vectors are in opposite sense c.other = true; } else { // no shared point; not clear which intersection to draw } } c.ModifyToSatisfy(); AddConstraint(&c); break; } case GraphicsWindow::MNU_PARALLEL: if(gs.vectors == 2 && gs.n == 2) { c.type = PARALLEL; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *arc = SK.GetEntity(gs.entity[1]); if(line->type == Entity::ARC_OF_CIRCLE) { SWAP(Entity *, line, arc); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(), a2 = SK.GetEntity(arc->point[2])->PointGetNum(); if(l0.Equals(a1) || l1.Equals(a1)) { c.other = false; } else if(l0.Equals(a2) || l1.Equals(a2)) { c.other = true; } else { Error("The tangent arc and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent."); return; } c.type = ARC_LINE_TANGENT; c.entityA = arc->h; c.entityB = line->h; } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *cubic = SK.GetEntity(gs.entity[1]); if(line->type == Entity::CUBIC) { SWAP(Entity *, line, cubic); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector as = cubic->CubicGetStartNum(), af = cubic->CubicGetFinishNum(); if(l0.Equals(as) || l1.Equals(as)) { c.other = false; } else if(l0.Equals(af) || l1.Equals(af)) { c.other = true; } else { Error("The tangent cubic and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent."); return; } c.type = CUBIC_LINE_TANGENT; c.entityA = cubic->h; c.entityB = line->h; } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) { if(!SS.GW.LockedInWorkplane()) { Error("Curve-curve tangency must apply in workplane."); return; } Entity *eA = SK.GetEntity(gs.entity[0]), *eB = SK.GetEntity(gs.entity[1]); Vector as = eA->EndpointStart(), af = eA->EndpointFinish(), bs = eB->EndpointStart(), bf = eB->EndpointFinish(); if(as.Equals(bs)) { c.other = false; c.other2 = false; } else if(as.Equals(bf)) { c.other = false; c.other2 = true; } else if(af.Equals(bs)) { c.other = true; c.other2 = false; } else if(af.Equals(bf)) { c.other = true; c.other2 = true; } else { Error("The curves must share an endpoint. Constrain them " "with Constrain -> On Point before constraining " "tangent."); return; } c.type = CURVE_CURVE_TANGENT; c.entityA = eA->h; c.entityB = eB->h; } else { Error("Bad selection for parallel / tangent constraint. This " "constraint can apply to:\n\n" " * two line segments (parallel)\n" " * a line segment and a normal (parallel)\n" " * two normals (parallel)\n" " * two line segments, arcs, or beziers, that share " "an endpoint (tangent)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_PERPENDICULAR: if(gs.vectors == 2 && gs.n == 2) { c.type = PERPENDICULAR; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else { Error("Bad selection for perpendicular constraint. This " "constraint can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_WHERE_DRAGGED: if(gs.points == 1 && gs.n == 1) { c.type = WHERE_DRAGGED; c.ptA = gs.point[0]; } else { Error("Bad selection for lock point where dragged constraint. " "This constraint can apply to:\n\n" " * a point\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_COMMENT: SS.GW.pending.operation = GraphicsWindow::MNU_COMMENT; SS.GW.pending.description = "click center of comment text"; SS.ScheduleShowTW(); break; default: oops(); } SS.GW.ClearSelection(); InvalidateGraphics(); }