solvespace/modify.cpp

410 lines
15 KiB
C++
Raw Normal View History

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