#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 < SK.constraint.n; i++) { Constraint *c = &(SK.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; } } } void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) { Request *r = SK.GetRequest(hr); if(r->group.v != SS.GW.activeGroup.v) return; Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(!(e->h.isFromRequest())) continue; if(e->h.request().v != hr.v) continue; if(e->type != Entity::POINT_IN_2D && e->type != Entity::POINT_IN_3D) { continue; } // This is a point generated by the request being deleted; so fix // the constraints for that. FixConstraintsForPointBeingDeleted(e->h); } } void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) { List ld; ZERO(&ld); Constraint *c; SK.constraint.ClearTags(); for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { if(c->type != Constraint::POINTS_COINCIDENT) continue; if(c->group.v != SS.GW.activeGroup.v) continue; if(c->ptA.v == hpt.v) { ld.Add(&(c->ptB)); c->tag = 1; } if(c->ptB.v == hpt.v) { ld.Add(&(c->ptA)); c->tag = 1; } } // These would get removed anyways when we regenerated, but do it now; // that way subsequent calls of this function (if multiple coincident // points are getting deleted) will work correctly. SK.constraint.RemoveTagged(); // If more than one point was constrained coincident with hpt, then // those two points were implicitly coincident with each other. By // deleting hpt (and all constraints that mention it), we will delete // that relationship. So put it back here now. int i; for(i = 1; i < ld.n; i++) { Constraint::ConstrainCoincident(ld.elem[i-1], ld.elem[i]); } ld.Clear(); } //----------------------------------------------------------------------------- // 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 = SK.GetEntity(gs.point[0])->PointGetNum(); ClearSelection(); int i, c = 0; Entity *line[2]; Request *lineReq[2]; bool point1[2]; for(i = 0; i < SK.request.n; i++) { Request *r = &(SK.request.elem[i]); if(r->group.v != activeGroup.v) continue; if(r->type != Request::LINE_SEGMENT) continue; if(r->construction) continue; Entity *e = SK.GetEntity(r->h.entity(0)); Vector p0 = SK.GetEntity(e->point[0])->PointGetNum(), p1 = SK.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 = SK.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 = SK.GetEntity(hother0)->PointGetNum(), pother1 = SK.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 = SK.GetEntity(rln0.entity(0)), *ln1 = SK.GetEntity(rln1.entity(0)); Entity *arc = SK.GetEntity(rarc.entity(0)); SK.GetEntity(ln0->point[0])->PointForceTo(pother0); Constraint::ConstrainCoincident(ln0->point[0], hother0); SK.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)); SK.GetEntity(ln0->point[1])->PointForceTo(arc0); SK.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)); } SK.GetEntity(arc->point[0])->PointForceTo(center); SK.GetEntity(arc->point[a])->PointForceTo(arc0); SK.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; } hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { // Save the original endpoints, since we're about to delete this entity. Entity *e01 = SK.GetEntity(he); hEntity hep0 = e01->point[0], hep1 = e01->point[1]; Vector p0 = SK.GetEntity(hep0)->PointGetNum(), p1 = SK.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 = SK.GetEntity(r0i.entity(0)), *ei1 = SK.GetEntity(ri1.entity(0)); SK.GetEntity(e0i->point[0])->PointForceTo(p0); SK.GetEntity(e0i->point[1])->PointForceTo(pinter); SK.GetEntity(ei1->point[0])->PointForceTo(pinter); SK.GetEntity(ei1->point[1])->PointForceTo(p1); ReplacePointInConstraints(hep0, e0i->point[0]); ReplacePointInConstraints(hep1, ei1->point[1]); Constraint::ConstrainCoincident(e0i->point[1], ei1->point[0]); return e0i->point[1]; } hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) { SS.UndoRemember(); Entity *circle = SK.GetEntity(he); if(circle->type == Entity::CIRCLE) { // Start with an unbroken circle, split it into a 360 degree arc. Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); circle = NULL; // shortly invalid! hRequest hr = AddRequest(Request::ARC_OF_CIRCLE, false); Entity *arc = SK.GetEntity(hr.entity(0)); SK.GetEntity(arc->point[0])->PointForceTo(center); SK.GetEntity(arc->point[1])->PointForceTo(pinter); SK.GetEntity(arc->point[2])->PointForceTo(pinter); Constraint::ConstrainCoincident(arc->point[1], arc->point[2]); return arc->point[1]; } else { // Start with an arc, break it in to two arcs hEntity hc = circle->point[0], hs = circle->point[1], hf = circle->point[2]; Vector center = SK.GetEntity(hc)->PointGetNum(), start = SK.GetEntity(hs)->PointGetNum(), finish = SK.GetEntity(hf)->PointGetNum(); circle = NULL; // shortly invalid! hRequest hr0 = AddRequest(Request::ARC_OF_CIRCLE, false), hr1 = AddRequest(Request::ARC_OF_CIRCLE, false); Entity *arc0 = SK.GetEntity(hr0.entity(0)), *arc1 = SK.GetEntity(hr1.entity(0)); SK.GetEntity(arc0->point[0])->PointForceTo(center); SK.GetEntity(arc0->point[1])->PointForceTo(start); SK.GetEntity(arc0->point[2])->PointForceTo(pinter); SK.GetEntity(arc1->point[0])->PointForceTo(center); SK.GetEntity(arc1->point[1])->PointForceTo(pinter); SK.GetEntity(arc1->point[2])->PointForceTo(finish); ReplacePointInConstraints(hs, arc0->point[1]); ReplacePointInConstraints(hf, arc1->point[2]); Constraint::ConstrainCoincident(arc0->point[2], arc1->point[1]); return arc0->point[2]; } } hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { // Save the original endpoints, since we're about to delete this entity. Entity *e01 = SK.GetEntity(he); hEntity hep0 = e01->point[0], hep1 = e01->point[1], hep2 = e01->point[2], hep3 = e01->point[3]; Vector p0 = SK.GetEntity(hep0)->PointGetNum(), p1 = SK.GetEntity(hep1)->PointGetNum(), p2 = SK.GetEntity(hep2)->PointGetNum(), p3 = SK.GetEntity(hep3)->PointGetNum(); SS.UndoRemember(); SBezier b0i, bi1, b01 = SBezier::From(p0, p1, p2, p3); double t; b01.ClosestPointTo(pinter, &t, true); b01.SplitAt(t, &b0i, &bi1); // Add the two cubic segments this one gets split into. hRequest r0i = AddRequest(Request::CUBIC, false), ri1 = AddRequest(Request::CUBIC, false); // Don't get entities till after adding, realloc issues Entity *e0i = SK.GetEntity(r0i.entity(0)), *ei1 = SK.GetEntity(ri1.entity(0)); SK.GetEntity(e0i->point[0])->PointForceTo(b0i.ctrl[0]); SK.GetEntity(e0i->point[1])->PointForceTo(b0i.ctrl[1]); SK.GetEntity(e0i->point[2])->PointForceTo(b0i.ctrl[2]); SK.GetEntity(e0i->point[3])->PointForceTo(b0i.ctrl[3]); SK.GetEntity(ei1->point[0])->PointForceTo(bi1.ctrl[0]); SK.GetEntity(ei1->point[1])->PointForceTo(bi1.ctrl[1]); SK.GetEntity(ei1->point[2])->PointForceTo(bi1.ctrl[2]); SK.GetEntity(ei1->point[3])->PointForceTo(bi1.ctrl[3]); ReplacePointInConstraints(hep0, e0i->point[0]); ReplacePointInConstraints(hep3, ei1->point[3]); Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]); return e0i->point[3]; } hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) { Entity *e = SK.GetEntity(he); int entityType = e->type; hEntity ret; if(e->IsCircle()) { ret = SplitCircle(he, pinter); } else if(e->type == Entity::LINE_SEGMENT) { ret = SplitLine(he, pinter); } else if(e->type == Entity::CUBIC && e->extraPoints == 0) { ret = SplitCubic(he, pinter); } else { Error("Couldn't split this entity; lines, circles, or cubics only."); return Entity::NO_ENTITY; } // Finally, delete the request that generated the original entity. int reqType = EntReqTable::GetRequestForEntity(entityType); SK.request.ClearTags(); for(int i = 0; i < SK.request.n; i++) { Request *r = &(SK.request.elem[i]); if(r->group.v != activeGroup.v) continue; if(r->type != reqType) continue; // If the user wants to keep the old entities around, they can just // mark them construction first. if(he.v == r->h.entity(0).v && !r->construction) { r->tag = 1; break; } } DeleteTaggedRequests(); return ret; } void GraphicsWindow::SplitLinesOrCurves(void) { if(!LockedInWorkplane()) { Error("Must be sketching in workplane to split."); return; } GroupSelection(); if(!(gs.n == 2 && (gs.lineSegments + gs.circlesOrArcs + gs.cubics) == 2)) { Error("Select two entities that intersect each other (e.g. two lines " "or two circles or a circle and a line)."); return; } hEntity ha = gs.entity[0], hb = gs.entity[1]; Entity *ea = SK.GetEntity(ha), *eb = SK.GetEntity(hb); // Compute the possibly-rational Bezier curves for each of these entities SBezierList sbla, sblb; ZERO(&sbla); ZERO(&sblb); ea->GenerateBezierCurves(&sbla); eb->GenerateBezierCurves(&sblb); // and then compute the points where they intersect, based on those curves. SPointList inters; ZERO(&inters); sbla.AllIntersectionsWith(&sblb, &inters); // If there's multiple points, then just take the first one. if(inters.l.n > 0) { Vector pi = inters.l.elem[0].p; hEntity hia = SplitEntity(ha, pi), hib = SplitEntity(hb, pi); // SplitEntity adds the coincident constraints to join the split halves // of each original entity; and then we add the constraint to join // the two entities together at the split point. if(hia.v && hib.v) { Constraint::ConstrainCoincident(hia, hib); } } else { Error("Can't split; no intersection found."); } // All done, clean up and regenerate. inters.Clear(); sbla.Clear(); sblb.Clear(); ClearSelection(); SS.later.generateAll = true; }