Solvespace
743 строки · 27.7 Кб
1//-----------------------------------------------------------------------------
2// User-initiated (not parametric) operations to modify our sketch, by
3// changing the requests, like to round a corner or split curves where they
4// intersect.
5//
6// Copyright 2008-2013 Jonathan Westhues.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"9
10//-----------------------------------------------------------------------------
11// Replace constraints on oldpt with the same constraints on newpt.
12// Useful when splitting, tangent arcing, or removing bezier points.
13//-----------------------------------------------------------------------------
14void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {15for(auto &c : SK.constraint) {16if(c.ptA == oldpt)17c.ptA = newpt;18if(c.ptB == oldpt)19c.ptB = newpt;20}21}
22
23//-----------------------------------------------------------------------------
24// Remove constraints on hpt. Useful when removing bezier points.
25//-----------------------------------------------------------------------------
26void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) {27SK.constraint.ClearTags();28for(auto &c : SK.constraint) {29if(c.ptA == hpt || c.ptB == hpt) {30c.tag = 1;31(SS.deleted.constraints)++;32if(c.type != Constraint::Type::POINTS_COINCIDENT &&33c.type != Constraint::Type::HORIZONTAL &&34c.type != Constraint::Type::VERTICAL)35{36(SS.deleted.nonTrivialConstraints)++;37}38}39}40SK.constraint.RemoveTagged();41}
42
43//-----------------------------------------------------------------------------
44// Let's say that A is coincident with B, and B is coincident with C. This
45// implies that A is coincident with C; but if we delete B, then both
46// constraints must be deleted too (since they reference B), and A is no
47// longer constrained to C. This routine adds back that constraint.
48//-----------------------------------------------------------------------------
49void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) {50Request *r = SK.GetRequest(hr);51if(r->group != SS.GW.activeGroup) return;52
53for(Entity &e : SK.entity) {54if(!(e.h.isFromRequest())) continue;55if(e.h.request() != hr) continue;56
57if(e.type != Entity::Type::POINT_IN_2D &&58e.type != Entity::Type::POINT_IN_3D)59{60continue;61}62
63// This is a point generated by the request being deleted; so fix64// the constraints for that.65FixConstraintsForPointBeingDeleted(e.h);66}67}
68void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {69List<hEntity> ld = {};70
71SK.constraint.ClearTags();72for(Constraint &c : SK.constraint) {73if(c.type != Constraint::Type::POINTS_COINCIDENT) continue;74if(c.group != SS.GW.activeGroup) continue;75
76if(c.ptA == hpt) {77ld.Add(&(c.ptB));78c.tag = 1;79}80if(c.ptB == hpt) {81ld.Add(&(c.ptA));82c.tag = 1;83}84}85// Remove constraints without waiting for regeneration; this way86// if another point takes the place of the deleted one (e.g. when87// removing control points of a bezier) the constraint doesn't88// spuriously move. Similarly, subsequent calls of this function89// (if multiple coincident points are getting deleted) will work90// correctly.91SK.constraint.RemoveTagged();92
93// If more than one point was constrained coincident with hpt, then94// those two points were implicitly coincident with each other. By95// deleting hpt (and all constraints that mention it), we will delete96// that relationship. So put it back here now.97for(int i = 1; i < ld.n; i++) {98Constraint::ConstrainCoincident(ld[i-1], ld[i]);99}100ld.Clear();101}
102
103//-----------------------------------------------------------------------------
104// A curve by its parametric equation, helper functions for computing tangent
105// arcs by a numerical method.
106//-----------------------------------------------------------------------------
107void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) {108*this = {};109Entity *e = SK.GetEntity(he);110if(e->type == Entity::Type::LINE_SEGMENT) {111isLine = true;112p0 = e->EndpointStart(),113p1 = e->EndpointFinish();114if(reverse) {115swap(p0, p1);116}117} else if(e->type == Entity::Type::ARC_OF_CIRCLE) {118isLine = false;119p0 = SK.GetEntity(e->point[0])->PointGetNum();120Vector pe = SK.GetEntity(e->point[1])->PointGetNum();121r = (pe.Minus(p0)).Magnitude();122e->ArcGetAngles(&theta0, &theta1, &dtheta);123if(reverse) {124swap(theta0, theta1);125dtheta = -dtheta;126}127EntityBase *wrkpln = SK.GetEntity(e->workplane)->Normal();128u = wrkpln->NormalU();129v = wrkpln->NormalV();130} else ssassert(false, "Unexpected entity type");131}
132double GraphicsWindow::ParametricCurve::LengthForAuto() {133if(isLine) {134// Allow a third of the line to disappear with auto radius135return (p1.Minus(p0)).Magnitude() / 3;136} else {137// But only a twentieth of the arc; shorter means fewer numerical138// problems since the curve is more linear over shorter sections.139return (fabs(dtheta)*r)/20;140}141}
142Vector GraphicsWindow::ParametricCurve::PointAt(double t) {143if(isLine) {144return p0.Plus((p1.Minus(p0)).ScaledBy(t));145} else {146double theta = theta0 + dtheta*t;147return p0.Plus(u.ScaledBy(r*cos(theta)).Plus(v.ScaledBy(r*sin(theta))));148}149}
150Vector GraphicsWindow::ParametricCurve::TangentAt(double t) {151if(isLine) {152return p1.Minus(p0);153} else {154double theta = theta0 + dtheta*t;155Vector t = u.ScaledBy(-r*sin(theta)).Plus(v.ScaledBy(r*cos(theta)));156t = t.ScaledBy(dtheta);157return t;158}159}
160/** Changes or copies the given entity and connects it to the arc.
161* \param t Where on this parametric curve does it connect to the arc.
162* \param reuseOrig Should the original entity be modified
163* \param orig The original entity.
164* \param arc The arc that will be connected to.
165* \param arcFinish Whether to connect to the end point of the arc.
166* \param pointf When changing the original entity, whether the end point should be modified.
167*/
168void GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t,169bool reuseOrig, hEntity orig, hEntity arc, bool arcFinish, bool pointf)170{
171hRequest hr;172Entity *e;173if(isLine) {174if (reuseOrig) {175e = SK.GetEntity(orig);176int i = pointf ? 1 : 0;177SK.GetEntity(e->point[i])->PointForceTo(PointAt(t));178ConstrainPointIfCoincident(e->point[i]);179} else {180hr = SS.GW.AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false),181e = SK.GetEntity(hr.entity(0));182SK.GetEntity(e->point[0])->PointForceTo(PointAt(t));183SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));184ConstrainPointIfCoincident(e->point[0]);185ConstrainPointIfCoincident(e->point[1]);186Constraint::Constrain(Constraint::Type::PT_ON_LINE,187hr.entity(1), Entity::NO_ENTITY, orig);188}189Constraint::Constrain(Constraint::Type::ARC_LINE_TANGENT,190Entity::NO_ENTITY, Entity::NO_ENTITY,191arc, e->h, /*other=*/arcFinish, /*other2=*/false);192} else {193if (reuseOrig) {194e = SK.GetEntity(orig);195int i = pointf ? 2 : 1;196SK.GetEntity(e->point[i])->PointForceTo(PointAt(t));197ConstrainPointIfCoincident(e->point[i]);198} else {199hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false),200e = SK.GetEntity(hr.entity(0));201SK.GetEntity(e->point[0])->PointForceTo(p0);202if(dtheta > 0) {203SK.GetEntity(e->point[1])->PointForceTo(PointAt(t));204SK.GetEntity(e->point[2])->PointForceTo(PointAt(1));205} else {206SK.GetEntity(e->point[2])->PointForceTo(PointAt(t));207SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));208}209ConstrainPointIfCoincident(e->point[0]);210ConstrainPointIfCoincident(e->point[1]);211ConstrainPointIfCoincident(e->point[2]);212}213// The tangency constraint alone is enough to fully constrain it,214// so there's no need for more.215Constraint::Constrain(Constraint::Type::CURVE_CURVE_TANGENT,216Entity::NO_ENTITY, Entity::NO_ENTITY,217arc, e->h, /*other=*/arcFinish, /*other2=*/(dtheta < 0));218}219}
220
221//-----------------------------------------------------------------------------
222// If a point in the same group as hpt, and numerically coincident with hpt,
223// happens to exist, then constrain that point coincident to hpt.
224//-----------------------------------------------------------------------------
225void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) {226Entity *pt;227pt = SK.GetEntity(hpt);228Vector ev, ptv;229ptv = pt->PointGetNum();230
231for(Entity &e : SK.entity) {232if(e.h == pt->h) continue;233if(!e.IsPoint()) continue;234if(e.group != pt->group) continue;235if(e.workplane != pt->workplane) continue;236
237ev = e.PointGetNum();238if(!ev.Equals(ptv)) continue;239
240Constraint::ConstrainCoincident(hpt, e.h);241break;242}243}
244
245//-----------------------------------------------------------------------------
246// A single point must be selected when this function is called. We find two
247// non-construction line segments that join at this point, and create a
248// tangent arc joining them.
249//-----------------------------------------------------------------------------
250void GraphicsWindow::MakeTangentArc() {251if(!LockedInWorkplane()) {252Error(_("Must be sketching in workplane to create tangent arc."));253return;254}255
256// The point corresponding to the vertex to be rounded.257Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum();258ClearSelection();259
260// First, find two requests (that are not construction, and that are261// in our group and workplane) that generate entities that have an262// endpoint at our vertex to be rounded.263int i, c = 0;264Entity *ent[2];265Request *req[2];266hRequest hreq[2];267hEntity hent[2];268bool pointf[2];269for(auto &r : SK.request) {270if(r.group != activeGroup)271continue;272if(r.workplane != ActiveWorkplane())273continue;274if(r.construction)275continue;276if(r.type != Request::Type::LINE_SEGMENT && r.type != Request::Type::ARC_OF_CIRCLE) {277continue;278}279
280Entity *e = SK.GetEntity(r.h.entity(0));281Vector ps = e->EndpointStart(),282pf = e->EndpointFinish();283
284if(ps.Equals(pshared) || pf.Equals(pshared)) {285if(c < 2) {286// We record the entity and request and their handles,287// and whether the vertex to be rounded is the start or288// finish of this entity.289ent[c] = e;290hent[c] = e->h;291req[c] = &r;292hreq[c] = r.h;293pointf[c] = (pf.Equals(pshared));294}295c++;296}297}298if(c != 2) {299Error(_("To create a tangent arc, select a point where two "300"non-construction lines or circles in this group and "301"workplane join."));302return;303}304
305Entity *wrkpl = SK.GetEntity(ActiveWorkplane());306Vector wn = wrkpl->Normal()->NormalN();307
308// Based on these two entities, we make the objects that we'll use to309// numerically find the tangent arc.310ParametricCurve pc[2];311pc[0].MakeFromEntity(ent[0]->h, pointf[0]);312pc[1].MakeFromEntity(ent[1]->h, pointf[1]);313
314// And thereafter we mustn't touch the entity or req ptrs,315// because the new requests/entities we add might force a316// realloc.317memset(ent, 0, sizeof(ent));318memset(req, 0, sizeof(req));319
320Vector pinter;321double r = 0.0, vv = 0.0;322// We now do Newton iterations to find the tangent arc, and its positions323// t back along the two curves, starting from shared point of the curves324// at t = 0. Lots of iterations helps convergence, and this is still325// ~10 ms for everything.326int iters = 1000;327double t[2] = { 0, 0 }, tp[2];328for(i = 0; i < iters + 20; i++) {329Vector p0 = pc[0].PointAt(t[0]),330p1 = pc[1].PointAt(t[1]),331t0 = pc[0].TangentAt(t[0]),332t1 = pc[1].TangentAt(t[1]);333
334pinter = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),335p1, p1.Plus(t1),336NULL, NULL, NULL);337
338// The sign of vv determines whether shortest distance is339// clockwise or anti-clockwise.340Vector v = (wn.Cross(t0)).WithMagnitude(1);341vv = t1.Dot(v);342
343double dot = (t0.WithMagnitude(1)).Dot(t1.WithMagnitude(1));344double theta = acos(dot);345
346if(SS.tangentArcManual) {347r = SS.tangentArcRadius;348} else {349r = 200/scale;350// Set the radius so that no more than one third of the351// line segment disappears.352r = min(r, pc[0].LengthForAuto()*tan(theta/2));353r = min(r, pc[1].LengthForAuto()*tan(theta/2));;354}355// We are source-stepping the radius, to improve convergence. So356// ramp that for most of the iterations, and then do a few at357// the end with that constant for polishing.358if(i < iters) {359r *= 0.1 + 0.9*i/((double)iters);360}361
362// The distance from the intersection of the lines to the endpoint363// of the arc, along each line.364double el = r/tan(theta/2);365
366// Compute the endpoints of the arc, for each curve367Vector pa0 = pinter.Plus(t0.WithMagnitude(el)),368pa1 = pinter.Plus(t1.WithMagnitude(el));369
370tp[0] = t[0];371tp[1] = t[1];372
373// And convert those points to parameter values along the curve.374t[0] += (pa0.Minus(p0)).DivProjected(t0);375t[1] += (pa1.Minus(p1)).DivProjected(t1);376}377
378// Stupid check for convergence, and for an out of range result (as379// we would get, for example, if the line is too short to fit the380// rounding arc).381if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 ||382t[0] < 0.01 || t[1] < 0.01 ||383t[0] > 0.99 || t[1] > 0.99 ||384IsReasonable(t[0]) || IsReasonable(t[1]))385{386Error(_("Couldn't round this corner. Try a smaller radius, or try "387"creating the desired geometry by hand with tangency "388"constraints."));389return;390}391
392// Compute the location of the center of the arc393Vector center = pc[0].PointAt(t[0]),394v0inter = pinter.Minus(center);395int a, b;396if(vv < 0) {397a = 1; b = 2;398center = center.Minus(v0inter.Cross(wn).WithMagnitude(r));399} else {400a = 2; b = 1;401center = center.Plus(v0inter.Cross(wn).WithMagnitude(r));402}403
404SS.UndoRemember();405
406if (SS.tangentArcModify) {407// Delete the coincident constraint for the removed point.408SK.constraint.ClearTags();409for(i = 0; i < SK.constraint.n; i++) {410Constraint *cs = &(SK.constraint[i]);411if(cs->group != activeGroup) continue;412if(cs->workplane != ActiveWorkplane()) continue;413if(cs->type != Constraint::Type::POINTS_COINCIDENT) continue;414if (SK.GetEntity(cs->ptA)->PointGetNum().Equals(pshared)) {415cs->tag = 1;416}417}418SK.constraint.RemoveTagged();419} else {420// Make the original entities construction, or delete them421// entirely, according to user preference.422SK.GetRequest(hreq[0])->construction = true;423SK.GetRequest(hreq[1])->construction = true;424}425
426// Create and position the new tangent arc.427hRequest harc = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);428Entity *earc = SK.GetEntity(harc.entity(0));429hEntity hearc = earc->h;430
431SK.GetEntity(earc->point[0])->PointForceTo(center);432SK.GetEntity(earc->point[a])->PointForceTo(pc[0].PointAt(t[0]));433SK.GetEntity(earc->point[b])->PointForceTo(pc[1].PointAt(t[1]));434
435earc = NULL;436
437// Modify or duplicate the original entities and connect them to the tangent arc.438pc[0].CreateRequestTrimmedTo(t[0], SS.tangentArcModify,439hent[0], hearc, /*arcFinish=*/(b == 1), pointf[0]);440pc[1].CreateRequestTrimmedTo(t[1], SS.tangentArcModify,441hent[1], hearc, /*arcFinish=*/(a == 1), pointf[1]);442}
443
444hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) {445// Save the original endpoints, since we're about to delete this entity.446Entity *e01 = SK.GetEntity(he);447hEntity hep0 = e01->point[0], hep1 = e01->point[1];448Vector p0 = SK.GetEntity(hep0)->PointGetNum(),449p1 = SK.GetEntity(hep1)->PointGetNum();450
451// Add the two line segments this one gets split into.452hRequest r0i = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false),453ri1 = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false);454// Don't get entities till after adding, realloc issues455
456Entity *e0i = SK.GetEntity(r0i.entity(0)),457*ei1 = SK.GetEntity(ri1.entity(0));458
459SK.GetEntity(e0i->point[0])->PointForceTo(p0);460SK.GetEntity(e0i->point[1])->PointForceTo(pinter);461SK.GetEntity(ei1->point[0])->PointForceTo(pinter);462SK.GetEntity(ei1->point[1])->PointForceTo(p1);463
464ReplacePointInConstraints(hep0, e0i->point[0]);465ReplacePointInConstraints(hep1, ei1->point[1]);466Constraint::ConstrainCoincident(e0i->point[1], ei1->point[0]);467return e0i->point[1];468}
469
470hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) {471Entity *circle = SK.GetEntity(he);472if(circle->type == Entity::Type::CIRCLE) {473// Start with an unbroken circle, split it into a 360 degree arc.474Vector center = SK.GetEntity(circle->point[0])->PointGetNum();475
476circle = NULL; // shortly invalid!477hRequest hr = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);478
479Entity *arc = SK.GetEntity(hr.entity(0));480
481SK.GetEntity(arc->point[0])->PointForceTo(center);482SK.GetEntity(arc->point[1])->PointForceTo(pinter);483SK.GetEntity(arc->point[2])->PointForceTo(pinter);484
485Constraint::ConstrainCoincident(arc->point[1], arc->point[2]);486return arc->point[1];487} else {488// Start with an arc, break it in to two arcs489hEntity hc = circle->point[0],490hs = circle->point[1],491hf = circle->point[2];492Vector center = SK.GetEntity(hc)->PointGetNum(),493start = SK.GetEntity(hs)->PointGetNum(),494finish = SK.GetEntity(hf)->PointGetNum();495
496circle = NULL; // shortly invalid!497hRequest hr0 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false),498hr1 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);499
500Entity *arc0 = SK.GetEntity(hr0.entity(0)),501*arc1 = SK.GetEntity(hr1.entity(0));502
503SK.GetEntity(arc0->point[0])->PointForceTo(center);504SK.GetEntity(arc0->point[1])->PointForceTo(start);505SK.GetEntity(arc0->point[2])->PointForceTo(pinter);506
507SK.GetEntity(arc1->point[0])->PointForceTo(center);508SK.GetEntity(arc1->point[1])->PointForceTo(pinter);509SK.GetEntity(arc1->point[2])->PointForceTo(finish);510
511ReplacePointInConstraints(hs, arc0->point[1]);512ReplacePointInConstraints(hf, arc1->point[2]);513Constraint::ConstrainCoincident(arc0->point[2], arc1->point[1]);514return arc0->point[2];515}516}
517
518hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) {519// Save the original endpoints, since we're about to delete this entity.520Entity *e01 = SK.GetEntity(he);521SBezierList sbl = {};522e01->GenerateBezierCurves(&sbl);523
524hEntity hep0 = e01->point[0],525hep1 = e01->point[3+e01->extraPoints],526hep0n = Entity::NO_ENTITY, // the new start point527hep1n = Entity::NO_ENTITY, // the new finish point528hepin = Entity::NO_ENTITY; // the intersection point529
530// The curve may consist of multiple cubic segments. So find which one531// contains the intersection point.532double t;533int i, j;534for(i = 0; i < sbl.l.n; i++) {535SBezier *sb = &(sbl.l[i]);536ssassert(sb->deg == 3, "Expected a cubic bezier");537
538sb->ClosestPointTo(pinter, &t, /*mustConverge=*/false);539if(pinter.Equals(sb->PointAt(t))) {540// Split that segment at the intersection.541SBezier b0i, bi1, b01 = *sb;542b01.SplitAt(t, &b0i, &bi1);543
544// Add the two cubic segments this one gets split into.545hRequest r0i = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false),546ri1 = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false);547// Don't get entities till after adding, realloc issues548
549Entity *e0i = SK.GetEntity(r0i.entity(0)),550*ei1 = SK.GetEntity(ri1.entity(0));551
552for(j = 0; j <= 3; j++) {553SK.GetEntity(e0i->point[j])->PointForceTo(b0i.ctrl[j]);554}555for(j = 0; j <= 3; j++) {556SK.GetEntity(ei1->point[j])->PointForceTo(bi1.ctrl[j]);557}558
559Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]);560if(i == 0) hep0n = e0i->point[0];561hep1n = ei1->point[3];562hepin = e0i->point[3];563} else {564hRequest r = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false);565Entity *e = SK.GetEntity(r.entity(0));566
567for(j = 0; j <= 3; j++) {568SK.GetEntity(e->point[j])->PointForceTo(sb->ctrl[j]);569}570
571if(i == 0) hep0n = e->point[0];572hep1n = e->point[3];573}574}575
576sbl.Clear();577
578ReplacePointInConstraints(hep0, hep0n);579ReplacePointInConstraints(hep1, hep1n);580return hepin;581}
582
583hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) {584Entity *e = SK.GetEntity(he);585Entity::Type entityType = e->type;586
587hEntity ret;588if(e->IsCircle()) {589ret = SplitCircle(he, pinter);590} else if(e->type == Entity::Type::LINE_SEGMENT) {591ret = SplitLine(he, pinter);592} else if(e->type == Entity::Type::CUBIC || e->type == Entity::Type::CUBIC_PERIODIC) {593ret = SplitCubic(he, pinter);594} else {595Error(_("Couldn't split this entity; lines, circles, or cubics only."));596return Entity::NO_ENTITY;597}598
599// Finally, delete the request that generated the original entity.600Request::Type reqType = EntReqTable::GetRequestForEntity(entityType);601SK.request.ClearTags();602for(auto &r : SK.request) {603if(r.group != activeGroup)604continue;605if(r.type != reqType)606continue;607
608// If the user wants to keep the old entities around, they can just609// mark them construction first.610if(he == r.h.entity(0) && !r.construction) {611r.tag = 1;612break;613}614}615DeleteTaggedRequests();616
617return ret;618}
619
620void GraphicsWindow::SplitLinesOrCurves() {621if(!LockedInWorkplane()) {622Error(_("Must be sketching in workplane to split."));623return;624}625
626GroupSelection();627int n = gs.lineSegments + gs.circlesOrArcs + gs.cubics + gs.periodicCubics;628if(!((n == 2 && gs.points == 0) || (n == 1 && gs.points == 1))) {629Error(_("Select two entities that intersect each other "630"(e.g. two lines/circles/arcs or a line/circle/arc and a point)."));631return;632}633
634bool splitAtPoint = (gs.points == 1);635hEntity ha = gs.entity[0],636hb = splitAtPoint ? gs.point[0] : gs.entity[1];637
638Entity *ea = SK.GetEntity(ha),639*eb = SK.GetEntity(hb);640SPointList inters = {};641SBezierList sbla = {},642sblb = {};643Vector pi = Vector::From(0, 0, 0);644
645SK.constraint.ClearTags();646
647// First, decide the point where we're going to make the split.648bool foundInters = false;649if(splitAtPoint) {650// One of the entities is a point, and this point must be on the other entity.651// Verify that a corresponding point-coincident constraint exists for the point/entity.652Vector p0, p1;653if(ea->type == Entity::Type::LINE_SEGMENT) {654p0 = ea->EndpointStart();655p1 = ea->EndpointFinish();656}657
658for(Constraint &c : SK.constraint) {659if(c.ptA.request() == hb.request() &&660c.entityA.request() == ha.request()) {661pi = SK.GetEntity(c.ptA)->PointGetNum();662
663if(ea->type == Entity::Type::LINE_SEGMENT && !pi.OnLineSegment(p0, p1)) {664// The point isn't between line endpoints, so there isn't an actual665// intersection.666continue;667}668
669c.tag = 1;670foundInters = true;671break;672}673}674} else {675// Compute the possibly-rational Bezier curves for each of these non-point entities...676ea->GenerateBezierCurves(&sbla);677eb->GenerateBezierCurves(&sblb);678// ... and then compute the points where they intersect, based on those curves.679sbla.AllIntersectionsWith(&sblb, &inters);680
681// If there's multiple points, then take the one closest to the mouse pointer.682if(!inters.l.IsEmpty()) {683double dmin = VERY_POSITIVE;684SPoint *sp;685for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) {686double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition);687if(d < dmin) {688dmin = d;689pi = sp->p;690}691}692}693
694foundInters = true;695}696
697// Then, actually split the entities.698if(foundInters) {699SS.UndoRemember();700
701// Remove any constraints we're going to replace.702SK.constraint.RemoveTagged();703
704hEntity hia = SplitEntity(ha, pi),705hib = {};706// SplitEntity adds the coincident constraints to join the split halves707// of each original entity; and then we add the constraint to join708// the two entities together at the split point.709if(splitAtPoint) {710// Remove datum point, as it has now been superseded by the split point.711SK.request.ClearTags();712for(Request &r : SK.request) {713if(r.h == hb.request()) {714if(r.type == Request::Type::DATUM_POINT) {715// Delete datum point.716r.tag = 1;717FixConstraintsForRequestBeingDeleted(r.h);718} else {719// Add constraint if not datum point, but endpoint of line/arc etc.720Constraint::ConstrainCoincident(hia, hb);721}722break;723}724}725SK.request.RemoveTagged();726} else {727// Split second non-point entity and add constraint.728hib = SplitEntity(hb, pi);729if(hia.v && hib.v) {730Constraint::ConstrainCoincident(hia, hib);731}732}733} else {734Error(_("Can't split; no intersection found."));735return;736}737
738// All done, clean up and regenerate.739inters.Clear();740sbla.Clear();741sblb.Clear();742ClearSelection();743}
744