solvespace/modify.cpp
Jonathan Westhues 0a56a63417 Add a trim command. I can now do circles (or arcs) against lines,
or lines against lines. The constraints get rather screwed up
afterwards, of course.

So make arcs with the endpoints coincident into circles, instead
of nothings; since the first split of a circle produces that.

And don't warn after deleting just point-coincident or horiz/vert
constraints as a dependency; that's just a nuisance, because it
happens too often.

[git-p4: depot-paths = "//depot/solvespace/": change = 1884]
2009-01-03 04:27:33 -08:00

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;
}