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]
solver
Jonathan Westhues 2009-01-03 04:27:33 -08:00
parent 0f228fc0fb
commit 0a56a63417
11 changed files with 533 additions and 177 deletions

View File

@ -15,6 +15,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \
$(OBJDIR)\textwin.obj \ $(OBJDIR)\textwin.obj \
$(OBJDIR)\textscreens.obj \ $(OBJDIR)\textscreens.obj \
$(OBJDIR)\graphicswin.obj \ $(OBJDIR)\graphicswin.obj \
$(OBJDIR)\modify.obj \
$(OBJDIR)\util.obj \ $(OBJDIR)\util.obj \
$(OBJDIR)\entity.obj \ $(OBJDIR)\entity.obj \
$(OBJDIR)\drawentity.obj \ $(OBJDIR)\drawentity.obj \

View File

@ -117,23 +117,18 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db,
db = db.ProjectVectorInto(workplane); db = db.ProjectVectorInto(workplane);
} }
// Make an orthogonal coordinate system from those directions bool skew;
Vector dn = da.Cross(db); // normal to both Vector pi = Vector::AtIntersectionOfLines(a0, a0.Plus(da),
Vector dna = dn.Cross(da); // normal to da b0, b0.Plus(db), &skew);
Vector dnb = dn.Cross(db); // normal to db
// At the intersection of the lines
// a0 + pa*da = b0 + pb*db (where pa, pb are scalar params)
// So dot this equation against dna and dnb to get two equations
// to solve for da and db
double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna));
double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb));
Vector pi = a0.Plus(da.ScaledBy(pa)); if(!skew) {
if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) {
*ref = pi.Plus(offset); *ref = pi.Plus(offset);
// We draw in a coordinate system centered at pi, with // We draw in a coordinate system centered at the intersection point.
// basis vectors da and dna. // One basis vector is da, and the other is normal to da and in
// the plane that contains our lines (so normal to its normal).
Vector dna = (da.Cross(db)).Cross(da);
da = da.WithMagnitude(1); dna = dna.WithMagnitude(1); da = da.WithMagnitude(1); dna = dna.WithMagnitude(1);
Vector rm = (*ref).Minus(pi); Vector rm = (*ref).Minus(pi);
double rda = rm.Dot(da), rdna = rm.Dot(dna); double rda = rm.Dot(da), rdna = rm.Dot(dna);
double r = sqrt(rda*rda + rdna*rdna); double r = sqrt(rda*rda + rdna*rdna);

3
dsc.h
View File

@ -45,6 +45,9 @@ public:
static Vector From(hParam x, hParam y, hParam z); static Vector From(hParam x, hParam y, hParam z);
static Vector AtIntersectionOfPlanes(Vector n1, double d1, static Vector AtIntersectionOfPlanes(Vector n1, double d1,
Vector n2, double d2); Vector n2, double d2);
static Vector AtIntersectionOfLines(Vector a0, Vector a1,
Vector b0, Vector b1,
bool *skew);
double Element(int i); double Element(int i);
bool Equals(Vector v, double tol=LENGTH_EPS); bool Equals(Vector v, double tol=LENGTH_EPS);

View File

@ -118,7 +118,9 @@ void Entity::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) {
*thetaa = atan2(pa2.y, pa2.x); *thetaa = atan2(pa2.y, pa2.x);
*thetab = atan2(pb2.y, pb2.x); *thetab = atan2(pb2.y, pb2.x);
*dtheta = *thetab - *thetaa; *dtheta = *thetab - *thetaa;
while(*dtheta < 0) *dtheta += 2*PI; // If the endpoints are coincident, call it a full arc, not a zero arc;
// useful concept to have when splitting
while(*dtheta < 1e-6) *dtheta += 2*PI;
while(*dtheta > (2*PI)) *dtheta -= 2*PI; while(*dtheta > (2*PI)) *dtheta -= 2*PI;
} }

View File

@ -36,6 +36,8 @@ bool SolveSpace::PruneOrphans(void) {
if(GroupExists(c->group)) continue; if(GroupExists(c->group)) continue;
(deleted.constraints)++; (deleted.constraints)++;
(deleted.nonTrivialConstraints)++;
constraint.RemoveById(c->h); constraint.RemoveById(c->h);
return true; return true;
} }
@ -118,6 +120,13 @@ bool SolveSpace::PruneConstraints(hGroup hg) {
} }
(deleted.constraints)++; (deleted.constraints)++;
if(c->type != Constraint::POINTS_COINCIDENT &&
c->type != Constraint::HORIZONTAL &&
c->type != Constraint::VERTICAL)
{
(deleted.nonTrivialConstraints)++;
}
constraint.RemoveById(c->h); constraint.RemoveById(c->h);
return true; return true;
} }
@ -264,14 +273,21 @@ void SolveSpace::GenerateAll(int first, int last) {
} }
later.showTW = true; later.showTW = true;
GW.ClearSuper(); GW.ClearSuper();
// People get annoyed if I complain whenever they delete any request,
// and I otherwise will, since those always come with pt-coincident
// constraints.
if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 ||
deleted.groups > 0)
{
// Don't display any errors until we've regenerated fully. The // Don't display any errors until we've regenerated fully. The
// sketch is not necessarily in a consistent state until we've // sketch is not necessarily in a consistent state until we've
// pruned any orphaned etc. objects, and the message loop for the // pruned any orphaned etc. objects, and the message loop for the
// messagebox could allow us to repaint and crash. But now we must // messagebox could allow us to repaint and crash. But now we must
// be fine. // be fine.
Error("Additional sketch elements were deleted, because they depend " Error("Additional sketch elements were deleted, because they "
"on the element that was just deleted explicitly. These " "depend on the element that was just deleted explicitly. "
"include: \r\n" "These include: \r\n"
" %d request%s\r\n" " %d request%s\r\n"
" %d constraint%s\r\n" " %d constraint%s\r\n"
" %d group%s\r\n\r\n" " %d group%s\r\n\r\n"
@ -279,6 +295,7 @@ void SolveSpace::GenerateAll(int first, int last) {
deleted.requests, deleted.requests == 1 ? "" : "s", deleted.requests, deleted.requests == 1 ? "" : "s",
deleted.constraints, deleted.constraints == 1 ? "" : "s", deleted.constraints, deleted.constraints == 1 ? "" : "s",
deleted.groups, deleted.groups == 1 ? "" : "s"); deleted.groups, deleted.groups == 1 ? "" : "s");
}
memset(&deleted, 0, sizeof(deleted)); memset(&deleted, 0, sizeof(deleted));
} }

View File

@ -79,6 +79,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 1, "&Text in TrueType Font\tT", MNU_TTF_TEXT, 'T', mReq }, { 1, "&Text in TrueType Font\tT", MNU_TTF_TEXT, 'T', mReq },
{ 1, NULL, 0, NULL }, { 1, NULL, 0, NULL },
{ 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq }, { 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq },
{ 1, "Split Curves at &Intersection\tI", MNU_SPLIT_CURVES, 'I', mReq },
{ 0, "&Constrain", 0, NULL }, { 0, "&Constrain", 0, NULL },
{ 1, "&Distance / Diameter\tD", MNU_DISTANCE_DIA, 'D', mCon }, { 1, "&Distance / Diameter\tD", MNU_DISTANCE_DIA, 'D', mCon },
@ -510,6 +511,22 @@ void GraphicsWindow::ForceTextWindowShown(void) {
} }
} }
void GraphicsWindow::DeleteTaggedRequests(void) {
SS.request.RemoveTagged();
// An edit might be in progress for the just-deleted item. So
// now it's not.
HideGraphicsEditControl();
HideTextEditControl();
// And clear out the selection, which could contain that item.
ClearSuper();
// And regenerate to get rid of what it generates, plus anything
// that references it (since the regen code checks for that).
SS.GenerateAll(0, INT_MAX);
EnsureValidActives();
SS.later.showTW = true;
}
void GraphicsWindow::MenuEdit(int id) { void GraphicsWindow::MenuEdit(int id) {
switch(id) { switch(id) {
case MNU_UNSELECT_ALL: case MNU_UNSELECT_ALL:
@ -542,20 +559,9 @@ void GraphicsWindow::MenuEdit(int id) {
SS.constraint.Tag(s->constraint, 1); SS.constraint.Tag(s->constraint, 1);
} }
} }
SS.request.RemoveTagged();
SS.constraint.RemoveTagged();
// An edit might be in progress for the just-deleted item. So SS.constraint.RemoveTagged();
// now it's not. SS.GW.DeleteTaggedRequests();
HideGraphicsEditControl();
HideTextEditControl();
// And clear out the selection, which could contain that item.
SS.GW.ClearSuper();
// And regenerate to get rid of what it generates, plus anything
// that references it (since the regen code checks for that).
SS.GenerateAll(0, INT_MAX);
SS.GW.EnsureValidActives();
SS.later.showTW = true;
break; break;
} }
@ -612,138 +618,7 @@ void GraphicsWindow::MenuRequest(int id) {
case MNU_ARC: { case MNU_ARC: {
SS.GW.GroupSelection(); SS.GW.GroupSelection();
if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) {
if(!SS.GW.LockedInWorkplane()) { SS.GW.MakeTangentArc();
Error("Must be sketching in workplane to create tangent "
"arc.");
break;
}
// 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(SS.GW.gs.point[0])->PointGetNum();
SS.GW.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 != SS.GW.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.");
break;
}
SS.UndoRemember();
Entity *wrkpl = SS.GetEntity(SS.GW.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/SS.GW.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 = SS.GW.AddRequest(Request::LINE_SEGMENT, false),
rln1 = SS.GW.AddRequest(Request::LINE_SEGMENT, false);
hRequest rarc = SS.GW.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;
} else { } else {
s = "click point on arc (draws anti-clockwise)"; s = "click point on arc (draws anti-clockwise)";
goto c; goto c;
@ -778,6 +653,10 @@ c:
break; break;
} }
case MNU_SPLIT_CURVES:
SS.GW.SplitLinesOrCurves();
break;
default: oops(); default: oops();
} }
} }

409
modify.cpp Normal file
View File

@ -0,0 +1,409 @@
#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;
}

View File

@ -15,11 +15,19 @@
#define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11)) #define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11))
inline int WRAP(int v, int n) { inline int WRAP(int v, int n) {
// Clamp it to the range [0, n)
while(v >= n) v -= n; while(v >= n) v -= n;
while(v < 0) v += n; while(v < 0) v += n;
return v; return v;
} }
inline double WRAP_NOT_0(double v, double n) {
// Clamp it to the range (0, n]
while(v > n) v -= n;
while(v <= 0) v += n;
return v;
}
#define SWAP(T, a, b) do { T temp = (a); (a) = (b); (b) = temp; } while(0) #define SWAP(T, a, b) do { T temp = (a); (a) = (b); (b) = temp; } while(0)
#define ZERO(v) memset((v), 0, sizeof(*(v))) #define ZERO(v) memset((v), 0, sizeof(*(v)))
#define CO(v) (v).x, (v).y, (v).z #define CO(v) (v).x, (v).y, (v).z
@ -447,6 +455,7 @@ public:
int requests; int requests;
int groups; int groups;
int constraints; int constraints;
int nonTrivialConstraints;
} deleted; } deleted;
bool GroupExists(hGroup hg); bool GroupExists(hGroup hg);
bool PruneOrphans(void); bool PruneOrphans(void);

View File

@ -14,7 +14,7 @@ static const struct {
{ Icon_bezier, GraphicsWindow::MNU_CUBIC, "Sketch cubic Bezier section" }, { Icon_bezier, GraphicsWindow::MNU_CUBIC, "Sketch cubic Bezier section" },
{ Icon_point, GraphicsWindow::MNU_DATUM_POINT, "Sketch datum point" }, { Icon_point, GraphicsWindow::MNU_DATUM_POINT, "Sketch datum point" },
{ Icon_construction, GraphicsWindow::MNU_CONSTRUCTION, "Toggle construction" }, { Icon_construction, GraphicsWindow::MNU_CONSTRUCTION, "Toggle construction" },
{ Icon_trim, GraphicsWindow::MNU_CONSTRUCTION, "Split lines / curves where they intersect" }, { Icon_trim, GraphicsWindow::MNU_SPLIT_CURVES, "Split lines / curves where they intersect" },
{ SPACER }, { SPACER },
{ Icon_length, GraphicsWindow::MNU_DISTANCE_DIA, "Constrain distance / diameter / length" }, { Icon_length, GraphicsWindow::MNU_DISTANCE_DIA, "Constrain distance / diameter / length" },
@ -23,7 +23,7 @@ static const struct {
{ Icon_vert, GraphicsWindow::MNU_VERTICAL, "Constrain to be vertical" }, { Icon_vert, GraphicsWindow::MNU_VERTICAL, "Constrain to be vertical" },
{ Icon_parallel, GraphicsWindow::MNU_PARALLEL, "Constrain to be parallel or tangent" }, { Icon_parallel, GraphicsWindow::MNU_PARALLEL, "Constrain to be parallel or tangent" },
{ Icon_perpendicular, GraphicsWindow::MNU_PERPENDICULAR, "Constrain to be perpendicular" }, { Icon_perpendicular, GraphicsWindow::MNU_PERPENDICULAR, "Constrain to be perpendicular" },
{ Icon_pointonx, GraphicsWindow::MNU_ON_ENTITY, "Constrain point on line / curve / plane / face" }, { Icon_pointonx, GraphicsWindow::MNU_ON_ENTITY, "Constrain point on line / curve / plane / point" },
{ Icon_symmetric, GraphicsWindow::MNU_SYMMETRIC, "Constrain symmetric" }, { Icon_symmetric, GraphicsWindow::MNU_SYMMETRIC, "Constrain symmetric" },
{ Icon_ref, GraphicsWindow::MNU_REFERENCE, "Toggle reference dimension" }, { Icon_ref, GraphicsWindow::MNU_REFERENCE, "Toggle reference dimension" },
{ SPACER }, { SPACER },

8
ui.h
View File

@ -207,6 +207,7 @@ public:
MNU_RECTANGLE, MNU_RECTANGLE,
MNU_CUBIC, MNU_CUBIC,
MNU_TTF_TEXT, MNU_TTF_TEXT,
MNU_SPLIT_CURVES,
MNU_CONSTRUCTION, MNU_CONSTRUCTION,
// Group // Group
MNU_GROUP_3D, MNU_GROUP_3D,
@ -325,9 +326,16 @@ public:
hConstraint constraintBeingEdited; hConstraint constraintBeingEdited;
bool ConstrainPointByHovered(hEntity pt); bool ConstrainPointByHovered(hEntity pt);
void DeleteTaggedRequests(void);
hRequest AddRequest(int type, bool rememberForUndo); hRequest AddRequest(int type, bool rememberForUndo);
hRequest AddRequest(int type); hRequest AddRequest(int type);
void MakeTangentArc(void);
void SplitLinesOrCurves(void);
void SplitLine(hEntity he, Vector pinter);
void SplitCircle(hEntity he, Vector pinter);
void ReplacePointInConstraints(hEntity oldpt, hEntity newpt);
// The current selection. // The current selection.
class Selection { class Selection {
public: public:

View File

@ -550,6 +550,39 @@ Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1,
return (n1.ScaledBy(c1)).Plus(n2.ScaledBy(c2)); return (n1.ScaledBy(c1)).Plus(n2.ScaledBy(c2));
} }
Vector Vector::AtIntersectionOfLines(Vector a0, Vector a1,
Vector b0, Vector b1,
bool *skew)
{
Vector da = a1.Minus(a0), db = b1.Minus(b0);
// Make an orthogonal coordinate system from those directions
Vector dn = da.Cross(db); // normal to both
Vector dna = dn.Cross(da); // normal to da
Vector dnb = dn.Cross(db); // normal to db
// At the intersection of the lines
// a0 + pa*da = b0 + pb*db (where pa, pb are scalar params)
// So dot this equation against dna and dnb to get two equations
// to solve for da and db
double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna));
double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb));
// And from either of those, we get the intersection point.
Vector pi = a0.Plus(da.ScaledBy(pa));
if(skew) {
// Check if the intersection points on each line are actually
// coincident...
if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) {
*skew = false;
} else {
*skew = true;
}
}
return pi;
}
Point2d Point2d::Plus(Point2d b) { Point2d Point2d::Plus(Point2d b) {
Point2d r; Point2d r;
r.x = x + b.x; r.x = x + b.x;