From 0a56a63417edb53b176a3be6c3eb7c6b659e465f Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Sat, 3 Jan 2009 04:27:33 -0800 Subject: [PATCH] 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] --- Makefile | 1 + drawconstraint.cpp | 23 +-- dsc.h | 3 + entity.cpp | 4 +- generate.cpp | 47 ++++-- graphicswin.cpp | 169 +++---------------- modify.cpp | 409 +++++++++++++++++++++++++++++++++++++++++++++ solvespace.h | 9 + toolbar.cpp | 4 +- ui.h | 8 + util.cpp | 33 ++++ 11 files changed, 533 insertions(+), 177 deletions(-) create mode 100644 modify.cpp diff --git a/Makefile b/Makefile index b50d96d2..ac875822 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\textwin.obj \ $(OBJDIR)\textscreens.obj \ $(OBJDIR)\graphicswin.obj \ + $(OBJDIR)\modify.obj \ $(OBJDIR)\util.obj \ $(OBJDIR)\entity.obj \ $(OBJDIR)\drawentity.obj \ diff --git a/drawconstraint.cpp b/drawconstraint.cpp index 54782f6a..8eb4e673 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -117,23 +117,18 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, db = db.ProjectVectorInto(workplane); } - // 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)); + bool skew; + Vector pi = Vector::AtIntersectionOfLines(a0, a0.Plus(da), + b0, b0.Plus(db), &skew); - Vector pi = a0.Plus(da.ScaledBy(pa)); - if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) { + if(!skew) { *ref = pi.Plus(offset); - // We draw in a coordinate system centered at pi, with - // basis vectors da and dna. + // We draw in a coordinate system centered at the intersection point. + // 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); + Vector rm = (*ref).Minus(pi); double rda = rm.Dot(da), rdna = rm.Dot(dna); double r = sqrt(rda*rda + rdna*rdna); diff --git a/dsc.h b/dsc.h index d4cfbf1d..92a21792 100644 --- a/dsc.h +++ b/dsc.h @@ -45,6 +45,9 @@ public: static Vector From(hParam x, hParam y, hParam z); static Vector AtIntersectionOfPlanes(Vector n1, double d1, Vector n2, double d2); + static Vector AtIntersectionOfLines(Vector a0, Vector a1, + Vector b0, Vector b1, + bool *skew); double Element(int i); bool Equals(Vector v, double tol=LENGTH_EPS); diff --git a/entity.cpp b/entity.cpp index 295d8c72..182cf190 100644 --- a/entity.cpp +++ b/entity.cpp @@ -118,7 +118,9 @@ void Entity::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) { *thetaa = atan2(pa2.y, pa2.x); *thetab = atan2(pb2.y, pb2.x); *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; } diff --git a/generate.cpp b/generate.cpp index 7e0a0fa1..a8376cc7 100644 --- a/generate.cpp +++ b/generate.cpp @@ -36,6 +36,8 @@ bool SolveSpace::PruneOrphans(void) { if(GroupExists(c->group)) continue; (deleted.constraints)++; + (deleted.nonTrivialConstraints)++; + constraint.RemoveById(c->h); return true; } @@ -118,6 +120,13 @@ bool SolveSpace::PruneConstraints(hGroup hg) { } (deleted.constraints)++; + if(c->type != Constraint::POINTS_COINCIDENT && + c->type != Constraint::HORIZONTAL && + c->type != Constraint::VERTICAL) + { + (deleted.nonTrivialConstraints)++; + } + constraint.RemoveById(c->h); return true; } @@ -264,21 +273,29 @@ void SolveSpace::GenerateAll(int first, int last) { } later.showTW = true; GW.ClearSuper(); - // Don't display any errors until we've regenerated fully. The - // sketch is not necessarily in a consistent state until we've - // pruned any orphaned etc. objects, and the message loop for the - // messagebox could allow us to repaint and crash. But now we must - // be fine. - Error("Additional sketch elements were deleted, because they depend " - "on the element that was just deleted explicitly. These " - "include: \r\n" - " %d request%s\r\n" - " %d constraint%s\r\n" - " %d group%s\r\n\r\n" - "Choose Edit -> Undo to undelete all elements.", - deleted.requests, deleted.requests == 1 ? "" : "s", - deleted.constraints, deleted.constraints == 1 ? "" : "s", - deleted.groups, deleted.groups == 1 ? "" : "s"); + + // 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 + // sketch is not necessarily in a consistent state until we've + // pruned any orphaned etc. objects, and the message loop for the + // messagebox could allow us to repaint and crash. But now we must + // be fine. + Error("Additional sketch elements were deleted, because they " + "depend on the element that was just deleted explicitly. " + "These include: \r\n" + " %d request%s\r\n" + " %d constraint%s\r\n" + " %d group%s\r\n\r\n" + "Choose Edit -> Undo to undelete all elements.", + deleted.requests, deleted.requests == 1 ? "" : "s", + deleted.constraints, deleted.constraints == 1 ? "" : "s", + deleted.groups, deleted.groups == 1 ? "" : "s"); + } memset(&deleted, 0, sizeof(deleted)); } diff --git a/graphicswin.cpp b/graphicswin.cpp index 1da1df6b..ec617b3d 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -79,6 +79,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "&Text in TrueType Font\tT", MNU_TTF_TEXT, 'T', mReq }, { 1, NULL, 0, NULL }, { 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq }, +{ 1, "Split Curves at &Intersection\tI", MNU_SPLIT_CURVES, 'I', mReq }, { 0, "&Constrain", 0, NULL }, { 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) { switch(id) { case MNU_UNSELECT_ALL: @@ -542,20 +559,9 @@ void GraphicsWindow::MenuEdit(int id) { SS.constraint.Tag(s->constraint, 1); } } - SS.request.RemoveTagged(); - SS.constraint.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. - 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; + SS.constraint.RemoveTagged(); + SS.GW.DeleteTaggedRequests(); break; } @@ -612,138 +618,7 @@ void GraphicsWindow::MenuRequest(int id) { case MNU_ARC: { SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { - if(!SS.GW.LockedInWorkplane()) { - 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; + SS.GW.MakeTangentArc(); } else { s = "click point on arc (draws anti-clockwise)"; goto c; @@ -778,6 +653,10 @@ c: break; } + case MNU_SPLIT_CURVES: + SS.GW.SplitLinesOrCurves(); + break; + default: oops(); } } diff --git a/modify.cpp b/modify.cpp new file mode 100644 index 00000000..1ea32a5a --- /dev/null +++ b/modify.cpp @@ -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; +} + diff --git a/solvespace.h b/solvespace.h index 28896e98..386522eb 100644 --- a/solvespace.h +++ b/solvespace.h @@ -15,11 +15,19 @@ #define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11)) inline int WRAP(int v, int n) { + // Clamp it to the range [0, n) while(v >= n) v -= n; while(v < 0) v += n; 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 ZERO(v) memset((v), 0, sizeof(*(v))) #define CO(v) (v).x, (v).y, (v).z @@ -447,6 +455,7 @@ public: int requests; int groups; int constraints; + int nonTrivialConstraints; } deleted; bool GroupExists(hGroup hg); bool PruneOrphans(void); diff --git a/toolbar.cpp b/toolbar.cpp index 1ff8edbe..d97357f0 100644 --- a/toolbar.cpp +++ b/toolbar.cpp @@ -14,7 +14,7 @@ static const struct { { Icon_bezier, GraphicsWindow::MNU_CUBIC, "Sketch cubic Bezier section" }, { Icon_point, GraphicsWindow::MNU_DATUM_POINT, "Sketch datum point" }, { 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 }, { 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_parallel, GraphicsWindow::MNU_PARALLEL, "Constrain to be parallel or tangent" }, { 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_ref, GraphicsWindow::MNU_REFERENCE, "Toggle reference dimension" }, { SPACER }, diff --git a/ui.h b/ui.h index c2f4cd24..0f66a747 100644 --- a/ui.h +++ b/ui.h @@ -207,6 +207,7 @@ public: MNU_RECTANGLE, MNU_CUBIC, MNU_TTF_TEXT, + MNU_SPLIT_CURVES, MNU_CONSTRUCTION, // Group MNU_GROUP_3D, @@ -325,8 +326,15 @@ public: hConstraint constraintBeingEdited; bool ConstrainPointByHovered(hEntity pt); + void DeleteTaggedRequests(void); hRequest AddRequest(int type, bool rememberForUndo); 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. class Selection { diff --git a/util.cpp b/util.cpp index b28d88d5..e51275e5 100644 --- a/util.cpp +++ b/util.cpp @@ -550,6 +550,39 @@ Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1, 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 r; r.x = x + b.x;