410 lines
15 KiB
C++
410 lines
15 KiB
C++
|
#include "solvespace.h"
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Replace a point-coincident constraint on oldpt with that same constraint
|
||
|
// on newpt. Useful when splitting or tangent arcing.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {
|
||
|
int i;
|
||
|
for(i = 0; i < SS.constraint.n; i++) {
|
||
|
Constraint *c = &(SS.constraint.elem[i]);
|
||
|
|
||
|
if(c->type == Constraint::POINTS_COINCIDENT) {
|
||
|
if(c->ptA.v == oldpt.v) c->ptA = newpt;
|
||
|
if(c->ptB.v == oldpt.v) c->ptB = newpt;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
// Find two line segments that join at the specified point,
|
||
|
// and blend them with a tangent arc. First, find the
|
||
|
// requests that generate the line segments.
|
||
|
Vector pshared = SS.GetEntity(gs.point[0])->PointGetNum();
|
||
|
ClearSelection();
|
||
|
|
||
|
int i, c = 0;
|
||
|
Entity *line[2];
|
||
|
Request *lineReq[2];
|
||
|
bool point1[2];
|
||
|
for(i = 0; i < SS.request.n; i++) {
|
||
|
Request *r = &(SS.request.elem[i]);
|
||
|
if(r->group.v != activeGroup.v) continue;
|
||
|
if(r->type != Request::LINE_SEGMENT) continue;
|
||
|
if(r->construction) continue;
|
||
|
|
||
|
Entity *e = SS.GetEntity(r->h.entity(0));
|
||
|
Vector p0 = SS.GetEntity(e->point[0])->PointGetNum(),
|
||
|
p1 = SS.GetEntity(e->point[1])->PointGetNum();
|
||
|
|
||
|
if(p0.Equals(pshared) || p1.Equals(pshared)) {
|
||
|
if(c < 2) {
|
||
|
line[c] = e;
|
||
|
lineReq[c] = r;
|
||
|
point1[c] = (p1.Equals(pshared));
|
||
|
}
|
||
|
c++;
|
||
|
}
|
||
|
}
|
||
|
if(c != 2) {
|
||
|
Error("To create a tangent arc, select a point where "
|
||
|
"two non-construction line segments join.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SS.UndoRemember();
|
||
|
|
||
|
Entity *wrkpl = SS.GetEntity(ActiveWorkplane());
|
||
|
Vector wn = wrkpl->Normal()->NormalN();
|
||
|
|
||
|
hEntity hshared = (line[0])->point[point1[0] ? 1 : 0],
|
||
|
hother0 = (line[0])->point[point1[0] ? 0 : 1],
|
||
|
hother1 = (line[1])->point[point1[1] ? 0 : 1];
|
||
|
|
||
|
Vector pother0 = SS.GetEntity(hother0)->PointGetNum(),
|
||
|
pother1 = SS.GetEntity(hother1)->PointGetNum();
|
||
|
|
||
|
Vector v0shared = pshared.Minus(pother0),
|
||
|
v1shared = pshared.Minus(pother1);
|
||
|
|
||
|
hEntity srcline0 = (line[0])->h,
|
||
|
srcline1 = (line[1])->h;
|
||
|
|
||
|
(lineReq[0])->construction = true;
|
||
|
(lineReq[1])->construction = true;
|
||
|
|
||
|
// And thereafter we mustn't touch the entity or req ptrs,
|
||
|
// because the new requests/entities we add might force a
|
||
|
// realloc.
|
||
|
memset(line, 0, sizeof(line));
|
||
|
memset(lineReq, 0, sizeof(lineReq));
|
||
|
|
||
|
// The sign of vv determines whether shortest distance is
|
||
|
// clockwise or anti-clockwise.
|
||
|
Vector v = (wn.Cross(v0shared)).WithMagnitude(1);
|
||
|
double vv = v1shared.Dot(v);
|
||
|
|
||
|
double dot = (v0shared.WithMagnitude(1)).Dot(
|
||
|
v1shared.WithMagnitude(1));
|
||
|
double theta = acos(dot);
|
||
|
double r = 200/scale;
|
||
|
// Set the radius so that no more than one third of the
|
||
|
// line segment disappears.
|
||
|
r = min(r, v0shared.Magnitude()*tan(theta/2)/3);
|
||
|
r = min(r, v1shared.Magnitude()*tan(theta/2)/3);
|
||
|
double el = r/tan(theta/2);
|
||
|
|
||
|
hRequest rln0 = AddRequest(Request::LINE_SEGMENT, false),
|
||
|
rln1 = AddRequest(Request::LINE_SEGMENT, false);
|
||
|
hRequest rarc = AddRequest(Request::ARC_OF_CIRCLE, false);
|
||
|
|
||
|
Entity *ln0 = SS.GetEntity(rln0.entity(0)),
|
||
|
*ln1 = SS.GetEntity(rln1.entity(0));
|
||
|
Entity *arc = SS.GetEntity(rarc.entity(0));
|
||
|
|
||
|
SS.GetEntity(ln0->point[0])->PointForceTo(pother0);
|
||
|
Constraint::ConstrainCoincident(ln0->point[0], hother0);
|
||
|
|
||
|
SS.GetEntity(ln1->point[0])->PointForceTo(pother1);
|
||
|
Constraint::ConstrainCoincident(ln1->point[0], hother1);
|
||
|
|
||
|
Vector arc0 = pshared.Minus(v0shared.WithMagnitude(el));
|
||
|
Vector arc1 = pshared.Minus(v1shared.WithMagnitude(el));
|
||
|
|
||
|
SS.GetEntity(ln0->point[1])->PointForceTo(arc0);
|
||
|
SS.GetEntity(ln1->point[1])->PointForceTo(arc1);
|
||
|
|
||
|
Constraint::Constrain(Constraint::PT_ON_LINE,
|
||
|
ln0->point[1], Entity::NO_ENTITY, srcline0);
|
||
|
Constraint::Constrain(Constraint::PT_ON_LINE,
|
||
|
ln1->point[1], Entity::NO_ENTITY, srcline1);
|
||
|
|
||
|
Vector center = arc0;
|
||
|
int a, b;
|
||
|
if(vv < 0) {
|
||
|
a = 1; b = 2;
|
||
|
center = center.Minus(v0shared.Cross(wn).WithMagnitude(r));
|
||
|
} else {
|
||
|
a = 2; b = 1;
|
||
|
center = center.Plus(v0shared.Cross(wn).WithMagnitude(r));
|
||
|
}
|
||
|
|
||
|
SS.GetEntity(arc->point[0])->PointForceTo(center);
|
||
|
SS.GetEntity(arc->point[a])->PointForceTo(arc0);
|
||
|
SS.GetEntity(arc->point[b])->PointForceTo(arc1);
|
||
|
|
||
|
Constraint::ConstrainCoincident(arc->point[a], ln0->point[1]);
|
||
|
Constraint::ConstrainCoincident(arc->point[b], ln1->point[1]);
|
||
|
|
||
|
Constraint::Constrain(Constraint::ARC_LINE_TANGENT,
|
||
|
Entity::NO_ENTITY, Entity::NO_ENTITY,
|
||
|
arc->h, ln0->h, (a==2));
|
||
|
Constraint::Constrain(Constraint::ARC_LINE_TANGENT,
|
||
|
Entity::NO_ENTITY, Entity::NO_ENTITY,
|
||
|
arc->h, ln1->h, (b==2));
|
||
|
|
||
|
SS.later.generateAll = true;
|
||
|
}
|
||
|
|
||
|
void GraphicsWindow::SplitLine(hEntity he, Vector pinter) {
|
||
|
// Save the original endpoints, since we're about to delete this entity.
|
||
|
Entity *e01 = SS.GetEntity(he);
|
||
|
hEntity hep0 = e01->point[0], hep1 = e01->point[1];
|
||
|
Vector p0 = SS.GetEntity(hep0)->PointGetNum(),
|
||
|
p1 = SS.GetEntity(hep1)->PointGetNum();
|
||
|
|
||
|
SS.UndoRemember();
|
||
|
|
||
|
// Add the two line segments this one gets split into.
|
||
|
hRequest r0i = AddRequest(Request::LINE_SEGMENT, false),
|
||
|
ri1 = AddRequest(Request::LINE_SEGMENT, false);
|
||
|
// Don't get entities till after adding, realloc issues
|
||
|
|
||
|
Entity *e0i = SS.GetEntity(r0i.entity(0)),
|
||
|
*ei1 = SS.GetEntity(ri1.entity(0));
|
||
|
|
||
|
SS.GetEntity(e0i->point[0])->PointForceTo(p0);
|
||
|
SS.GetEntity(e0i->point[1])->PointForceTo(pinter);
|
||
|
SS.GetEntity(ei1->point[0])->PointForceTo(pinter);
|
||
|
SS.GetEntity(ei1->point[1])->PointForceTo(p1);
|
||
|
|
||
|
ReplacePointInConstraints(hep0, e0i->point[0]);
|
||
|
ReplacePointInConstraints(hep1, ei1->point[1]);
|
||
|
|
||
|
// Finally, delete the original line
|
||
|
int i;
|
||
|
SS.request.ClearTags();
|
||
|
for(i = 0; i < SS.request.n; i++) {
|
||
|
Request *r = &(SS.request.elem[i]);
|
||
|
if(r->group.v != activeGroup.v) continue;
|
||
|
if(r->type != Request::LINE_SEGMENT) continue;
|
||
|
|
||
|
// If the user wants to keep the old lines around, they can just
|
||
|
// mark it construction first.
|
||
|
if(he.v == r->h.entity(0).v && !r->construction) {
|
||
|
r->tag = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
DeleteTaggedRequests();
|
||
|
}
|
||
|
|
||
|
void GraphicsWindow::SplitCircle(hEntity he, Vector pinter) {
|
||
|
SS.UndoRemember();
|
||
|
|
||
|
Entity *circle = SS.GetEntity(he);
|
||
|
if(circle->type == Entity::CIRCLE) {
|
||
|
// Start with an unbroken circle, split it into a 360 degree arc.
|
||
|
Vector center = SS.GetEntity(circle->point[0])->PointGetNum();
|
||
|
|
||
|
circle = NULL; // shortly invalid!
|
||
|
hRequest hr = AddRequest(Request::ARC_OF_CIRCLE, false);
|
||
|
|
||
|
Entity *arc = SS.GetEntity(hr.entity(0));
|
||
|
|
||
|
SS.GetEntity(arc->point[0])->PointForceTo(center);
|
||
|
SS.GetEntity(arc->point[1])->PointForceTo(pinter);
|
||
|
SS.GetEntity(arc->point[2])->PointForceTo(pinter);
|
||
|
} else {
|
||
|
// Start with an arc, break it in to two arcs
|
||
|
Vector center = SS.GetEntity(circle->point[0])->PointGetNum(),
|
||
|
start = SS.GetEntity(circle->point[1])->PointGetNum(),
|
||
|
finish = SS.GetEntity(circle->point[2])->PointGetNum();
|
||
|
|
||
|
circle = NULL; // shortly invalid!
|
||
|
hRequest hr0 = AddRequest(Request::ARC_OF_CIRCLE, false),
|
||
|
hr1 = AddRequest(Request::ARC_OF_CIRCLE, false);
|
||
|
|
||
|
Entity *arc0 = SS.GetEntity(hr0.entity(0)),
|
||
|
*arc1 = SS.GetEntity(hr1.entity(0));
|
||
|
|
||
|
SS.GetEntity(arc0->point[0])->PointForceTo(center);
|
||
|
SS.GetEntity(arc0->point[1])->PointForceTo(start);
|
||
|
SS.GetEntity(arc0->point[2])->PointForceTo(pinter);
|
||
|
|
||
|
SS.GetEntity(arc1->point[0])->PointForceTo(center);
|
||
|
SS.GetEntity(arc1->point[1])->PointForceTo(pinter);
|
||
|
SS.GetEntity(arc1->point[2])->PointForceTo(finish);
|
||
|
}
|
||
|
|
||
|
// Finally, delete the original circle or arc
|
||
|
int i;
|
||
|
SS.request.ClearTags();
|
||
|
for(i = 0; i < SS.request.n; i++) {
|
||
|
Request *r = &(SS.request.elem[i]);
|
||
|
if(r->group.v != activeGroup.v) continue;
|
||
|
if(r->type != Request::CIRCLE && r->type != Request::ARC_OF_CIRCLE) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If the user wants to keep the old lines around, they can just
|
||
|
// mark it construction first.
|
||
|
if(he.v == r->h.entity(0).v && !r->construction) {
|
||
|
r->tag = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
DeleteTaggedRequests();
|
||
|
}
|
||
|
|
||
|
void GraphicsWindow::SplitLinesOrCurves(void) {
|
||
|
if(!LockedInWorkplane()) {
|
||
|
Error("Must be sketching in workplane to split.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GroupSelection();
|
||
|
if(gs.n == 2 && gs.lineSegments == 2) {
|
||
|
Entity *la = SS.GetEntity(gs.entity[0]),
|
||
|
*lb = SS.GetEntity(gs.entity[1]);
|
||
|
Vector a0 = SS.GetEntity(la->point[0])->PointGetNum(),
|
||
|
a1 = SS.GetEntity(la->point[1])->PointGetNum(),
|
||
|
b0 = SS.GetEntity(lb->point[0])->PointGetNum(),
|
||
|
b1 = SS.GetEntity(lb->point[1])->PointGetNum();
|
||
|
Vector da = a1.Minus(a0), db = b1.Minus(b0);
|
||
|
|
||
|
// First, check if the lines intersect.
|
||
|
bool skew;
|
||
|
Vector pinter = Vector::AtIntersectionOfLines(a0, a1, b0, b1, &skew);
|
||
|
if(skew) {
|
||
|
Error("Lines are parallel or skew; no intersection to split.");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
double ta = (pinter.Minus(a0)).DivPivoting(da),
|
||
|
tb = (pinter.Minus(b0)).DivPivoting(db);
|
||
|
|
||
|
double tola = LENGTH_EPS/da.Magnitude(),
|
||
|
tolb = LENGTH_EPS/db.Magnitude();
|
||
|
|
||
|
hEntity ha = la->h, hb = lb->h;
|
||
|
la = NULL; lb = NULL;
|
||
|
// Following adds will cause a realloc, break the pointers
|
||
|
|
||
|
bool didSomething = false;
|
||
|
if(ta > tola && ta < (1 - tola)) {
|
||
|
SplitLine(ha, pinter);
|
||
|
didSomething = true;
|
||
|
}
|
||
|
if(tb > tolb && tb < (1 - tolb)) {
|
||
|
SplitLine(hb, pinter);
|
||
|
didSomething = true;
|
||
|
}
|
||
|
if(!didSomething) {
|
||
|
Error(
|
||
|
"Nothing to split; intersection does not lie on either line.");
|
||
|
}
|
||
|
} else if(gs.n == 2 && gs.lineSegments == 1 && gs.circlesOrArcs == 1) {
|
||
|
Entity *line = SS.GetEntity(gs.entity[0]),
|
||
|
*circle = SS.GetEntity(gs.entity[1]);
|
||
|
if(line->type != Entity::LINE_SEGMENT) {
|
||
|
SWAP(Entity *, line, circle);
|
||
|
}
|
||
|
hEntity hline = line->h, hcircle = circle->h;
|
||
|
|
||
|
Vector l0 = SS.GetEntity(line->point[0])->PointGetNum(),
|
||
|
l1 = SS.GetEntity(line->point[1])->PointGetNum();
|
||
|
Vector dl = l1.Minus(l0);
|
||
|
|
||
|
Quaternion q = SS.GetEntity(circle->normal)->NormalGetNum();
|
||
|
Vector cn = q.RotationN();
|
||
|
Vector cc = SS.GetEntity(circle->point[0])->PointGetNum();
|
||
|
double cd = cc.Dot(cn);
|
||
|
double cr = circle->CircleGetRadiusNum();
|
||
|
|
||
|
if(fabs(l0.Dot(cn) - cd) > LENGTH_EPS ||
|
||
|
fabs(l1.Dot(cn) - cd) > LENGTH_EPS)
|
||
|
{
|
||
|
Error("Lines does not lie in same plane as circle.");
|
||
|
goto done;
|
||
|
}
|
||
|
// Now let's see if they intersect; transform everything into a csys
|
||
|
// with origin at the center of the circle, and where the line is
|
||
|
// horizontal.
|
||
|
Vector n = cn.WithMagnitude(1);
|
||
|
Vector u = dl.WithMagnitude(1);
|
||
|
Vector v = n.Cross(u);
|
||
|
|
||
|
Vector nl0 = (l0.Minus(cc)).DotInToCsys(u, v, n),
|
||
|
nl1 = (l1.Minus(cc)).DotInToCsys(u, v, n);
|
||
|
|
||
|
double yint = nl0.y;
|
||
|
if(fabs(yint) > (cr - LENGTH_EPS)) {
|
||
|
Error("Line does not intersect (or is tangent to) circle.");
|
||
|
goto done;
|
||
|
}
|
||
|
double xint = sqrt(cr*cr - yint*yint);
|
||
|
|
||
|
Vector inter0 = Vector::From( xint, yint, 0),
|
||
|
inter1 = Vector::From(-xint, yint, 0);
|
||
|
|
||
|
// While we're here, let's calculate the angles at which the
|
||
|
// intersections (and the endpoints of the arc) occur.
|
||
|
double theta0 = atan2(yint, xint), theta1 = atan2(yint, -xint);
|
||
|
double thetamin, thetamax;
|
||
|
if(circle->type == Entity::CIRCLE) {
|
||
|
thetamin = 0;
|
||
|
thetamax = 2.1*PI; // fudge, make sure it's a good complete circle
|
||
|
} else {
|
||
|
Vector start = SS.GetEntity(circle->point[1])->PointGetNum(),
|
||
|
finish = SS.GetEntity(circle->point[2])->PointGetNum();
|
||
|
start = (start .Minus(cc)).DotInToCsys(u, v, n);
|
||
|
finish = (finish.Minus(cc)).DotInToCsys(u, v, n);
|
||
|
thetamin = atan2(start.y, start.x);
|
||
|
thetamax = atan2(finish.y, finish.x);
|
||
|
|
||
|
// Normalize; arc is drawn with increasing theta from start,
|
||
|
// so subtract that off and make all angles in (0, 2*pi]
|
||
|
theta0 = WRAP_NOT_0(theta0 - thetamin, 2*PI);
|
||
|
theta1 = WRAP_NOT_0(theta1 - thetamin, 2*PI);
|
||
|
thetamax = WRAP_NOT_0(thetamax - thetamin, 2*PI);
|
||
|
}
|
||
|
|
||
|
// And move our intersections back out to the base frame.
|
||
|
inter0 = inter0.ScaleOutOfCsys(u, v, n).Plus(cc);
|
||
|
inter1 = inter1.ScaleOutOfCsys(u, v, n).Plus(cc);
|
||
|
|
||
|
// So now we have our intersection points. Let's see where they are
|
||
|
// on the line.
|
||
|
double t0 = (inter0.Minus(l0)).DivPivoting(dl),
|
||
|
t1 = (inter1.Minus(l0)).DivPivoting(dl);
|
||
|
double tol = LENGTH_EPS/dl.Magnitude();
|
||
|
|
||
|
bool didSomething = false;
|
||
|
// Split only once, even if it crosses multiple times; just pick
|
||
|
// arbitrarily which.
|
||
|
if(t0 > tol && t0 < (1 - tol) && theta0 < thetamax) {
|
||
|
SplitLine(hline, inter0);
|
||
|
SplitCircle(hcircle, inter0);
|
||
|
didSomething = true;
|
||
|
} else if(t1 > tol && t1 < (1 - tol) && theta1 < thetamax) {
|
||
|
SplitLine(hline, inter1);
|
||
|
SplitCircle(hcircle, inter1);
|
||
|
didSomething = true;
|
||
|
}
|
||
|
if(!didSomething) {
|
||
|
Error("Nothing to split; neither intersection lies on both the "
|
||
|
"line and the circle.");
|
||
|
}
|
||
|
} else {
|
||
|
Error("Can't split these entities; select two lines, a line and "
|
||
|
"a circle, or a line and an arc.");
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
ClearSelection();
|
||
|
SS.later.generateAll = true;
|
||
|
}
|
||
|
|