Пример #1
0
//-----------------------------------------------------------------------------
// 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();
    }
}
Пример #2
0
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();
}
Пример #3
0
//-----------------------------------------------------------------------------
// A single point must be selected when this function is called. We find two
// non-construction line segments that join at this point, and create a
// tangent arc joining them.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeTangentArc(void) {
    if(!LockedInWorkplane()) {
        Error("Must be sketching in workplane to create tangent "
              "arc.");
        return;
    }

    // The point corresponding to the vertex to be rounded.
    Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum();
    ClearSelection();

    // First, find two requests (that are not construction, and that are
    // in our group and workplane) that generate entities that have an
    // endpoint at our vertex to be rounded.
    int i, c = 0;
    Entity *ent[2];
    Request *req[2];
    hRequest hreq[2];
    hEntity hent[2];
    bool pointf[2];
    for(i = 0; i < SK.request.n; i++) {
        Request *r = &(SK.request.elem[i]);
        if(r->group.v != activeGroup.v) continue;
        if(r->workplane.v != ActiveWorkplane().v) continue;
        if(r->construction) continue;
        if(r->type != Request::LINE_SEGMENT &&
           r->type != Request::ARC_OF_CIRCLE)
        {
            continue;
        }

        Entity *e = SK.GetEntity(r->h.entity(0));
        Vector ps = e->EndpointStart(),
               pf = e->EndpointFinish();
        
        if(ps.Equals(pshared) || pf.Equals(pshared)) {
            if(c < 2) {
                // We record the entity and request and their handles,
                // and whether the vertex to be rounded is the start or
                // finish of this entity.
                ent[c] = e;
                hent[c] = e->h;
                req[c] = r;
                hreq[c] = r->h;
                pointf[c] = (pf.Equals(pshared));
            }
            c++;
        }
    }
    if(c != 2) {
        Error("To create a tangent arc, select a point where two "
              "non-construction lines or cicles in this group and "
              "workplane join.");
        return;
    }

    Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
    Vector wn = wrkpl->Normal()->NormalN();

    // Based on these two entities, we make the objects that we'll use to
    // numerically find the tangent arc.
    ParametricCurve pc[2];
    pc[0].MakeFromEntity(ent[0]->h, pointf[0]);
    pc[1].MakeFromEntity(ent[1]->h, pointf[1]);

    // And thereafter we mustn't touch the entity or req ptrs,
    // because the new requests/entities we add might force a
    // realloc.
    memset(ent, 0, sizeof(ent));
    memset(req, 0, sizeof(req));

    Vector pinter;
    double r, vv;
    // We now do Newton iterations to find the tangent arc, and its positions
    // t back along the two curves, starting from shared point of the curves
    // at t = 0. Lots of iterations helps convergence, and this is still
    // ~10 ms for everything.
    int iters = 1000;
    double t[2] = { 0, 0 }, tp[2];
    for(i = 0; i < iters + 20; i++) {
        Vector p0 = pc[0].PointAt(t[0]),
               p1 = pc[1].PointAt(t[1]),
               t0 = pc[0].TangentAt(t[0]),
               t1 = pc[1].TangentAt(t[1]);

        pinter = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
                                               p1, p1.Plus(t1),
                                               NULL, NULL, NULL);

        // The sign of vv determines whether shortest distance is
        // clockwise or anti-clockwise.
        Vector v = (wn.Cross(t0)).WithMagnitude(1);
        vv = t1.Dot(v);

        double dot = (t0.WithMagnitude(1)).Dot(t1.WithMagnitude(1));
        double theta = acos(dot);

        if(SS.tangentArcManual) {
            r = SS.tangentArcRadius;
        } else {
            r = 200/scale;
            // Set the radius so that no more than one third of the 
            // line segment disappears.
            r = min(r, pc[0].LengthForAuto()*tan(theta/2));
            r = min(r, pc[1].LengthForAuto()*tan(theta/2));;
        }
        // We are source-stepping the radius, to improve convergence. So
        // ramp that for most of the iterations, and then do a few at
        // the end with that constant for polishing.
        if(i < iters) {
            r *= 0.1 + 0.9*i/((double)iters);
        }

        // The distance from the intersection of the lines to the endpoint
        // of the arc, along each line.
        double el = r/tan(theta/2);

        // Compute the endpoints of the arc, for each curve
        Vector pa0 = pinter.Plus(t0.WithMagnitude(el)),
               pa1 = pinter.Plus(t1.WithMagnitude(el));

        tp[0] = t[0];
        tp[1] = t[1];

        // And convert those points to parameter values along the curve.
        t[0] += (pa0.Minus(p0)).DivPivoting(t0);
        t[1] += (pa1.Minus(p1)).DivPivoting(t1);
    }

    // Stupid check for convergence, and for an out of range result (as
    // we would get, for example, if the line is too short to fit the
    // rounding arc).
    if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 ||
        t[0] < 0.01 || t[1] < 0.01 ||
        t[0] > 0.99 || t[1] > 0.99 ||
        isnan(t[0]) || isnan(t[1]))
    {
        Error("Couldn't round this corner. Try a smaller radius, or try "
              "creating the desired geometry by hand with tangency "
              "constraints.");
        return;
    }

    // Compute the location of the center of the arc
    Vector center = pc[0].PointAt(t[0]),
           v0inter = pinter.Minus(center);
    int a, b;
    if(vv < 0) {
        a = 1; b = 2;
        center = center.Minus(v0inter.Cross(wn).WithMagnitude(r));
    } else {
        a = 2; b = 1;
        center = center.Plus(v0inter.Cross(wn).WithMagnitude(r));
    }

    SS.UndoRemember();

    hRequest harc = AddRequest(Request::ARC_OF_CIRCLE, false);
    Entity *earc = SK.GetEntity(harc.entity(0));
    hEntity hearc = earc->h;

    SK.GetEntity(earc->point[0])->PointForceTo(center);
    SK.GetEntity(earc->point[a])->PointForceTo(pc[0].PointAt(t[0]));
    SK.GetEntity(earc->point[b])->PointForceTo(pc[1].PointAt(t[1]));
    
    earc = NULL;

    pc[0].CreateRequestTrimmedTo(t[0], !SS.tangentArcDeleteOld,
                hent[0], hearc, (b == 1));
    pc[1].CreateRequestTrimmedTo(t[1], !SS.tangentArcDeleteOld,
                hent[1], hearc, (a == 1));

    // Now either make the original entities construction, or delete them
    // entirely, according to user preference.
    Request *re;
    SK.request.ClearTags();
    for(re = SK.request.First(); re; re = SK.request.NextAfter(re)) {
        if(re->h.v == hreq[0].v || re->h.v == hreq[1].v) {
            if(SS.tangentArcDeleteOld) {
                re->tag = 1;
            } else {
                re->construction = true;
            }
        }
    }
    if(SS.tangentArcDeleteOld) {
        DeleteTaggedRequests();
    }

    SS.later.generateAll = true;
}
Пример #4
0
void GraphicsWindow::MenuEdit(int id) {
    switch(id) {
        case MNU_UNSELECT_ALL:
            SS.GW.GroupSelection();
            // If there's nothing selected to de-select, and no operation
            // to cancel, then perhaps they want to return to the home
            // screen in the text window.
            if(SS.GW.gs.n               == 0 &&
               SS.GW.gs.constraints     == 0 &&
               SS.GW.pending.operation  == 0)
            {
                if(!(TextEditControlIsVisible() ||
                     GraphicsEditControlIsVisible()))
                {
                    if(SS.TW.shown.screen == TextWindow::SCREEN_STYLE_INFO) {
                        SS.TW.GoToScreen(TextWindow::SCREEN_LIST_OF_STYLES);
                    } else {
                        SS.TW.ClearSuper();
                    }
                }
            }
            SS.GW.ClearSuper();
            SS.TW.HideEditControl();
            SS.nakedEdges.Clear();
            SS.justExportedInfo.draw = false;
            // This clears the marks drawn to indicate which points are
            // still free to drag.
            Param *p;
            for(p = SK.param.First(); p; p = SK.param.NextAfter(p)) {
                p->free = false;
            }
            if(SS.exportMode) {
                SS.exportMode = false;
                SS.GenerateAll(SolveSpaceUI::GENERATE_ALL);
            }
            break;

        case MNU_SELECT_ALL: {
            Entity *e;
            for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
                if(e->group.v != SS.GW.activeGroup.v) continue;
                if(e->IsFace() || e->IsDistance()) continue;
                if(!e->IsVisible()) continue;

                SS.GW.MakeSelected(e->h);
            }
            InvalidateGraphics();
            SS.ScheduleShowTW();
            break;
        }

        case MNU_SELECT_CHAIN: {
            Entity *e;
            int newlySelected = 0;
            bool didSomething;
            do {
                didSomething = false;
                for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
                    if(e->group.v != SS.GW.activeGroup.v) continue;
                    if(!e->HasEndpoints()) continue;
                    if(!e->IsVisible()) continue;

                    Vector st = e->EndpointStart(),
                           fi = e->EndpointFinish();

                    bool onChain = false, alreadySelected = false;
                    List<Selection> *ls = &(SS.GW.selection);
                    for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
                        if(!s->entity.v) continue;
                        if(s->entity.v == e->h.v) {
                            alreadySelected = true;
                            continue;
                        }
                        Entity *se = SK.GetEntity(s->entity);
                        if(!se->HasEndpoints()) continue;

                        Vector sst = se->EndpointStart(),
                               sfi = se->EndpointFinish();

                        if(sst.Equals(st) || sst.Equals(fi) ||
                           sfi.Equals(st) || sfi.Equals(fi))
                        {
                            onChain = true;
                        }
                    }
                    if(onChain && !alreadySelected) {
                        SS.GW.MakeSelected(e->h);
                        newlySelected++;
                        didSomething = true;
                    }
                }
            } while(didSomething);
            if(newlySelected == 0) {
                Error("No additional entities share endpoints with the "
                      "selected entities.");
            }
            InvalidateGraphics();
            SS.ScheduleShowTW();
            break;
        }

        case MNU_ROTATE_90: {
            SS.GW.GroupSelection();
            Entity *e = NULL;
            if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) {
                e = SK.GetEntity(SS.GW.gs.point[0]);
            } else if(SS.GW.gs.n == 1 && SS.GW.gs.entities == 1) {
                e = SK.GetEntity(SS.GW.gs.entity[0]);
            }
            SS.GW.ClearSelection();

            hGroup hg = e ? e->group : SS.GW.activeGroup;
            Group *g = SK.GetGroup(hg);
            if(g->type != Group::IMPORTED) {
                Error("To use this command, select a point or other "
                      "entity from an imported part, or make an import "
                      "group the active group.");
                break;
            }


            SS.UndoRemember();
            // Rotate by ninety degrees about the coordinate axis closest
            // to the screen normal.
            Vector norm = SS.GW.projRight.Cross(SS.GW.projUp);
            norm = norm.ClosestOrtho();
            norm = norm.WithMagnitude(1);
            Quaternion qaa = Quaternion::From(norm, PI/2);

            g->TransformImportedBy(Vector::From(0, 0, 0), qaa);

            // and regenerate as necessary.
            SS.MarkGroupDirty(hg);
            SS.GenerateAll();
            break;
        }

        case MNU_SNAP_TO_GRID: {
            if(!SS.GW.LockedInWorkplane()) {
                Error("No workplane is active. Select a workplane to define "
                      "the plane for the snap grid.");
                break;
            }
            SS.GW.GroupSelection();
            if(SS.GW.gs.points == 0 && SS.GW.gs.constraintLabels == 0) {
                Error("Can't snap these items to grid; select points, "
                      "text comments, or constraints with a label. "
                      "To snap a line, select its endpoints.");
                break;
            }
            SS.UndoRemember();

            List<Selection> *ls = &(SS.GW.selection);
            for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
                if(s->entity.v) {
                    hEntity hp = s->entity;
                    Entity *ep = SK.GetEntity(hp);
                    if(!ep->IsPoint()) continue;

                    Vector p = ep->PointGetNum();
                    ep->PointForceTo(SS.GW.SnapToGrid(p));
                    SS.GW.pending.points.Add(&hp);
                    SS.MarkGroupDirty(ep->group);
                } else if(s->constraint.v) {
                    Constraint *c = SK.GetConstraint(s->constraint);
                    c->disp.offset = SS.GW.SnapToGrid(c->disp.offset);
                }
            }
            // Regenerate, with these points marked as dragged so that they
            // get placed as close as possible to our snap grid.
            SS.GenerateAll();
            SS.GW.ClearPending();

            SS.GW.ClearSelection();
            InvalidateGraphics();
            break;
        }

        case MNU_UNDO:
            SS.UndoUndo();
            break;

        case MNU_REDO:
            SS.UndoRedo();
            break;

        case MNU_REGEN_ALL:
            SS.ReloadAllImported();
            SS.GenerateAll(SolveSpaceUI::GENERATE_UNTIL_ACTIVE);
            SS.ScheduleShowTW();
            break;

        default: oops();
    }
}