diff --git a/constraint.cpp b/constraint.cpp index 5f791b4..7d10794 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -110,6 +110,10 @@ void Constraint::MenuConstrain(int id) { c.type = EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; + } else if(gs.circlesOrArcs == 2 && gs.n == 2) { + c.type = EQUAL_RADIUS; + c.entityA = gs.entity[0]; + c.entityB = gs.entity[1]; } else { Error("Bad selection for equal length / radius constraint."); return; @@ -406,11 +410,19 @@ void Constraint::Generate(IdList *l) { case DIAMETER: { Entity *circle = SS.GetEntity(entityA); - Expr *r = (SS.GetEntity(circle->distance))->DistanceGetExpr(); + Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, (r->Times(Expr::FromConstant(2)))->Minus(exprA), 0); break; } + case EQUAL_RADIUS: { + Entity *c1 = SS.GetEntity(entityA); + Entity *c2 = SS.GetEntity(entityB); + AddEq(l, (c1->CircleGetRadiusExpr())->Minus( + c2->CircleGetRadiusExpr()), 0); + break; + } + case POINTS_COINCIDENT: { Entity *a = SS.GetEntity(ptA); Entity *b = SS.GetEntity(ptB); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index ccba8af..396049b 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -171,7 +171,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { case DIAMETER: { Entity *circle = SS.GetEntity(entityA); Vector center = SS.GetEntity(circle->point[0])->PointGetNum(); - double r = SS.GetEntity(circle->distance)->DistanceGetNum(); + double r = circle->CircleGetRadiusNum(); Vector ref = center.Plus(disp.offset); double theta = atan2(disp.offset.Dot(gu), disp.offset.Dot(gr)); @@ -261,6 +261,32 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { break; } + case EQUAL_RADIUS: { + for(int i = 0; i < 2; i++) { + Entity *circ = SS.GetEntity(i == 0 ? entityA : entityB); + Vector center = SS.GetEntity(circ->point[0])->PointGetNum(); + double r = circ->CircleGetRadiusNum(); + Quaternion q = circ->Normal()->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + double theta; + if(circ->type == Entity::CIRCLE) { + theta = PI/2; + } else if(circ->type == Entity::ARC_OF_CIRCLE) { + double thetaa, thetab, dtheta; + circ->ArcGetAngles(&thetaa, &thetab, &dtheta); + theta = thetaa + dtheta/2; + } else oops(); + + Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); + d = d.ScaledBy(r); + Vector p = center.Plus(d); + Vector tick = d.WithMagnitude(10/SS.GW.scale); + LineDrawOrGetDistance(p.Plus(tick), p.Minus(tick)); + } + break; + } + case LENGTH_RATIO: case EQUAL_LENGTH_LINES: { Vector a, b; diff --git a/dsc.h b/dsc.h index eabb57b..df30bbe 100644 --- a/dsc.h +++ b/dsc.h @@ -53,6 +53,7 @@ public: Vector ProjectInto(hEntity wrkpl); double DivPivoting(Vector delta); Vector ClosestOrtho(void); + Point2d Project2d(Vector u, Vector v); }; class Point2d { diff --git a/entity.cpp b/entity.cpp index 4306341..4ea3cf9 100644 --- a/entity.cpp +++ b/entity.cpp @@ -76,6 +76,45 @@ bool Entity::IsCircle(void) { return (type == CIRCLE); } +Expr *Entity::CircleGetRadiusExpr(void) { + if(type == CIRCLE) { + return SS.GetEntity(distance)->DistanceGetExpr(); + } else if(type == ARC_OF_CIRCLE) { + return Constraint::Distance(workplane, point[0], point[1]); + } else oops(); +} + +double Entity::CircleGetRadiusNum(void) { + if(type == CIRCLE) { + return SS.GetEntity(distance)->DistanceGetNum(); + } else if(type == ARC_OF_CIRCLE) { + Vector c = SS.GetEntity(point[0])->PointGetNum(); + Vector pa = SS.GetEntity(point[1])->PointGetNum(); + return (pa.Minus(c)).Magnitude(); + } else oops(); +} + +void Entity::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) { + if(type != ARC_OF_CIRCLE) oops(); + + Quaternion q = Normal()->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + Vector c = SS.GetEntity(point[0])->PointGetNum(); + Vector pa = SS.GetEntity(point[1])->PointGetNum(); + Vector pb = SS.GetEntity(point[2])->PointGetNum(); + + Point2d c2 = c.Project2d(u, v); + Point2d pa2 = (pa.Project2d(u, v)).Minus(c2); + Point2d pb2 = (pb.Project2d(u, v)).Minus(c2); + + *thetaa = atan2(pa2.y, pa2.x); + *thetab = atan2(pb2.y, pb2.x); + *dtheta = *thetab - *thetaa; + while(*dtheta < 0) *dtheta += 2*PI; + while(*dtheta > (2*PI)) *dtheta -= 2*PI; +} + bool Entity::IsWorkplane(void) { return (type == WORKPLANE); } @@ -685,6 +724,33 @@ void Entity::DrawOrGetDistance(int order) { break; } + case ARC_OF_CIRCLE: { + if(order >= 0 && order != 1) break; + Vector c = SS.GetEntity(point[0])->PointGetNum(); + Vector pa = SS.GetEntity(point[1])->PointGetNum(); + Vector pb = SS.GetEntity(point[2])->PointGetNum(); + Quaternion q = SS.GetEntity(normal)->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + double ra = (pa.Minus(c)).Magnitude(); + double rb = (pb.Minus(c)).Magnitude(); + + double thetaa, thetab, dtheta; + ArcGetAngles(&thetaa, &thetab, &dtheta); + + int i, n = (int)((40*dtheta)/(2*PI)); + Vector prev = pa; + for(i = 1; i <= n; i++) { + double theta = thetaa + (dtheta*i)/n; + double r = ra + ((rb - ra)*i)/n; + Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); + Vector p = c.Plus(d.ScaledBy(r)); + LineDrawOrGetDistanceOrEdge(prev, p); + prev = p; + } + break; + } + case CIRCLE: { if(order >= 0 && order != 1) break; @@ -725,6 +791,17 @@ void Entity::GenerateEquations(IdList *l) { AddEq(l, (q.Magnitude())->Minus(Expr::FromConstant(1)), 0); break; } + case ARC_OF_CIRCLE: { + // If this is a copied entity, with its point already fixed + // with respect to each other, then we don't want to generate + // the distance constraint! + if(SS.GetEntity(point[0])->type == POINT_IN_2D) { + Expr *ra = Constraint::Distance(workplane, point[0], point[1]); + Expr *rb = Constraint::Distance(workplane, point[0], point[2]); + AddEq(l, ra->Minus(rb), 0); + } + break; + } default:; // Most entities do not generate equations. } diff --git a/graphicswin.cpp b/graphicswin.cpp index 4739f7a..6f6d59e 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -60,7 +60,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Line &Segment\tS", MNU_LINE_SEGMENT, 'S', mReq }, { 1, "&Rectangle\tR", MNU_RECTANGLE, 'R', mReq }, { 1, "&Circle\tC", MNU_CIRCLE, 'C', mReq }, -{ 1, "&Arc of a Circle\tA", 0, 'A', mReq }, +{ 1, "&Arc of a Circle\tA", MNU_ARC, 'A', mReq }, { 1, "&Cubic Segment\t3", MNU_CUBIC, '3', mReq }, { 1, NULL, 0, NULL }, { 1, "Sym&bolic Variable\tB", 0, 'B', mReq }, @@ -350,6 +350,7 @@ void GraphicsWindow::MenuRequest(int id) { case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c; case MNU_CUBIC: s = "click first point of cubic segment"; goto c; case MNU_CIRCLE: s = "click center of circle"; goto c; + case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c; case MNU_WORKPLANE: s = "click origin of workplane"; goto c; case MNU_RECTANGLE: s = "click one corner of rectangular"; goto c; c: @@ -540,6 +541,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, orig.mouse = mp; } else { UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); } break; } @@ -556,6 +558,18 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SS.GetEntity(hr.entity(3))->PointForceTo(p2); break; } + case DRAGGING_NEW_ARC_POINT: { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + + hRequest hr = pending.point.request(); + Vector ona = SS.GetEntity(hr.entity(2))->PointGetNum(); + Vector onb = SS.GetEntity(hr.entity(3))->PointGetNum(); + Vector center = (ona.Plus(onb)).ScaledBy(0.5); + + SS.GetEntity(hr.entity(1))->PointForceTo(center); + break; + } case DRAGGING_NEW_RADIUS: case DRAGGING_RADIUS: { Entity *circle = SS.GetEntity(pending.circle); @@ -699,6 +713,8 @@ void GraphicsWindow::GroupSelection(void) { switch(e->type) { case Entity::WORKPLANE: (gs.workplanes)++; break; case Entity::LINE_SEGMENT: (gs.lineSegments)++; break; + + case Entity::ARC_OF_CIRCLE: case Entity::CIRCLE: (gs.circlesOrArcs)++; break; } } @@ -814,6 +830,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { Entity::NO_ENTITY, Entity::NO_ENTITY, lns[i].entity(0)); } + ConstrainPointByHovered(lns[2].entity(1)); pending.operation = DRAGGING_NEW_POINT; pending.point = lns[1].entity(2); @@ -836,6 +853,26 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetParam(hr.param(0))->val = 0; break; + case MNU_ARC: + if(SS.GW.activeWorkplane.v == Entity::FREE_IN_3D.v) { + Error("Can't draw arc in 3d; select a workplane first."); + ClearPending(); + break; + } + hr = AddRequest(Request::ARC_OF_CIRCLE); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(2))->PointForceTo(v); + SS.GetEntity(hr.entity(3))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(2)); + + ClearSelection(); hover.Clear(); + + ClearPending(); + pending.operation = DRAGGING_NEW_ARC_POINT; + pending.point = hr.entity(3); + pending.description = "click to place point"; + break; + case MNU_CUBIC: hr = AddRequest(Request::CUBIC); SS.GetEntity(hr.entity(1))->PointForceTo(v); @@ -868,6 +905,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { ClearPending(); break; + case DRAGGING_NEW_ARC_POINT: case DRAGGING_NEW_CUBIC_POINT: ConstrainPointByHovered(pending.point); ClearPending(); diff --git a/sketch.cpp b/sketch.cpp index 3003deb..b3aaf7a 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -273,6 +273,13 @@ void Group::CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz, en.distance = Remap(ep->distance, a); break; + case Entity::ARC_OF_CIRCLE: + en.point[0] = Remap(ep->point[0], a); + en.point[1] = Remap(ep->point[1], a); + en.point[2] = Remap(ep->point[2], a); + en.normal = Remap(ep->normal, a); + break; + case Entity::POINT_N_COPY: case Entity::POINT_N_TRANS: case Entity::POINT_N_ROT_TRANS: @@ -511,6 +518,12 @@ void Request::Generate(IdList *entity, hasDistance = true; break; + case Request::ARC_OF_CIRCLE: + et = Entity::ARC_OF_CIRCLE; + points = 3; + hasNormal = true; + break; + case Request::CUBIC: et = Entity::CUBIC; points = 4; diff --git a/sketch.h b/sketch.h index f9c7a58..618c3b0 100644 --- a/sketch.h +++ b/sketch.h @@ -153,6 +153,7 @@ public: static const int LINE_SEGMENT = 200; static const int CUBIC = 300; static const int CIRCLE = 400; + static const int ARC_OF_CIRCLE = 500; int type; @@ -200,6 +201,7 @@ public: static const int LINE_SEGMENT = 11000; static const int CUBIC = 12000; static const int CIRCLE = 13000; + static const int ARC_OF_CIRCLE = 14000; int type; @@ -227,6 +229,9 @@ public: int timesApplied; bool IsCircle(void); + Expr *CircleGetRadiusExpr(void); + double CircleGetRadiusNum(void); + void ArcGetAngles(double *thetaa, double *thetab, double *dtheta); bool HasVector(void); ExprVector VectorGetExprs(void); @@ -333,6 +338,7 @@ public: static const int PT_ON_CIRCLE = 100; static const int SAME_ORIENTATION = 110; static const int PARALLEL = 120; + static const int EQUAL_RADIUS = 130; int tag; hConstraint h; diff --git a/system.cpp b/system.cpp index 419a6ee..9c5d0ef 100644 --- a/system.cpp +++ b/system.cpp @@ -344,7 +344,7 @@ bool System::NewtonSolve(int tag) { } bool System::Solve(void) { - int i, j; + int i, j = 0; /* dbp("%d equations", eq.n); diff --git a/ui.h b/ui.h index 1181f54..f4920d1 100644 --- a/ui.h +++ b/ui.h @@ -105,6 +105,7 @@ public: MNU_WORKPLANE, MNU_LINE_SEGMENT, MNU_CIRCLE, + MNU_ARC, MNU_RECTANGLE, MNU_CUBIC, MNU_CONSTRUCTION, @@ -184,10 +185,11 @@ public: static const int DRAGGING_NEW_POINT = 0x0f000001; static const int DRAGGING_NEW_LINE_POINT = 0x0f000002; static const int DRAGGING_NEW_CUBIC_POINT = 0x0f000003; - static const int DRAGGING_CONSTRAINT = 0x0f000004; - static const int DRAGGING_RADIUS = 0x0f000005; - static const int DRAGGING_NORMAL = 0x0f000006; - static const int DRAGGING_NEW_RADIUS = 0x0f000007; + static const int DRAGGING_NEW_ARC_POINT = 0x0f000004; + static const int DRAGGING_CONSTRAINT = 0x0f000005; + static const int DRAGGING_RADIUS = 0x0f000006; + static const int DRAGGING_NORMAL = 0x0f000007; + static const int DRAGGING_NEW_RADIUS = 0x0f000008; struct { int operation; diff --git a/util.cpp b/util.cpp index 99992d7..7b532f6 100644 --- a/util.cpp +++ b/util.cpp @@ -298,6 +298,13 @@ Vector Vector::ProjectInto(hEntity wrkpl) { return p0.Plus((u.ScaledBy(up)).Plus(v.ScaledBy(vp))); } +Point2d Vector::Project2d(Vector u, Vector v) { + Point2d p; + p.x = this->Dot(u); + p.y = this->Dot(v); + return p; +} + double Vector::DivPivoting(Vector delta) { double m = max(fabs(delta.x), max(fabs(delta.y), fabs(delta.z)));