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