Solvespace

Форк
0
/
group.cpp 
1212 строк · 46.5 Кб
1
//-----------------------------------------------------------------------------
2
// Implementation of the Group class, which represents a set of entities and
3
// constraints that are solved together, in some cases followed by another
4
// operation, like to extrude surfaces from the entities or to step and
5
// repeat them parametrically.
6
//
7
// Copyright 2008-2013 Jonathan Westhues.
8
//-----------------------------------------------------------------------------
9
#include "solvespace.h"
10

11
const hParam   Param::NO_PARAM = { 0 };
12
#define NO_PARAM (Param::NO_PARAM)
13

14
const hGroup Group::HGROUP_REFERENCES = { 1 };
15

16
//-----------------------------------------------------------------------------
17
// The group structure includes pointers to other dynamically-allocated
18
// memory. This clears and frees them all.
19
//-----------------------------------------------------------------------------
20
void Group::Clear() {
21
    polyLoops.Clear();
22
    bezierLoops.Clear();
23
    bezierOpens.Clear();
24
    thisMesh.Clear();
25
    runningMesh.Clear();
26
    thisShell.Clear();
27
    runningShell.Clear();
28
    displayMesh.Clear();
29
    displayOutlines.Clear();
30
    impMesh.Clear();
31
    impShell.Clear();
32
    impEntity.Clear();
33
    // remap is the only one that doesn't get recreated when we regen
34
    remap.clear();
35
}
36

37
void Group::AddParam(IdList<Param,hParam> *param, hParam hp, double v) {
38
    Param pa = {};
39
    pa.h = hp;
40
    pa.val = v;
41

42
    param->Add(&pa);
43
}
44

45
bool Group::IsVisible() {
46
    if(!visible) return false;
47
    Group *active = SK.GetGroup(SS.GW.activeGroup);
48
    if(order > active->order) return false;
49
    return true;
50
}
51

52
size_t Group::GetNumConstraints() {
53
    return std::count_if(SK.constraint.begin(), SK.constraint.end(),
54
                         [&](Constraint const &c) { return c.group == h; });
55
}
56

57
Vector Group::ExtrusionGetVector() {
58
    return Vector::From(h.param(0), h.param(1), h.param(2));
59
}
60

61
void Group::ExtrusionForceVectorTo(const Vector &v) {
62
    SK.GetParam(h.param(0))->val = v.x;
63
    SK.GetParam(h.param(1))->val = v.y;
64
    SK.GetParam(h.param(2))->val = v.z;
65
}
66

67
void Group::MenuGroup(Command id)  {
68
    MenuGroup(id, Platform::Path());
69
}
70

71
void Group::MenuGroup(Command id, Platform::Path linkFile) {
72
    Platform::SettingsRef settings = Platform::GetSettings();
73

74
    Group g = {};
75
    g.visible = true;
76
    g.color = RGBi(100, 100, 100);
77
    g.scale = 1;
78
    g.linkFile = linkFile;
79

80
    SS.GW.GroupSelection();
81
    auto const &gs = SS.GW.gs;
82

83
    switch(id) {
84
        case Command::GROUP_3D:
85
            g.type = Type::DRAWING_3D;
86
            g.name = C_("group-name", "sketch-in-3d");
87
            break;
88

89
        case Command::GROUP_WRKPL:
90
            g.type = Type::DRAWING_WORKPLANE;
91
            g.name = C_("group-name", "sketch-in-plane");
92
            if(gs.points == 1 && gs.n == 1) {
93
                g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO;
94

95
                Vector u = SS.GW.projRight, v = SS.GW.projUp;
96
                u = u.ClosestOrtho();
97
                v = v.Minus(u.ScaledBy(v.Dot(u)));
98
                v = v.ClosestOrtho();
99

100
                g.predef.q = Quaternion::From(u, v);
101
                g.predef.origin = gs.point[0];
102
            } else if(gs.points == 1 && gs.lineSegments == 2 && gs.n == 3) {
103
                g.subtype = Subtype::WORKPLANE_BY_LINE_SEGMENTS;
104

105
                g.predef.origin = gs.point[0];
106
                g.predef.entityB = gs.entity[0];
107
                g.predef.entityC = gs.entity[1];
108

109
                Vector ut = SK.GetEntity(g.predef.entityB)->VectorGetNum();
110
                Vector vt = SK.GetEntity(g.predef.entityC)->VectorGetNum();
111
                ut = ut.WithMagnitude(1);
112
                vt = vt.WithMagnitude(1);
113

114
                if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) {
115
                    swap(ut, vt);
116
                    g.predef.swapUV = true;
117
                }
118
                if(SS.GW.projRight.Dot(ut) < 0) g.predef.negateU = true;
119
                if(SS.GW.projUp.   Dot(vt) < 0) g.predef.negateV = true;
120
            } else if(gs.workplanes == 1 && gs.n == 1) {
121
                if(gs.entity[0].isFromRequest()) {
122
                    Entity *wrkpl = SK.GetEntity(gs.entity[0]);
123
                    Entity *normal = SK.GetEntity(wrkpl->normal);
124
                    g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO;
125
                    g.predef.origin = wrkpl->point[0];
126
                    g.predef.q = normal->NormalGetNum();
127
                } else {
128
                    Group *wrkplg = SK.GetGroup(gs.entity[0].group());
129
                    g.subtype = wrkplg->subtype;
130
                    g.predef.origin = wrkplg->predef.origin;
131
                    if(wrkplg->subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) {
132
                        g.predef.entityB = wrkplg->predef.entityB;
133
                        g.predef.entityC = wrkplg->predef.entityC;
134
                        g.predef.swapUV = wrkplg->predef.swapUV;
135
                        g.predef.negateU = wrkplg->predef.negateU;
136
                        g.predef.negateV = wrkplg->predef.negateV;
137
                    } else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
138
                        g.predef.q = wrkplg->predef.q;
139
                    } else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
140
                        g.predef.q = wrkplg->predef.q;
141
                        g.predef.entityB = wrkplg->predef.entityB;
142
                    } else ssassert(false, "Unexpected workplane subtype");
143
                }
144
            } else if(gs.anyNormals == 1 && gs.points == 1 && gs.n == 2) {
145
                g.subtype       = Subtype::WORKPLANE_BY_POINT_NORMAL;
146
                g.predef.entityB = gs.anyNormal[0];
147
                g.predef.q      = SK.GetEntity(gs.anyNormal[0])->NormalGetNum();
148
                g.predef.origin = gs.point[0];
149
            //} else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
150
            //    g.subtype = Subtype::WORKPLANE_BY_POINT_FACE;
151
            //    g.predef.q      = SK.GetEntity(gs.face[0])->NormalGetNum();
152
            //    g.predef.origin = gs.point[0];
153
            } else {
154
                Error(_("Bad selection for new sketch in workplane. This "
155
                        "group can be created with:\n\n"
156
                        "    * a point (through the point, orthogonal to coordinate axes)\n"
157
                        "    * a point and two line segments (through the point, "
158
                        "parallel to the lines)\n"
159
                        "    * a point and a normal (through the point, "
160
                        "orthogonal to the normal)\n"
161
                        /*"    * a point and a face (through the point, "
162
                        "parallel to the face)\n"*/
163
                        "    * a workplane (copy of the workplane)\n"));
164
                return;
165
            }
166
            break;
167

168
        case Command::GROUP_EXTRUDE:
169
            if(!SS.GW.LockedInWorkplane()) {
170
                Error(_("Activate a workplane (Sketch -> In Workplane) before "
171
                        "extruding. The sketch will be extruded normal to the "
172
                        "workplane."));
173
                return;
174
            }
175
            g.type = Type::EXTRUDE;
176
            g.opA = SS.GW.activeGroup;
177
            g.predef.entityB = SS.GW.ActiveWorkplane();
178
            g.subtype = Subtype::ONE_SIDED;
179
            g.name = C_("group-name", "extrude");
180
            break;
181

182
        case Command::GROUP_LATHE:
183
            if(!SS.GW.LockedInWorkplane()) {
184
                Error(_("Lathe operation can only be applied to planar sketches."));
185
                return;
186
            }
187
            if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
188
                g.predef.origin = gs.point[0];
189
                g.predef.entityB = gs.vector[0];
190
            } else if(gs.lineSegments == 1 && gs.n == 1) {
191
                g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
192
                g.predef.entityB = gs.entity[0];
193
                // since a line segment is a vector
194
            } else {
195
                Error(_("Bad selection for new lathe group. This group can "
196
                        "be created with:\n\n"
197
                        "    * a point and a line segment or normal "
198
                                 "(revolved about an axis parallel to line / "
199
                                 "normal, through point)\n"
200
                        "    * a line segment (revolved about line segment)\n"));
201
                return;
202
            }
203
            g.type = Type::LATHE;
204
            g.opA = SS.GW.activeGroup;
205
            g.name = C_("group-name", "lathe");
206
            break;
207

208
        case Command::GROUP_REVOLVE:
209
            if(!SS.GW.LockedInWorkplane()) {
210
                Error(_("Revolve operation can only be applied to planar sketches."));
211
                return;
212
            }
213
            if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
214
                g.predef.origin  = gs.point[0];
215
                g.predef.entityB = gs.vector[0];
216
            } else if(gs.lineSegments == 1 && gs.n == 1) {
217
                g.predef.origin  = SK.GetEntity(gs.entity[0])->point[0];
218
                g.predef.entityB = gs.entity[0];
219
                // since a line segment is a vector
220
            } else {
221
                Error(_("Bad selection for new revolve group. This group can "
222
                        "be created with:\n\n"
223
                        "    * a point and a line segment or normal "
224
                                 "(revolved about an axis parallel to line / "
225
                                 "normal, through point)\n"
226
                        "    * a line segment (revolved about line segment)\n"));
227
                return;
228
            }
229
            g.type    = Type::REVOLVE;
230
            g.opA     = SS.GW.activeGroup;
231
            g.valA    = 2;
232
            g.subtype = Subtype::ONE_SIDED;
233
            g.name    = C_("group-name", "revolve");
234
            break;
235

236
        case Command::GROUP_HELIX:
237
            if(!SS.GW.LockedInWorkplane()) {
238
                Error(_("Helix operation can only be applied to planar sketches."));
239
                return;
240
            }
241
            if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
242
                g.predef.origin  = gs.point[0];
243
                g.predef.entityB = gs.vector[0];
244
            } else if(gs.lineSegments == 1 && gs.n == 1) {
245
                g.predef.origin  = SK.GetEntity(gs.entity[0])->point[0];
246
                g.predef.entityB = gs.entity[0];
247
                // since a line segment is a vector
248
            } else {
249
                Error(_("Bad selection for new helix group. This group can "
250
                        "be created with:\n\n"
251
                        "    * a point and a line segment or normal "
252
                                 "(revolved about an axis parallel to line / "
253
                                 "normal, through point)\n"
254
                        "    * a line segment (revolved about line segment)\n"));
255
                return;
256
            }
257
            g.type    = Type::HELIX;
258
            g.opA     = SS.GW.activeGroup;
259
            g.valA    = 2;
260
            g.subtype = Subtype::ONE_SIDED;
261
            g.name    = C_("group-name", "helix");
262
            break;
263

264
        case Command::GROUP_ROT: {
265
            if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) {
266
                g.predef.origin = gs.point[0];
267
                Entity *w = SK.GetEntity(SS.GW.ActiveWorkplane());
268
                g.predef.entityB = w->Normal()->h;
269
                g.activeWorkplane = w->h;
270
            } else if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
271
                g.predef.origin = gs.point[0];
272
                g.predef.entityB = gs.vector[0];
273
            } else {
274
                Error(_("Bad selection for new rotation. This group can "
275
                        "be created with:\n\n"
276
                        "    * a point, while locked in workplane (rotate "
277
                              "in plane, about that point)\n"
278
                        "    * a point and a line or a normal (rotate about "
279
                              "an axis through the point, and parallel to "
280
                              "line / normal)\n"));
281
                return;
282
            }
283
            g.type = Type::ROTATE;
284
            g.opA = SS.GW.activeGroup;
285
            g.valA = 3;
286
            g.subtype = Subtype::ONE_SIDED;
287
            g.name = C_("group-name", "rotate");
288
            break;
289
        }
290

291
        case Command::GROUP_TRANS:
292
            g.type = Type::TRANSLATE;
293
            g.opA = SS.GW.activeGroup;
294
            g.valA = 3;
295
            g.subtype = Subtype::ONE_SIDED;
296
            g.predef.entityB = SS.GW.ActiveWorkplane();
297
            g.activeWorkplane = SS.GW.ActiveWorkplane();
298
            g.name = C_("group-name", "translate");
299
            break;
300

301
        case Command::GROUP_LINK: {
302
            g.type = Type::LINKED;
303
            g.meshCombine = CombineAs::ASSEMBLE;
304
            if(g.linkFile.IsEmpty()) {
305
                Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
306
                dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
307
                dialog->ThawChoices(settings, "LinkSketch");
308
                if(!dialog->RunModal()) return;
309
                dialog->FreezeChoices(settings, "LinkSketch");
310
                g.linkFile = dialog->GetFilename();
311
            }
312

313
            // Assign the default name of the group based on the name of
314
            // the linked file.
315
            g.name = g.linkFile.FileStem();
316
            for(size_t i = 0; i < g.name.length(); i++) {
317
                if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) {
318
                    // convert punctuation to dashes
319
                    g.name[i] = '-';
320
                }
321
            }
322
            break;
323
        }
324

325
        default: ssassert(false, "Unexpected menu ID");
326
    }
327

328
    // Copy color from the previous mesh-contributing group.
329
    if(g.IsMeshGroup() && !SK.groupOrder.IsEmpty()) {
330
        Group *running = SK.GetRunningMeshGroupFor(SS.GW.activeGroup);
331
        if(running != NULL) {
332
            g.color = running->color;
333
        }
334
    }
335

336
    SS.GW.ClearSelection();
337
    SS.UndoRemember();
338

339
    bool afterActive = false;
340
    for(hGroup hg : SK.groupOrder) {
341
        Group *gi = SK.GetGroup(hg);
342
        if(afterActive)
343
            gi->order += 1;
344
        if(gi->h == SS.GW.activeGroup) {
345
            g.order = gi->order + 1;
346
            afterActive = true;
347
        }
348
    }
349

350
    SK.group.AddAndAssignId(&g);
351
    Group *gg = SK.GetGroup(g.h);
352

353
    if(gg->type == Type::LINKED) {
354
        SS.ReloadAllLinked(SS.saveFile);
355
    }
356
    gg->clean = false;
357
    SS.GW.activeGroup = gg->h;
358
    SS.GenerateAll();
359
    if(gg->type == Type::DRAWING_WORKPLANE) {
360
        // Can't set the active workplane for this one until after we've
361
        // regenerated, because the workplane doesn't exist until then.
362
        gg->activeWorkplane = gg->h.entity(0);
363
    }
364
    gg->Activate();
365
    TextWindow::ScreenSelectGroup(0, gg->h.v);
366
    SS.GW.AnimateOntoWorkplane();
367
}
368

369
void Group::TransformImportedBy(Vector t, Quaternion q) {
370
    ssassert(type == Type::LINKED, "Expected a linked group");
371

372
    hParam tx, ty, tz, qw, qx, qy, qz;
373
    tx = h.param(0);
374
    ty = h.param(1);
375
    tz = h.param(2);
376
    qw = h.param(3);
377
    qx = h.param(4);
378
    qy = h.param(5);
379
    qz = h.param(6);
380

381
    Quaternion qg = Quaternion::From(qw, qx, qy, qz);
382
    qg = q.Times(qg);
383

384
    Vector tg = Vector::From(tx, ty, tz);
385
    tg = tg.Plus(t);
386

387
    SK.GetParam(tx)->val = tg.x;
388
    SK.GetParam(ty)->val = tg.y;
389
    SK.GetParam(tz)->val = tg.z;
390

391
    SK.GetParam(qw)->val = qg.w;
392
    SK.GetParam(qx)->val = qg.vx;
393
    SK.GetParam(qy)->val = qg.vy;
394
    SK.GetParam(qz)->val = qg.vz;
395
}
396

397
bool Group::IsForcedToMeshBySource() const {
398
    const Group *srcg = this;
399
    if(type == Type::TRANSLATE || type == Type::ROTATE) {
400
        // A step and repeat gets merged against the group's previous group,
401
        // not our own previous group.
402
        srcg = SK.GetGroup(opA);
403
        if(srcg->forceToMesh) return true;
404
    }
405
    Group *g = srcg->RunningMeshGroup();
406
    if(g == NULL) return false;
407
    return g->forceToMesh || g->IsForcedToMeshBySource();
408
}
409

410
bool Group::IsForcedToMesh() const {
411
    return forceToMesh || IsTriangleMeshAssembly() || IsForcedToMeshBySource();
412
}
413

414
bool Group::IsTriangleMeshAssembly() const {
415
    if (type != Type::LINKED) return false;
416
    if (!impMesh.IsEmpty() && impShell.IsEmpty()) return true;
417
    return false;
418
}
419

420
std::string Group::DescriptionString() {
421
    if(name.empty()) {
422
        return ssprintf("g%03x-%s", h.v, _("(unnamed)"));
423
    } else {
424
        return ssprintf("g%03x-%s", h.v, name.c_str());
425
    }
426
}
427

428
void Group::Activate() {
429
    if(type == Type::DRAWING_WORKPLANE || type == Type::DRAWING_3D) {
430
        SS.GW.showFaces = SS.GW.showFacesDrawing;
431
    } else {
432
        SS.GW.showFaces = SS.GW.showFacesNonDrawing;
433
    }
434
    SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
435
    SS.ScheduleShowTW();
436
}
437

438
void Group::Generate(IdList<Entity,hEntity> *entity,
439
                     IdList<Param,hParam> *param)
440
{
441
    Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
442
    Vector gp = SS.GW.projRight.Plus(SS.GW.projUp);
443
    Vector gc = (SS.GW.offset).ScaledBy(-1);
444
    gn = gn.WithMagnitude(200/SS.GW.scale);
445
    gp = gp.WithMagnitude(200/SS.GW.scale);
446
    int a, i;
447
    switch(type) {
448
        case Type::DRAWING_3D:
449
            return;
450

451
        case Type::DRAWING_WORKPLANE: {
452
            Quaternion q;
453
            if(subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) {
454
                Vector u = SK.GetEntity(predef.entityB)->VectorGetNum();
455
                Vector v = SK.GetEntity(predef.entityC)->VectorGetNum();
456
                u = u.WithMagnitude(1);
457
                Vector n = u.Cross(v);
458
                v = (n.Cross(u)).WithMagnitude(1);
459

460
                if(predef.swapUV) swap(u, v);
461
                if(predef.negateU) u = u.ScaledBy(-1);
462
                if(predef.negateV) v = v.ScaledBy(-1);
463
                q = Quaternion::From(u, v);
464
            } else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
465
                // Already given, numerically.
466
                q = predef.q;
467
            } else if(subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
468
                q = SK.GetEntity(predef.entityB)->NormalGetNum();
469
            } else ssassert(false, "Unexpected workplane subtype");
470

471
            Entity normal = {};
472
            normal.type = Entity::Type::NORMAL_N_COPY;
473
            normal.numNormal = q;
474

475
            normal.point[0] = h.entity(2);
476
            normal.group = h;
477
            normal.h = h.entity(1);
478
            entity->Add(&normal);
479

480
            Entity point = {};
481
            point.type = Entity::Type::POINT_N_COPY;
482
            point.numPoint = SK.GetEntity(predef.origin)->PointGetNum();
483
            point.construction = true;
484
            point.group = h;
485
            point.h = h.entity(2);
486
            entity->Add(&point);
487

488
            Entity wp = {};
489
            wp.type = Entity::Type::WORKPLANE;
490
            wp.normal = normal.h;
491
            wp.point[0] = point.h;
492
            wp.group = h;
493
            wp.h = h.entity(0);
494
            entity->Add(&wp);
495
            return;
496
        }
497

498
        case Type::EXTRUDE: {
499
            AddParam(param, h.param(0), gn.x);
500
            AddParam(param, h.param(1), gn.y);
501
            AddParam(param, h.param(2), gn.z);
502
            int ai, af;
503
            if(subtype == Subtype::ONE_SIDED) {
504
                ai = 0; af = 2;
505
            } else if(subtype == Subtype::TWO_SIDED) {
506
                ai = -1; af = 1;
507
            } else ssassert(false, "Unexpected extrusion subtype");
508

509
            // Get some arbitrary point in the sketch, that will be used
510
            // as a reference when defining top and bottom faces.
511
            hEntity pt = { 0 };
512
            // Not using range-for here because we're changing the size of entity in the loop.
513
            for(i = 0; i < entity->n; i++) {
514
                Entity *e = &(entity->Get(i));
515
                if(e->group != opA) continue;
516

517
                if(e->IsPoint()) pt = e->h;
518

519
                e->CalculateNumerical(/*forExport=*/false);
520
                hEntity he = e->h; e = NULL;
521
                // As soon as I call CopyEntity, e may become invalid! That
522
                // adds entities, which may cause a realloc.
523
                CopyEntity(entity, SK.GetEntity(he), ai, REMAP_BOTTOM,
524
                    h.param(0), h.param(1), h.param(2),
525
                    NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
526
                    CopyAs::N_TRANS);
527
                CopyEntity(entity, SK.GetEntity(he), af, REMAP_TOP,
528
                    h.param(0), h.param(1), h.param(2),
529
                    NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
530
                    CopyAs::N_TRANS);
531
                MakeExtrusionLines(entity, he);
532
            }
533
            // Remapped versions of that arbitrary point will be used to
534
            // provide points on the plane faces.
535
            MakeExtrusionTopBottomFaces(entity, pt);
536
            return;
537
        }
538

539
        case Type::LATHE: {
540
            Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
541
            Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
542

543
            // Not using range-for here because we're changing the size of entity in the loop.
544
            for(i = 0; i < entity->n; i++) {
545
                Entity *e = &(entity->Get(i));
546
                if(e->group != opA) continue;
547

548
                e->CalculateNumerical(/*forExport=*/false);
549
                hEntity he = e->h;
550

551
                // As soon as I call CopyEntity, e may become invalid! That
552
                // adds entities, which may cause a realloc.
553

554
                // this is the regular copy of all entities
555
                CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_START,
556
                    NO_PARAM, NO_PARAM, NO_PARAM,
557
                    NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
558
                    CopyAs::NUMERIC);
559

560
                e = &(entity->Get(i)); // because we copied.
561
                if (e->IsPoint()) {
562
                // for points this copy is used for the circle centers
563
                    CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_ARC_CENTER,
564
                        NO_PARAM, NO_PARAM, NO_PARAM,
565
                        NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
566
                        CopyAs::NUMERIC);
567
                    MakeLatheCircles(entity, param, he, axis_pos, axis_dir);
568
                };
569
                MakeLatheSurfacesSelectable(entity, he, axis_dir);
570
            }
571
            return;
572
        }
573

574
        case Type::REVOLVE: {
575
            // this was borrowed from LATHE and ROTATE
576
            Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
577
            Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
578

579
            // The center of rotation
580
            AddParam(param, h.param(0), axis_pos.x);
581
            AddParam(param, h.param(1), axis_pos.y);
582
            AddParam(param, h.param(2), axis_pos.z);
583
            // The rotation quaternion
584
            AddParam(param, h.param(3), 30 * PI / 180);
585
            AddParam(param, h.param(4), axis_dir.x);
586
            AddParam(param, h.param(5), axis_dir.y);
587
            AddParam(param, h.param(6), axis_dir.z);
588

589
            // Get some arbitrary point in the sketch, that will be used
590
            // as a reference when defining end faces.
591
            hEntity pt = { 0 };
592

593
            int ai = 0, af = 2;
594
            if (subtype == Subtype::TWO_SIDED)
595
            {
596
                ai = -1;
597
                af = 1;
598
            }
599
            // Not using range-for here because we're changing the size of entity in the loop.
600
            for(i = 0; i < entity->n; i++) {
601
                Entity *e = &(entity->Get(i));
602
                if(e->group != opA)
603
                    continue;
604

605
                if(e->IsPoint()) pt = e->h;
606

607
                e->CalculateNumerical(/*forExport=*/false);
608
                hEntity he = e->h;
609
                // one copy for each end of the revolved surface
610
                CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0),
611
                       h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
612
                       h.param(6), NO_PARAM, CopyAs::N_ROT_AA);
613

614
                e = &(entity->Get(i)); // because we copied.
615
                CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0),
616
                       h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
617
                       h.param(6), NO_PARAM, CopyAs::N_ROT_AA);
618

619
                // Arcs are not generated for revolve groups, for now, because our current arc
620
                // entity is not chiral, and dragging a revolve may break the arc by inverting it.
621
                // MakeLatheCircles(entity, param, he, axis_pos, axis_dir);
622
                MakeLatheSurfacesSelectable(entity, he, axis_dir);
623
            }
624
            MakeRevolveEndFaces(entity, pt, ai, af);
625
            return;
626
        }
627

628
        case Type::HELIX:   {
629
            Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
630
            Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
631

632
            // The center of rotation
633
            AddParam(param, h.param(0), axis_pos.x);
634
            AddParam(param, h.param(1), axis_pos.y);
635
            AddParam(param, h.param(2), axis_pos.z);
636
            // The rotation quaternion
637
            AddParam(param, h.param(3), 30 * PI / 180);
638
            AddParam(param, h.param(4), axis_dir.x);
639
            AddParam(param, h.param(5), axis_dir.y);
640
            AddParam(param, h.param(6), axis_dir.z);
641
            // distance to translate along the rotation axis
642
            AddParam(param, h.param(7), 20);
643

644
            // Get some arbitrary point in the sketch, that will be used
645
            // as a reference when defining end faces.
646
            hEntity pt = { 0 };
647

648
            int ai = 0, af = 2;  // initial and final number of transformations
649
            if (subtype != Subtype::ONE_SIDED)
650
            {
651
                ai = -1;
652
                af = 1;
653
            }
654

655
            // Not using range-for here because we're changing the size of entity in the loop.
656
            for(i = 0; i < entity->n; i++) {
657
                Entity *e = &(entity->Get(i));
658
                if((e->group.v != opA.v) && !(e->h == predef.origin))
659
                    continue;
660

661
                if(e->IsPoint()) pt = e->h;
662

663
                e->CalculateNumerical(/*forExport=*/false);
664

665
                // one copy for each end of the helix
666
                CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0),
667
                           h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
668
                           h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS);
669

670
                e = &(entity->Get(i)); // because we copied.
671
                CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0),
672
                           h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
673
                           h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS);
674

675
                // For point entities on the axis, create a construction line
676
                e = &(entity->Get(i));
677
                if(e->IsPoint()) {
678
                    Vector check = e->PointGetNum().Minus(axis_pos).Cross(axis_dir);
679
                    if (check.Dot(check) < LENGTH_EPS) {
680
                        //! @todo isn't this the same as &(ent[i])?
681
                        Entity *ep = SK.GetEntity(e->h);
682
                        Entity en = {};
683
                        // A point gets extruded to form a line segment
684
                        en.point[0] = Remap(ep->h, REMAP_LATHE_START);
685
                        en.point[1] = Remap(ep->h, REMAP_LATHE_END);
686
                        en.group = h;
687
                        en.construction = ep->construction;
688
                        en.style = ep->style;
689
                        en.h = Remap(ep->h, REMAP_PT_TO_LINE);
690
                        en.type = Entity::Type::LINE_SEGMENT;
691
                        entity->Add(&en);
692
                    }
693
                }
694
            }
695
            MakeRevolveEndFaces(entity, pt, ai, af);
696
            return;
697
        }
698

699
        case Type::TRANSLATE: {
700
            // inherit meshCombine from source group
701
            Group *srcg = SK.GetGroup(opA);
702
            meshCombine = srcg->meshCombine;
703
            // The translation vector
704
            AddParam(param, h.param(0), gp.x);
705
            AddParam(param, h.param(1), gp.y);
706
            AddParam(param, h.param(2), gp.z);
707

708
            int n = (int)valA, a0 = 0;
709
            if(subtype == Subtype::ONE_SIDED && skipFirst) {
710
                a0++; n++;
711
            }
712

713
            for(a = a0; a < n; a++) {
714
                // Not using range-for here because we're changing the size of entity in the loop.
715
                for(i = 0; i < entity->n; i++) {
716
                    Entity *e = &(entity->Get(i));
717
                    if(e->group != opA) continue;
718

719
                    e->CalculateNumerical(/*forExport=*/false);
720
                    CopyEntity(entity, e,
721
                        a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
722
                        (a == (n - 1)) ? REMAP_LAST : a,
723
                        h.param(0), h.param(1), h.param(2),
724
                        NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
725
                        CopyAs::N_TRANS);
726
                }
727
            }
728
            return;
729
        }
730
        case Type::ROTATE: {
731
            // inherit meshCombine from source group
732
            Group *srcg = SK.GetGroup(opA);
733
            meshCombine = srcg->meshCombine;
734
            // The center of rotation
735
            AddParam(param, h.param(0), gc.x);
736
            AddParam(param, h.param(1), gc.y);
737
            AddParam(param, h.param(2), gc.z);
738
            // The rotation quaternion
739
            AddParam(param, h.param(3), 30*PI/180);
740
            AddParam(param, h.param(4), gn.x);
741
            AddParam(param, h.param(5), gn.y);
742
            AddParam(param, h.param(6), gn.z);
743

744
            int n = (int)valA, a0 = 0;
745
            if(subtype == Subtype::ONE_SIDED && skipFirst) {
746
                a0++; n++;
747
            }
748

749
            for(a = a0; a < n; a++) {
750
                // Not using range-for here because we're changing the size of entity in the loop.
751
                for(i = 0; i < entity->n; i++) {
752
                    Entity *e = &(entity->Get(i));
753
                    if(e->group != opA) continue;
754

755
                    e->CalculateNumerical(/*forExport=*/false);
756
                    CopyEntity(entity, e,
757
                        a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
758
                        (a == (n - 1)) ? REMAP_LAST : a,
759
                        h.param(0), h.param(1), h.param(2),
760
                        h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
761
                        CopyAs::N_ROT_AA);
762
                }
763
            }
764
            return;
765
        }
766
        case Type::LINKED:
767
            // The translation vector
768
            AddParam(param, h.param(0), gp.x);
769
            AddParam(param, h.param(1), gp.y);
770
            AddParam(param, h.param(2), gp.z);
771
            // The rotation quaternion
772
            AddParam(param, h.param(3), 1);
773
            AddParam(param, h.param(4), 0);
774
            AddParam(param, h.param(5), 0);
775
            AddParam(param, h.param(6), 0);
776

777
            // Not using range-for here because we're changing the size of entity in the loop.
778
            for(i = 0; i < impEntity.n; i++) {
779
                Entity *ie = &(impEntity[i]);
780
                CopyEntity(entity, ie, 0, 0,
781
                    h.param(0), h.param(1), h.param(2),
782
                    h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
783
                    CopyAs::N_ROT_TRANS);
784
            }
785
            return;
786
    }
787
    ssassert(false, "Unexpected group type");
788
}
789

790
bool Group::IsSolvedOkay() {
791
    return this->solved.how == SolveResult::OKAY ||
792
           (this->allowRedundant && this->solved.how == SolveResult::REDUNDANT_OKAY);
793
}
794

795
void Group::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
796
    Equation eq;
797
    eq.e = expr;
798
    eq.h = h.equation(index);
799
    l->Add(&eq);
800
}
801

802
void Group::GenerateEquations(IdList<Equation,hEquation> *l) {
803
    if(type == Type::LINKED) {
804
        // Normalize the quaternion
805
        ExprQuaternion q = {
806
            Expr::From(h.param(3)),
807
            Expr::From(h.param(4)),
808
            Expr::From(h.param(5)),
809
            Expr::From(h.param(6)) };
810
        AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
811
    } else if(type == Type::ROTATE || type == Type::REVOLVE || type == Type::HELIX) {
812
        // The axis and center of rotation are specified numerically
813
#define EC(x) (Expr::From(x))
814
#define EP(x) (Expr::From(h.param(x)))
815
        ExprVector orig = SK.GetEntity(predef.origin)->PointGetExprs();
816
        AddEq(l, (orig.x)->Minus(EP(0)), 0);
817
        AddEq(l, (orig.y)->Minus(EP(1)), 1);
818
        AddEq(l, (orig.z)->Minus(EP(2)), 2);
819
        // param 3 is the angle, which is free
820
        Vector axis = SK.GetEntity(predef.entityB)->VectorGetNum();
821
        axis = axis.WithMagnitude(1);
822
        AddEq(l, (EC(axis.x))->Minus(EP(4)), 3);
823
        AddEq(l, (EC(axis.y))->Minus(EP(5)), 4);
824
        AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
825
#undef EC
826
#undef EP
827
        if(type == Type::HELIX) {
828
            if(valB != 0.0) {
829
                AddEq(l, Expr::From(h.param(7))->Times(Expr::From(PI))->
830
                Minus(Expr::From(h.param(3))->Times(Expr::From(valB))), 6);
831
            }
832
        }
833
    } else if(type == Type::EXTRUDE) {
834
        if(predef.entityB != Entity::FREE_IN_3D) {
835
            // The extrusion path is locked along a line, normal to the
836
            // specified workplane.
837
            Entity *w = SK.GetEntity(predef.entityB);
838
            ExprVector u = w->Normal()->NormalExprsU();
839
            ExprVector v = w->Normal()->NormalExprsV();
840
            ExprVector extruden = {
841
                Expr::From(h.param(0)),
842
                Expr::From(h.param(1)),
843
                Expr::From(h.param(2)) };
844

845
            AddEq(l, u.Dot(extruden), 0);
846
            AddEq(l, v.Dot(extruden), 1);
847
        }
848
    } else if(type == Type::TRANSLATE) {
849
        if(predef.entityB != Entity::FREE_IN_3D) {
850
            Entity *w = SK.GetEntity(predef.entityB);
851
            ExprVector n = w->Normal()->NormalExprsN();
852
            ExprVector trans;
853
            trans = ExprVector::From(h.param(0), h.param(1), h.param(2));
854

855
            // The translation vector is parallel to the workplane
856
            AddEq(l, trans.Dot(n), 0);
857
        }
858
    }
859
}
860

861
hEntity Group::Remap(hEntity in, int copyNumber) {
862
    auto it = remap.find({ in, copyNumber });
863
    if(it == remap.end()) {
864
        std::tie(it, std::ignore) =
865
            remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() + 1 } });
866
    }
867
    return h.entity(it->second.v);
868
}
869

870
void Group::MakeExtrusionLines(IdList<Entity,hEntity> *el, hEntity in) {
871
    Entity *ep = SK.GetEntity(in);
872

873
    Entity en = {};
874
    if(ep->IsPoint()) {
875
        // A point gets extruded to form a line segment
876
        en.point[0] = Remap(ep->h, REMAP_TOP);
877
        en.point[1] = Remap(ep->h, REMAP_BOTTOM);
878
        en.group = h;
879
        en.construction = ep->construction;
880
        en.style = ep->style;
881
        en.h = Remap(ep->h, REMAP_PT_TO_LINE);
882
        en.type = Entity::Type::LINE_SEGMENT;
883
        el->Add(&en);
884
    } else if(ep->type == Entity::Type::LINE_SEGMENT) {
885
        // A line gets extruded to form a plane face; an endpoint of the
886
        // original line is a point in the plane, and the line is in the plane.
887
        Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
888
        Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
889
        Vector ab = b.Minus(a);
890

891
        en.param[0] = h.param(0);
892
        en.param[1] = h.param(1);
893
        en.param[2] = h.param(2);
894
        en.numPoint = a;
895
        en.numNormal = Quaternion::From(0, ab.x, ab.y, ab.z);
896

897
        en.group = h;
898
        en.construction = ep->construction;
899
        en.style = ep->style;
900
        en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
901
        en.type = Entity::Type::FACE_XPROD;
902
        el->Add(&en);
903
    }
904
}
905

906
void Group::MakeLatheCircles(IdList<Entity,hEntity> *el, IdList<Param,hParam> *param, hEntity in, Vector pt, Vector axis) {
907
    Entity *ep = SK.GetEntity(in);
908

909
    Entity en = {};
910
    if(ep->IsPoint()) {
911
        // A point gets revolved to form an arc.
912
        en.point[0] = Remap(ep->h, REMAP_LATHE_ARC_CENTER);
913
        en.point[1] = Remap(ep->h, REMAP_LATHE_START);
914
        en.point[2] = en.point[1]; //Remap(ep->h, REMAP_LATHE_END);
915

916
        // Get arc center and point on arc.
917
        Entity *pc = SK.GetEntity(en.point[0]);
918
        Entity *pp = SK.GetEntity(en.point[1]);
919

920
        // Project arc point to the revolution axis and use it for arc center.
921
        double k = pp->numPoint.Minus(pt).Dot(axis) / axis.Dot(axis);
922
        pc->numPoint = pt.Plus(axis.ScaledBy(k));
923

924
        // Create arc entity.
925
        en.group = h;
926
        en.construction = ep->construction;
927
        en.style = ep->style;
928
        en.h = Remap(ep->h, REMAP_PT_TO_ARC);
929
        en.type = Entity::Type::ARC_OF_CIRCLE;
930

931
        // Generate a normal.
932
        Entity n = {};
933
        n.workplane = en.workplane;
934
        n.h = Remap(ep->h, REMAP_PT_TO_NORMAL);
935
        n.group = en.group;
936
        n.style = en.style;
937
        n.type = Entity::Type::NORMAL_N_COPY;
938

939
        // Create basis for the normal.
940
        Vector nu = pp->numPoint.Minus(pc->numPoint).WithMagnitude(1.0);
941
        Vector nv = nu.Cross(axis).WithMagnitude(1.0);
942
        n.numNormal = Quaternion::From(nv, nu);
943

944
        // The point determines where the normal gets displayed on-screen;
945
        // it's entirely cosmetic.
946
        n.point[0] = en.point[0];
947
        el->Add(&n);
948
        en.normal = n.h;
949
        el->Add(&en);
950
    }
951
}
952

953
void Group::MakeLatheSurfacesSelectable(IdList<Entity, hEntity> *el, hEntity in, Vector axis) {
954
    Entity *ep = SK.GetEntity(in);
955

956
    Entity en = {};
957
    if(ep->type == Entity::Type::LINE_SEGMENT) {
958
        // An axis-perpendicular line gets revolved to form a face.
959
        Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
960
        Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
961
        Vector u = b.Minus(a).WithMagnitude(1.0);
962

963
        // Check for perpendicularity: calculate cosine of the angle
964
        // between axis and line direction and check that
965
        // cos(angle) == 0 <-> angle == +-90 deg.
966
        if(fabs(u.Dot(axis) / axis.Magnitude()) < ANGLE_COS_EPS) {
967
            en.param[0] = h.param(0);
968
            en.param[1] = h.param(1);
969
            en.param[2] = h.param(2);
970
            Vector v = axis.Cross(u).WithMagnitude(1.0);
971
            Vector n = u.Cross(v);
972
            en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
973

974
            en.group = h;
975
            en.construction = ep->construction;
976
            en.style = ep->style;
977
            en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
978
            en.type = Entity::Type::FACE_NORMAL_PT;
979
            en.point[0] = ep->point[0];
980
            el->Add(&en);
981
        }
982
    }
983
}
984

985
// For Revolve and Helix groups the end faces are remapped from an arbitrary
986
// point on the sketch. We reference the transformed point but there is
987
// no existing normal so we need to define the rotation and timesApplied.
988
void Group::MakeRevolveEndFaces(IdList<Entity,hEntity> *el, hEntity pt, int ai, int af)
989
{
990
    if(pt.v == 0) return;
991
    Group *src = SK.GetGroup(opA);
992
    Vector n = src->polyLoops.normal;
993

994
    // When there is no loop normal (e.g. if the loop is broken), use normal of workplane
995
    // as fallback, to avoid breaking constraints depending on the faces.
996
    if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) {
997
        n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN();
998
    }
999

1000
    Entity en = {};
1001
    en.type = Entity::Type::FACE_ROT_NORMAL_PT;
1002
    en.group = h;
1003
    // The center of rotation
1004
    en.param[0] = h.param(0);
1005
    en.param[1] = h.param(1);
1006
    en.param[2] = h.param(2);
1007
    // The rotation quaternion
1008
    en.param[3] = h.param(3);
1009
    en.param[4] = h.param(4);
1010
    en.param[5] = h.param(5);
1011
    en.param[6] = h.param(6);
1012

1013
    en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
1014
    en.point[0] = Remap(pt, REMAP_LATHE_START);
1015
    en.timesApplied = ai;
1016
    en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_START);
1017
    el->Add(&en);
1018

1019
    en.point[0] = Remap(pt, REMAP_LATHE_END);
1020
    en.timesApplied = af;
1021
    en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_END);
1022
    el->Add(&en);
1023
}
1024

1025
void Group::MakeExtrusionTopBottomFaces(IdList<Entity,hEntity> *el, hEntity pt)
1026
{
1027
    if(pt.v == 0) return;
1028
    Group *src = SK.GetGroup(opA);
1029
    Vector n = src->polyLoops.normal;
1030

1031
    // When there is no loop normal (e.g. if the loop is broken), use normal of workplane
1032
    // as fallback, to avoid breaking constraints depending on the faces.
1033
    if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) {
1034
        n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN();
1035
    }
1036

1037
    Entity en = {};
1038
    en.type = Entity::Type::FACE_NORMAL_PT;
1039
    en.group = h;
1040

1041
    en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
1042
    en.point[0] = Remap(pt, REMAP_TOP);
1043
    en.h = Remap(Entity::NO_ENTITY, REMAP_TOP);
1044
    el->Add(&en);
1045

1046
    en.point[0] = Remap(pt, REMAP_BOTTOM);
1047
    en.h = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
1048
    el->Add(&en);
1049
}
1050

1051
void Group::CopyEntity(IdList<Entity,hEntity> *el,
1052
                       Entity *ep, int timesApplied, int remap,
1053
                       hParam dx, hParam dy, hParam dz,
1054
                       hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist,
1055
                       CopyAs as)
1056
{
1057
    Entity en = {};
1058
    en.type = ep->type;
1059
    en.extraPoints = ep->extraPoints;
1060
    en.h = Remap(ep->h, remap);
1061
    en.timesApplied = timesApplied;
1062
    en.group = h;
1063
    en.construction = ep->construction;
1064
    en.style = ep->style;
1065
    en.str = ep->str;
1066
    en.font = ep->font;
1067
    en.file = ep->file;
1068

1069
    switch(ep->type) {
1070
        case Entity::Type::WORKPLANE:
1071
            // Don't copy these.
1072
            return;
1073

1074
        case Entity::Type::POINT_N_COPY:
1075
        case Entity::Type::POINT_N_TRANS:
1076
        case Entity::Type::POINT_N_ROT_TRANS:
1077
        case Entity::Type::POINT_N_ROT_AA:
1078
        case Entity::Type::POINT_N_ROT_AXIS_TRANS:
1079
        case Entity::Type::POINT_IN_3D:
1080
        case Entity::Type::POINT_IN_2D:
1081
            if(as == CopyAs::N_TRANS) {
1082
                en.type = Entity::Type::POINT_N_TRANS;
1083
                en.param[0] = dx;
1084
                en.param[1] = dy;
1085
                en.param[2] = dz;
1086
            } else if(as == CopyAs::NUMERIC) {
1087
                en.type = Entity::Type::POINT_N_COPY;
1088
            } else {
1089
                if(as == CopyAs::N_ROT_AA) {
1090
                    en.type = Entity::Type::POINT_N_ROT_AA;
1091
                } else if (as == CopyAs::N_ROT_AXIS_TRANS) {
1092
                    en.type = Entity::Type::POINT_N_ROT_AXIS_TRANS;
1093
                } else {
1094
                    en.type = Entity::Type::POINT_N_ROT_TRANS;
1095
                }
1096
                en.param[0] = dx;
1097
                en.param[1] = dy;
1098
                en.param[2] = dz;
1099
                en.param[3] = qw;
1100
                en.param[4] = qvx;
1101
                en.param[5] = qvy;
1102
                en.param[6] = qvz;
1103
                if (as ==  CopyAs::N_ROT_AXIS_TRANS) {
1104
                    en.param[7] = dist;
1105
                }
1106
            }
1107
            en.numPoint = (ep->actPoint).ScaledBy(scale);
1108
            break;
1109

1110
        case Entity::Type::NORMAL_N_COPY:
1111
        case Entity::Type::NORMAL_N_ROT:
1112
        case Entity::Type::NORMAL_N_ROT_AA:
1113
        case Entity::Type::NORMAL_IN_3D:
1114
        case Entity::Type::NORMAL_IN_2D:
1115
            if(as == CopyAs::N_TRANS || as == CopyAs::NUMERIC) {
1116
                en.type = Entity::Type::NORMAL_N_COPY;
1117
            } else {  // N_ROT_AXIS_TRANS probably doesn't warrant a new entity Type
1118
                if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) {
1119
                    en.type = Entity::Type::NORMAL_N_ROT_AA;
1120
                } else {
1121
                    en.type = Entity::Type::NORMAL_N_ROT;
1122
                }
1123
                en.param[0] = qw;
1124
                en.param[1] = qvx;
1125
                en.param[2] = qvy;
1126
                en.param[3] = qvz;
1127
            }
1128
            en.numNormal = ep->actNormal;
1129
            if(scale < 0) en.numNormal = en.numNormal.Mirror();
1130

1131
            en.point[0] = Remap(ep->point[0], remap);
1132
            break;
1133

1134
        case Entity::Type::DISTANCE_N_COPY:
1135
        case Entity::Type::DISTANCE:
1136
            en.type = Entity::Type::DISTANCE_N_COPY;
1137
            en.numDistance = ep->actDistance*fabs(scale);
1138
            break;
1139

1140
        case Entity::Type::FACE_NORMAL_PT:
1141
        case Entity::Type::FACE_XPROD:
1142
        case Entity::Type::FACE_N_ROT_TRANS:
1143
        case Entity::Type::FACE_N_TRANS:
1144
        case Entity::Type::FACE_N_ROT_AA:
1145
        case Entity::Type::FACE_ROT_NORMAL_PT:
1146
        case Entity::Type::FACE_N_ROT_AXIS_TRANS:
1147
            if(as == CopyAs::N_TRANS) {
1148
                en.type = Entity::Type::FACE_N_TRANS;
1149
                en.param[0] = dx;
1150
                en.param[1] = dy;
1151
                en.param[2] = dz;
1152
            } else if (as == CopyAs::NUMERIC) {
1153
                en.type = Entity::Type::FACE_NORMAL_PT;
1154
            } else if (as == CopyAs::N_ROT_AXIS_TRANS) {
1155
                en.type = Entity::Type::FACE_N_ROT_AXIS_TRANS;
1156
                en.param[0] = dx;
1157
                en.param[1] = dy;
1158
                en.param[2] = dz;
1159
                en.param[3] = qw;
1160
                en.param[4] = qvx;
1161
                en.param[5] = qvy;
1162
                en.param[6] = qvz;
1163
                en.param[7] = dist;
1164
            } else {
1165
                if(as == CopyAs::N_ROT_AA) {
1166
                    en.type = Entity::Type::FACE_N_ROT_AA;
1167
                } else {
1168
                    en.type = Entity::Type::FACE_N_ROT_TRANS;
1169
                }
1170
                en.param[0] = dx;
1171
                en.param[1] = dy;
1172
                en.param[2] = dz;
1173
                en.param[3] = qw;
1174
                en.param[4] = qvx;
1175
                en.param[5] = qvy;
1176
                en.param[6] = qvz;
1177
            }
1178
            en.numPoint  = (ep->actPoint).ScaledBy(scale);
1179
            en.numNormal = (ep->actNormal).ScaledBy(scale);
1180
            break;
1181

1182
        default: {
1183
            if((Entity::Type::IMAGE == ep->type) && (true == ep->construction)) {
1184
                // Do not copy image entities if they are construction.
1185
                return;
1186
            }
1187

1188
            int i, points;
1189
            bool hasNormal, hasDistance;
1190
            EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
1191
                NULL, &points, &hasNormal, &hasDistance);
1192
            for(i = 0; i < points; i++) {
1193
                en.point[i] = Remap(ep->point[i], remap);
1194
            }
1195
            if(hasNormal)   en.normal   = Remap(ep->normal, remap);
1196
            if(hasDistance) en.distance = Remap(ep->distance, remap);
1197
            break;
1198
        }
1199
    }
1200

1201
    // If the entity came from an linked file where it was invisible then
1202
    // ep->actiVisble will be false, and we should hide it. Or if the entity
1203
    // came from a copy (e.g. step and repeat) of a force-hidden linked
1204
    // entity, then we also want to hide it.
1205
    en.forceHidden = (!ep->actVisible) || ep->forceHidden;
1206

1207
    el->Add(&en);
1208
}
1209

1210
bool Group::ShouldDrawExploded() const {
1211
    return SS.explode && h == SS.GW.activeGroup && type == Type::DRAWING_WORKPLANE && !SS.exportMode;
1212
}
1213

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

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

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

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