Solvespace

Форк
0
/
modify.cpp 
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
//-----------------------------------------------------------------------------
14
void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {
15
    for(auto &c : SK.constraint) {
16
        if(c.ptA == oldpt)
17
            c.ptA = newpt;
18
        if(c.ptB == oldpt)
19
            c.ptB = newpt;
20
    }
21
}
22

23
//-----------------------------------------------------------------------------
24
// Remove constraints on hpt. Useful when removing bezier points.
25
//-----------------------------------------------------------------------------
26
void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) {
27
    SK.constraint.ClearTags();
28
    for(auto &c : SK.constraint) {
29
        if(c.ptA == hpt || c.ptB == hpt) {
30
            c.tag = 1;
31
            (SS.deleted.constraints)++;
32
            if(c.type != Constraint::Type::POINTS_COINCIDENT &&
33
               c.type != Constraint::Type::HORIZONTAL &&
34
               c.type != Constraint::Type::VERTICAL)
35
            {
36
                (SS.deleted.nonTrivialConstraints)++;
37
            }
38
        }
39
    }
40
    SK.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
//-----------------------------------------------------------------------------
49
void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) {
50
    Request *r = SK.GetRequest(hr);
51
    if(r->group != SS.GW.activeGroup) return;
52

53
    for(Entity &e : SK.entity) {
54
        if(!(e.h.isFromRequest())) continue;
55
        if(e.h.request() != hr) continue;
56

57
        if(e.type != Entity::Type::POINT_IN_2D &&
58
           e.type != Entity::Type::POINT_IN_3D)
59
        {
60
            continue;
61
        }
62

63
        // This is a point generated by the request being deleted; so fix
64
        // the constraints for that.
65
        FixConstraintsForPointBeingDeleted(e.h);
66
    }
67
}
68
void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {
69
    List<hEntity> ld = {};
70

71
    SK.constraint.ClearTags();
72
    for(Constraint &c : SK.constraint) {
73
        if(c.type != Constraint::Type::POINTS_COINCIDENT) continue;
74
        if(c.group != SS.GW.activeGroup) continue;
75

76
        if(c.ptA == hpt) {
77
            ld.Add(&(c.ptB));
78
            c.tag = 1;
79
        }
80
        if(c.ptB == hpt) {
81
            ld.Add(&(c.ptA));
82
            c.tag = 1;
83
        }
84
    }
85
    // Remove constraints without waiting for regeneration; this way
86
    // if another point takes the place of the deleted one (e.g. when
87
    // removing control points of a bezier) the constraint doesn't
88
    // spuriously move. Similarly, subsequent calls of this function
89
    // (if multiple coincident points are getting deleted) will work
90
    // correctly.
91
    SK.constraint.RemoveTagged();
92

93
    // If more than one point was constrained coincident with hpt, then
94
    // those two points were implicitly coincident with each other. By
95
    // deleting hpt (and all constraints that mention it), we will delete
96
    // that relationship. So put it back here now.
97
    for(int i = 1; i < ld.n; i++) {
98
        Constraint::ConstrainCoincident(ld[i-1], ld[i]);
99
    }
100
    ld.Clear();
101
}
102

103
//-----------------------------------------------------------------------------
104
// A curve by its parametric equation, helper functions for computing tangent
105
// arcs by a numerical method.
106
//-----------------------------------------------------------------------------
107
void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) {
108
    *this = {};
109
    Entity *e = SK.GetEntity(he);
110
    if(e->type == Entity::Type::LINE_SEGMENT) {
111
        isLine = true;
112
        p0 = e->EndpointStart(),
113
        p1 = e->EndpointFinish();
114
        if(reverse) {
115
            swap(p0, p1);
116
        }
117
    } else if(e->type == Entity::Type::ARC_OF_CIRCLE) {
118
        isLine = false;
119
        p0 = SK.GetEntity(e->point[0])->PointGetNum();
120
        Vector pe = SK.GetEntity(e->point[1])->PointGetNum();
121
        r = (pe.Minus(p0)).Magnitude();
122
        e->ArcGetAngles(&theta0, &theta1, &dtheta);
123
        if(reverse) {
124
            swap(theta0, theta1);
125
            dtheta = -dtheta;
126
        }
127
        EntityBase *wrkpln = SK.GetEntity(e->workplane)->Normal();
128
        u = wrkpln->NormalU();
129
        v = wrkpln->NormalV();
130
    } else ssassert(false, "Unexpected entity type");
131
}
132
double GraphicsWindow::ParametricCurve::LengthForAuto() {
133
    if(isLine) {
134
        // Allow a third of the line to disappear with auto radius
135
        return (p1.Minus(p0)).Magnitude() / 3;
136
    } else {
137
        // But only a twentieth of the arc; shorter means fewer numerical
138
        // problems since the curve is more linear over shorter sections.
139
        return (fabs(dtheta)*r)/20;
140
    }
141
}
142
Vector GraphicsWindow::ParametricCurve::PointAt(double t) {
143
    if(isLine) {
144
        return p0.Plus((p1.Minus(p0)).ScaledBy(t));
145
    } else {
146
        double theta = theta0 + dtheta*t;
147
        return p0.Plus(u.ScaledBy(r*cos(theta)).Plus(v.ScaledBy(r*sin(theta))));
148
    }
149
}
150
Vector GraphicsWindow::ParametricCurve::TangentAt(double t) {
151
    if(isLine) {
152
        return p1.Minus(p0);
153
    } else {
154
        double theta = theta0 + dtheta*t;
155
        Vector t =  u.ScaledBy(-r*sin(theta)).Plus(v.ScaledBy(r*cos(theta)));
156
        t = t.ScaledBy(dtheta);
157
        return 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
 */
168
void GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t,
169
    bool reuseOrig, hEntity orig, hEntity arc, bool arcFinish, bool pointf)
170
{
171
    hRequest hr;
172
    Entity *e;
173
    if(isLine) {
174
        if (reuseOrig) {
175
            e = SK.GetEntity(orig);
176
            int i = pointf ? 1 : 0;
177
            SK.GetEntity(e->point[i])->PointForceTo(PointAt(t));
178
            ConstrainPointIfCoincident(e->point[i]);
179
        } else {
180
            hr = SS.GW.AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false),
181
            e = SK.GetEntity(hr.entity(0));
182
            SK.GetEntity(e->point[0])->PointForceTo(PointAt(t));
183
            SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));
184
            ConstrainPointIfCoincident(e->point[0]);
185
            ConstrainPointIfCoincident(e->point[1]);
186
            Constraint::Constrain(Constraint::Type::PT_ON_LINE,
187
                hr.entity(1), Entity::NO_ENTITY, orig);
188
        }
189
        Constraint::Constrain(Constraint::Type::ARC_LINE_TANGENT,
190
            Entity::NO_ENTITY, Entity::NO_ENTITY,
191
            arc, e->h, /*other=*/arcFinish, /*other2=*/false);
192
    } else {
193
        if (reuseOrig) {
194
            e = SK.GetEntity(orig);
195
            int i = pointf ? 2 : 1;
196
            SK.GetEntity(e->point[i])->PointForceTo(PointAt(t));
197
            ConstrainPointIfCoincident(e->point[i]);
198
        } else {
199
            hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false),
200
            e = SK.GetEntity(hr.entity(0));
201
            SK.GetEntity(e->point[0])->PointForceTo(p0);
202
            if(dtheta > 0) {
203
                SK.GetEntity(e->point[1])->PointForceTo(PointAt(t));
204
                SK.GetEntity(e->point[2])->PointForceTo(PointAt(1));
205
            } else {
206
                SK.GetEntity(e->point[2])->PointForceTo(PointAt(t));
207
                SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));
208
            }
209
            ConstrainPointIfCoincident(e->point[0]);
210
            ConstrainPointIfCoincident(e->point[1]);
211
            ConstrainPointIfCoincident(e->point[2]);
212
        }
213
        // The tangency constraint alone is enough to fully constrain it,
214
        // so there's no need for more.
215
        Constraint::Constrain(Constraint::Type::CURVE_CURVE_TANGENT,
216
            Entity::NO_ENTITY, Entity::NO_ENTITY,
217
            arc, 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
//-----------------------------------------------------------------------------
225
void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) {
226
    Entity *pt;
227
    pt = SK.GetEntity(hpt);
228
    Vector ev, ptv;
229
    ptv = pt->PointGetNum();
230

231
    for(Entity &e : SK.entity) {
232
        if(e.h == pt->h) continue;
233
        if(!e.IsPoint()) continue;
234
        if(e.group != pt->group) continue;
235
        if(e.workplane != pt->workplane) continue;
236

237
        ev = e.PointGetNum();
238
        if(!ev.Equals(ptv)) continue;
239

240
        Constraint::ConstrainCoincident(hpt, e.h);
241
        break;
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
//-----------------------------------------------------------------------------
250
void GraphicsWindow::MakeTangentArc() {
251
    if(!LockedInWorkplane()) {
252
        Error(_("Must be sketching in workplane to create tangent arc."));
253
        return;
254
    }
255

256
    // The point corresponding to the vertex to be rounded.
257
    Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum();
258
    ClearSelection();
259

260
    // First, find two requests (that are not construction, and that are
261
    // in our group and workplane) that generate entities that have an
262
    // endpoint at our vertex to be rounded.
263
    int i, c = 0;
264
    Entity *ent[2];
265
    Request *req[2];
266
    hRequest hreq[2];
267
    hEntity hent[2];
268
    bool pointf[2];
269
    for(auto &r : SK.request) {
270
        if(r.group != activeGroup)
271
            continue;
272
        if(r.workplane != ActiveWorkplane())
273
            continue;
274
        if(r.construction)
275
            continue;
276
        if(r.type != Request::Type::LINE_SEGMENT && r.type != Request::Type::ARC_OF_CIRCLE) {
277
            continue;
278
        }
279

280
        Entity *e = SK.GetEntity(r.h.entity(0));
281
        Vector ps = e->EndpointStart(),
282
               pf = e->EndpointFinish();
283

284
        if(ps.Equals(pshared) || pf.Equals(pshared)) {
285
            if(c < 2) {
286
                // We record the entity and request and their handles,
287
                // and whether the vertex to be rounded is the start or
288
                // finish of this entity.
289
                ent[c] = e;
290
                hent[c] = e->h;
291
                req[c] = &r;
292
                hreq[c] = r.h;
293
                pointf[c] = (pf.Equals(pshared));
294
            }
295
            c++;
296
        }
297
    }
298
    if(c != 2) {
299
        Error(_("To create a tangent arc, select a point where two "
300
                "non-construction lines or circles in this group and "
301
                "workplane join."));
302
        return;
303
    }
304

305
    Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
306
    Vector wn = wrkpl->Normal()->NormalN();
307

308
    // Based on these two entities, we make the objects that we'll use to
309
    // numerically find the tangent arc.
310
    ParametricCurve pc[2];
311
    pc[0].MakeFromEntity(ent[0]->h, pointf[0]);
312
    pc[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 a
316
    // realloc.
317
    memset(ent, 0, sizeof(ent));
318
    memset(req, 0, sizeof(req));
319

320
    Vector pinter;
321
    double r = 0.0, vv = 0.0;
322
    // We now do Newton iterations to find the tangent arc, and its positions
323
    // t back along the two curves, starting from shared point of the curves
324
    // at t = 0. Lots of iterations helps convergence, and this is still
325
    // ~10 ms for everything.
326
    int iters = 1000;
327
    double t[2] = { 0, 0 }, tp[2];
328
    for(i = 0; i < iters + 20; i++) {
329
        Vector p0 = pc[0].PointAt(t[0]),
330
               p1 = pc[1].PointAt(t[1]),
331
               t0 = pc[0].TangentAt(t[0]),
332
               t1 = pc[1].TangentAt(t[1]);
333

334
        pinter = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
335
                                               p1, p1.Plus(t1),
336
                                               NULL, NULL, NULL);
337

338
        // The sign of vv determines whether shortest distance is
339
        // clockwise or anti-clockwise.
340
        Vector v = (wn.Cross(t0)).WithMagnitude(1);
341
        vv = t1.Dot(v);
342

343
        double dot = (t0.WithMagnitude(1)).Dot(t1.WithMagnitude(1));
344
        double theta = acos(dot);
345

346
        if(SS.tangentArcManual) {
347
            r = SS.tangentArcRadius;
348
        } else {
349
            r = 200/scale;
350
            // Set the radius so that no more than one third of the
351
            // line segment disappears.
352
            r = min(r, pc[0].LengthForAuto()*tan(theta/2));
353
            r = min(r, pc[1].LengthForAuto()*tan(theta/2));;
354
        }
355
        // We are source-stepping the radius, to improve convergence. So
356
        // ramp that for most of the iterations, and then do a few at
357
        // the end with that constant for polishing.
358
        if(i < iters) {
359
            r *= 0.1 + 0.9*i/((double)iters);
360
        }
361

362
        // The distance from the intersection of the lines to the endpoint
363
        // of the arc, along each line.
364
        double el = r/tan(theta/2);
365

366
        // Compute the endpoints of the arc, for each curve
367
        Vector pa0 = pinter.Plus(t0.WithMagnitude(el)),
368
               pa1 = pinter.Plus(t1.WithMagnitude(el));
369

370
        tp[0] = t[0];
371
        tp[1] = t[1];
372

373
        // And convert those points to parameter values along the curve.
374
        t[0] += (pa0.Minus(p0)).DivProjected(t0);
375
        t[1] += (pa1.Minus(p1)).DivProjected(t1);
376
    }
377

378
    // Stupid check for convergence, and for an out of range result (as
379
    // we would get, for example, if the line is too short to fit the
380
    // rounding arc).
381
    if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 ||
382
        t[0] < 0.01 || t[1] < 0.01 ||
383
        t[0] > 0.99 || t[1] > 0.99 ||
384
        IsReasonable(t[0]) || IsReasonable(t[1]))
385
    {
386
        Error(_("Couldn't round this corner. Try a smaller radius, or try "
387
                "creating the desired geometry by hand with tangency "
388
                "constraints."));
389
        return;
390
    }
391

392
    // Compute the location of the center of the arc
393
    Vector center = pc[0].PointAt(t[0]),
394
           v0inter = pinter.Minus(center);
395
    int a, b;
396
    if(vv < 0) {
397
        a = 1; b = 2;
398
        center = center.Minus(v0inter.Cross(wn).WithMagnitude(r));
399
    } else {
400
        a = 2; b = 1;
401
        center = center.Plus(v0inter.Cross(wn).WithMagnitude(r));
402
    }
403

404
    SS.UndoRemember();
405

406
    if (SS.tangentArcModify) {
407
        // Delete the coincident constraint for the removed point.
408
        SK.constraint.ClearTags();
409
        for(i = 0; i < SK.constraint.n; i++) {
410
            Constraint *cs = &(SK.constraint[i]);
411
            if(cs->group != activeGroup) continue;
412
            if(cs->workplane != ActiveWorkplane()) continue;
413
            if(cs->type != Constraint::Type::POINTS_COINCIDENT) continue;
414
            if (SK.GetEntity(cs->ptA)->PointGetNum().Equals(pshared)) {
415
            cs->tag = 1;
416
            }
417
        }
418
        SK.constraint.RemoveTagged();
419
    } else {
420
        // Make the original entities construction, or delete them
421
        // entirely, according to user preference.
422
        SK.GetRequest(hreq[0])->construction = true;
423
        SK.GetRequest(hreq[1])->construction = true;
424
    }
425

426
    // Create and position the new tangent arc.
427
    hRequest harc = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);
428
    Entity *earc = SK.GetEntity(harc.entity(0));
429
    hEntity hearc = earc->h;
430

431
    SK.GetEntity(earc->point[0])->PointForceTo(center);
432
    SK.GetEntity(earc->point[a])->PointForceTo(pc[0].PointAt(t[0]));
433
    SK.GetEntity(earc->point[b])->PointForceTo(pc[1].PointAt(t[1]));
434

435
    earc = NULL;
436

437
    // Modify or duplicate the original entities and connect them to the tangent arc.
438
    pc[0].CreateRequestTrimmedTo(t[0], SS.tangentArcModify,
439
                hent[0], hearc, /*arcFinish=*/(b == 1), pointf[0]);
440
    pc[1].CreateRequestTrimmedTo(t[1], SS.tangentArcModify,
441
                hent[1], hearc, /*arcFinish=*/(a == 1), pointf[1]);
442
}
443

444
hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) {
445
    // Save the original endpoints, since we're about to delete this entity.
446
    Entity *e01 = SK.GetEntity(he);
447
    hEntity hep0 = e01->point[0], hep1 = e01->point[1];
448
    Vector p0 = SK.GetEntity(hep0)->PointGetNum(),
449
           p1 = SK.GetEntity(hep1)->PointGetNum();
450

451
    // Add the two line segments this one gets split into.
452
    hRequest r0i = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false),
453
             ri1 = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false);
454
    // Don't get entities till after adding, realloc issues
455

456
    Entity *e0i = SK.GetEntity(r0i.entity(0)),
457
           *ei1 = SK.GetEntity(ri1.entity(0));
458

459
    SK.GetEntity(e0i->point[0])->PointForceTo(p0);
460
    SK.GetEntity(e0i->point[1])->PointForceTo(pinter);
461
    SK.GetEntity(ei1->point[0])->PointForceTo(pinter);
462
    SK.GetEntity(ei1->point[1])->PointForceTo(p1);
463

464
    ReplacePointInConstraints(hep0, e0i->point[0]);
465
    ReplacePointInConstraints(hep1, ei1->point[1]);
466
    Constraint::ConstrainCoincident(e0i->point[1], ei1->point[0]);
467
    return e0i->point[1];
468
}
469

470
hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) {
471
    Entity *circle = SK.GetEntity(he);
472
    if(circle->type == Entity::Type::CIRCLE) {
473
        // Start with an unbroken circle, split it into a 360 degree arc.
474
        Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
475

476
        circle = NULL; // shortly invalid!
477
        hRequest hr = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);
478

479
        Entity *arc = SK.GetEntity(hr.entity(0));
480

481
        SK.GetEntity(arc->point[0])->PointForceTo(center);
482
        SK.GetEntity(arc->point[1])->PointForceTo(pinter);
483
        SK.GetEntity(arc->point[2])->PointForceTo(pinter);
484

485
        Constraint::ConstrainCoincident(arc->point[1], arc->point[2]);
486
        return arc->point[1];
487
    } else {
488
        // Start with an arc, break it in to two arcs
489
        hEntity hc = circle->point[0],
490
                hs = circle->point[1],
491
                hf = circle->point[2];
492
        Vector center = SK.GetEntity(hc)->PointGetNum(),
493
               start  = SK.GetEntity(hs)->PointGetNum(),
494
               finish = SK.GetEntity(hf)->PointGetNum();
495

496
        circle = NULL; // shortly invalid!
497
        hRequest hr0 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false),
498
                 hr1 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false);
499

500
        Entity *arc0 = SK.GetEntity(hr0.entity(0)),
501
               *arc1 = SK.GetEntity(hr1.entity(0));
502

503
        SK.GetEntity(arc0->point[0])->PointForceTo(center);
504
        SK.GetEntity(arc0->point[1])->PointForceTo(start);
505
        SK.GetEntity(arc0->point[2])->PointForceTo(pinter);
506

507
        SK.GetEntity(arc1->point[0])->PointForceTo(center);
508
        SK.GetEntity(arc1->point[1])->PointForceTo(pinter);
509
        SK.GetEntity(arc1->point[2])->PointForceTo(finish);
510

511
        ReplacePointInConstraints(hs, arc0->point[1]);
512
        ReplacePointInConstraints(hf, arc1->point[2]);
513
        Constraint::ConstrainCoincident(arc0->point[2], arc1->point[1]);
514
        return arc0->point[2];
515
    }
516
}
517

518
hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) {
519
    // Save the original endpoints, since we're about to delete this entity.
520
    Entity *e01 = SK.GetEntity(he);
521
    SBezierList sbl = {};
522
    e01->GenerateBezierCurves(&sbl);
523

524
    hEntity hep0 = e01->point[0],
525
            hep1 = e01->point[3+e01->extraPoints],
526
            hep0n = Entity::NO_ENTITY, // the new start point
527
            hep1n = Entity::NO_ENTITY, // the new finish point
528
            hepin = Entity::NO_ENTITY; // the intersection point
529

530
    // The curve may consist of multiple cubic segments. So find which one
531
    // contains the intersection point.
532
    double t;
533
    int i, j;
534
    for(i = 0; i < sbl.l.n; i++) {
535
        SBezier *sb = &(sbl.l[i]);
536
        ssassert(sb->deg == 3, "Expected a cubic bezier");
537

538
        sb->ClosestPointTo(pinter, &t, /*mustConverge=*/false);
539
        if(pinter.Equals(sb->PointAt(t))) {
540
            // Split that segment at the intersection.
541
            SBezier b0i, bi1, b01 = *sb;
542
            b01.SplitAt(t, &b0i, &bi1);
543

544
            // Add the two cubic segments this one gets split into.
545
            hRequest r0i = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false),
546
                     ri1 = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false);
547
            // Don't get entities till after adding, realloc issues
548

549
            Entity *e0i = SK.GetEntity(r0i.entity(0)),
550
                   *ei1 = SK.GetEntity(ri1.entity(0));
551

552
            for(j = 0; j <= 3; j++) {
553
                SK.GetEntity(e0i->point[j])->PointForceTo(b0i.ctrl[j]);
554
            }
555
            for(j = 0; j <= 3; j++) {
556
                SK.GetEntity(ei1->point[j])->PointForceTo(bi1.ctrl[j]);
557
            }
558

559
            Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]);
560
            if(i == 0) hep0n = e0i->point[0];
561
            hep1n = ei1->point[3];
562
            hepin = e0i->point[3];
563
        } else {
564
            hRequest r = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false);
565
            Entity *e = SK.GetEntity(r.entity(0));
566

567
            for(j = 0; j <= 3; j++) {
568
                SK.GetEntity(e->point[j])->PointForceTo(sb->ctrl[j]);
569
            }
570

571
            if(i == 0) hep0n = e->point[0];
572
            hep1n = e->point[3];
573
        }
574
    }
575

576
    sbl.Clear();
577

578
    ReplacePointInConstraints(hep0, hep0n);
579
    ReplacePointInConstraints(hep1, hep1n);
580
    return hepin;
581
}
582

583
hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) {
584
    Entity *e = SK.GetEntity(he);
585
    Entity::Type entityType = e->type;
586

587
    hEntity ret;
588
    if(e->IsCircle()) {
589
        ret = SplitCircle(he, pinter);
590
    } else if(e->type == Entity::Type::LINE_SEGMENT) {
591
        ret = SplitLine(he, pinter);
592
    } else if(e->type == Entity::Type::CUBIC || e->type == Entity::Type::CUBIC_PERIODIC) {
593
        ret = SplitCubic(he, pinter);
594
    } else {
595
        Error(_("Couldn't split this entity; lines, circles, or cubics only."));
596
        return Entity::NO_ENTITY;
597
    }
598

599
    // Finally, delete the request that generated the original entity.
600
    Request::Type reqType = EntReqTable::GetRequestForEntity(entityType);
601
    SK.request.ClearTags();
602
    for(auto &r : SK.request) {
603
        if(r.group != activeGroup)
604
            continue;
605
        if(r.type != reqType)
606
            continue;
607

608
        // If the user wants to keep the old entities around, they can just
609
        // mark them construction first.
610
        if(he == r.h.entity(0) && !r.construction) {
611
            r.tag = 1;
612
            break;
613
        }
614
    }
615
    DeleteTaggedRequests();
616

617
    return ret;
618
}
619

620
void GraphicsWindow::SplitLinesOrCurves() {
621
    if(!LockedInWorkplane()) {
622
        Error(_("Must be sketching in workplane to split."));
623
        return;
624
    }
625

626
    GroupSelection();
627
    int n = gs.lineSegments + gs.circlesOrArcs + gs.cubics + gs.periodicCubics;
628
    if(!((n == 2 && gs.points == 0) || (n == 1 && gs.points == 1))) {
629
        Error(_("Select two entities that intersect each other "
630
                "(e.g. two lines/circles/arcs or a line/circle/arc and a point)."));
631
        return;
632
    }
633

634
    bool splitAtPoint = (gs.points == 1);
635
    hEntity ha = gs.entity[0],
636
            hb = splitAtPoint ? gs.point[0] : gs.entity[1];
637

638
    Entity *ea = SK.GetEntity(ha),
639
           *eb = SK.GetEntity(hb);
640
    SPointList inters = {};
641
    SBezierList sbla = {},
642
                sblb = {};
643
    Vector pi = Vector::From(0, 0, 0);
644

645
    SK.constraint.ClearTags();
646

647
    // First, decide the point where we're going to make the split.
648
    bool foundInters = false;
649
    if(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.
652
        Vector p0, p1;
653
        if(ea->type == Entity::Type::LINE_SEGMENT) {
654
            p0 = ea->EndpointStart();
655
            p1 = ea->EndpointFinish();
656
        }
657

658
        for(Constraint &c : SK.constraint) {
659
            if(c.ptA.request() == hb.request() &&
660
               c.entityA.request() == ha.request()) {
661
                pi = SK.GetEntity(c.ptA)->PointGetNum();
662

663
                if(ea->type == Entity::Type::LINE_SEGMENT && !pi.OnLineSegment(p0, p1)) {
664
                    // The point isn't between line endpoints, so there isn't an actual
665
                    // intersection.
666
                    continue;
667
                }
668

669
                c.tag = 1;
670
                foundInters = true;
671
                break;
672
            }
673
        }
674
    } else {
675
        // Compute the possibly-rational Bezier curves for each of these non-point entities...
676
        ea->GenerateBezierCurves(&sbla);
677
        eb->GenerateBezierCurves(&sblb);
678
        // ... and then compute the points where they intersect, based on those curves.
679
        sbla.AllIntersectionsWith(&sblb, &inters);
680

681
        // If there's multiple points, then take the one closest to the mouse pointer.
682
        if(!inters.l.IsEmpty()) {
683
            double dmin = VERY_POSITIVE;
684
            SPoint *sp;
685
            for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) {
686
                double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition);
687
                if(d < dmin) {
688
                    dmin = d;
689
                    pi = sp->p;
690
                }
691
            }
692
        }
693

694
        foundInters = true;
695
    }
696

697
    // Then, actually split the entities.
698
    if(foundInters) {
699
        SS.UndoRemember();
700

701
        // Remove any constraints we're going to replace.
702
        SK.constraint.RemoveTagged();
703

704
        hEntity hia = SplitEntity(ha, pi),
705
                hib = {};
706
        // SplitEntity adds the coincident constraints to join the split halves
707
        // of each original entity; and then we add the constraint to join
708
        // the two entities together at the split point.
709
        if(splitAtPoint) {
710
            // Remove datum point, as it has now been superseded by the split point.
711
            SK.request.ClearTags();
712
            for(Request &r : SK.request) {
713
                if(r.h == hb.request()) {
714
                    if(r.type == Request::Type::DATUM_POINT) {
715
                        // Delete datum point.
716
                        r.tag = 1;
717
                        FixConstraintsForRequestBeingDeleted(r.h);
718
                    } else {
719
                        // Add constraint if not datum point, but endpoint of line/arc etc.
720
                        Constraint::ConstrainCoincident(hia, hb);
721
                    }
722
                    break;
723
                }
724
            }
725
            SK.request.RemoveTagged();
726
        } else {
727
            // Split second non-point entity and add constraint.
728
            hib = SplitEntity(hb, pi);
729
            if(hia.v && hib.v) {
730
                Constraint::ConstrainCoincident(hia, hib);
731
            }
732
        }
733
    } else {
734
        Error(_("Can't split; no intersection found."));
735
        return;
736
    }
737

738
    // All done, clean up and regenerate.
739
    inters.Clear();
740
    sbla.Clear();
741
    sblb.Clear();
742
    ClearSelection();
743
}
744

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.