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