Solvespace
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
11const hParam Param::NO_PARAM = { 0 };
12#define NO_PARAM (Param::NO_PARAM)
13
14const 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//-----------------------------------------------------------------------------
20void Group::Clear() {
21polyLoops.Clear();
22bezierLoops.Clear();
23bezierOpens.Clear();
24thisMesh.Clear();
25runningMesh.Clear();
26thisShell.Clear();
27runningShell.Clear();
28displayMesh.Clear();
29displayOutlines.Clear();
30impMesh.Clear();
31impShell.Clear();
32impEntity.Clear();
33// remap is the only one that doesn't get recreated when we regen
34remap.clear();
35}
36
37void Group::AddParam(IdList<Param,hParam> *param, hParam hp, double v) {
38Param pa = {};
39pa.h = hp;
40pa.val = v;
41
42param->Add(&pa);
43}
44
45bool Group::IsVisible() {
46if(!visible) return false;
47Group *active = SK.GetGroup(SS.GW.activeGroup);
48if(order > active->order) return false;
49return true;
50}
51
52size_t Group::GetNumConstraints() {
53return std::count_if(SK.constraint.begin(), SK.constraint.end(),
54[&](Constraint const &c) { return c.group == h; });
55}
56
57Vector Group::ExtrusionGetVector() {
58return Vector::From(h.param(0), h.param(1), h.param(2));
59}
60
61void Group::ExtrusionForceVectorTo(const Vector &v) {
62SK.GetParam(h.param(0))->val = v.x;
63SK.GetParam(h.param(1))->val = v.y;
64SK.GetParam(h.param(2))->val = v.z;
65}
66
67void Group::MenuGroup(Command id) {
68MenuGroup(id, Platform::Path());
69}
70
71void Group::MenuGroup(Command id, Platform::Path linkFile) {
72Platform::SettingsRef settings = Platform::GetSettings();
73
74Group g = {};
75g.visible = true;
76g.color = RGBi(100, 100, 100);
77g.scale = 1;
78g.linkFile = linkFile;
79
80SS.GW.GroupSelection();
81auto const &gs = SS.GW.gs;
82
83switch(id) {
84case Command::GROUP_3D:
85g.type = Type::DRAWING_3D;
86g.name = C_("group-name", "sketch-in-3d");
87break;
88
89case Command::GROUP_WRKPL:
90g.type = Type::DRAWING_WORKPLANE;
91g.name = C_("group-name", "sketch-in-plane");
92if(gs.points == 1 && gs.n == 1) {
93g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO;
94
95Vector u = SS.GW.projRight, v = SS.GW.projUp;
96u = u.ClosestOrtho();
97v = v.Minus(u.ScaledBy(v.Dot(u)));
98v = v.ClosestOrtho();
99
100g.predef.q = Quaternion::From(u, v);
101g.predef.origin = gs.point[0];
102} else if(gs.points == 1 && gs.lineSegments == 2 && gs.n == 3) {
103g.subtype = Subtype::WORKPLANE_BY_LINE_SEGMENTS;
104
105g.predef.origin = gs.point[0];
106g.predef.entityB = gs.entity[0];
107g.predef.entityC = gs.entity[1];
108
109Vector ut = SK.GetEntity(g.predef.entityB)->VectorGetNum();
110Vector vt = SK.GetEntity(g.predef.entityC)->VectorGetNum();
111ut = ut.WithMagnitude(1);
112vt = vt.WithMagnitude(1);
113
114if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) {
115swap(ut, vt);
116g.predef.swapUV = true;
117}
118if(SS.GW.projRight.Dot(ut) < 0) g.predef.negateU = true;
119if(SS.GW.projUp. Dot(vt) < 0) g.predef.negateV = true;
120} else if(gs.workplanes == 1 && gs.n == 1) {
121if(gs.entity[0].isFromRequest()) {
122Entity *wrkpl = SK.GetEntity(gs.entity[0]);
123Entity *normal = SK.GetEntity(wrkpl->normal);
124g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO;
125g.predef.origin = wrkpl->point[0];
126g.predef.q = normal->NormalGetNum();
127} else {
128Group *wrkplg = SK.GetGroup(gs.entity[0].group());
129g.subtype = wrkplg->subtype;
130g.predef.origin = wrkplg->predef.origin;
131if(wrkplg->subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) {
132g.predef.entityB = wrkplg->predef.entityB;
133g.predef.entityC = wrkplg->predef.entityC;
134g.predef.swapUV = wrkplg->predef.swapUV;
135g.predef.negateU = wrkplg->predef.negateU;
136g.predef.negateV = wrkplg->predef.negateV;
137} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
138g.predef.q = wrkplg->predef.q;
139} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
140g.predef.q = wrkplg->predef.q;
141g.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) {
145g.subtype = Subtype::WORKPLANE_BY_POINT_NORMAL;
146g.predef.entityB = gs.anyNormal[0];
147g.predef.q = SK.GetEntity(gs.anyNormal[0])->NormalGetNum();
148g.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 {
154Error(_("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"));
164return;
165}
166break;
167
168case Command::GROUP_EXTRUDE:
169if(!SS.GW.LockedInWorkplane()) {
170Error(_("Activate a workplane (Sketch -> In Workplane) before "
171"extruding. The sketch will be extruded normal to the "
172"workplane."));
173return;
174}
175g.type = Type::EXTRUDE;
176g.opA = SS.GW.activeGroup;
177g.predef.entityB = SS.GW.ActiveWorkplane();
178g.subtype = Subtype::ONE_SIDED;
179g.name = C_("group-name", "extrude");
180break;
181
182case Command::GROUP_LATHE:
183if(!SS.GW.LockedInWorkplane()) {
184Error(_("Lathe operation can only be applied to planar sketches."));
185return;
186}
187if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
188g.predef.origin = gs.point[0];
189g.predef.entityB = gs.vector[0];
190} else if(gs.lineSegments == 1 && gs.n == 1) {
191g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
192g.predef.entityB = gs.entity[0];
193// since a line segment is a vector
194} else {
195Error(_("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"));
201return;
202}
203g.type = Type::LATHE;
204g.opA = SS.GW.activeGroup;
205g.name = C_("group-name", "lathe");
206break;
207
208case Command::GROUP_REVOLVE:
209if(!SS.GW.LockedInWorkplane()) {
210Error(_("Revolve operation can only be applied to planar sketches."));
211return;
212}
213if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
214g.predef.origin = gs.point[0];
215g.predef.entityB = gs.vector[0];
216} else if(gs.lineSegments == 1 && gs.n == 1) {
217g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
218g.predef.entityB = gs.entity[0];
219// since a line segment is a vector
220} else {
221Error(_("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"));
227return;
228}
229g.type = Type::REVOLVE;
230g.opA = SS.GW.activeGroup;
231g.valA = 2;
232g.subtype = Subtype::ONE_SIDED;
233g.name = C_("group-name", "revolve");
234break;
235
236case Command::GROUP_HELIX:
237if(!SS.GW.LockedInWorkplane()) {
238Error(_("Helix operation can only be applied to planar sketches."));
239return;
240}
241if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
242g.predef.origin = gs.point[0];
243g.predef.entityB = gs.vector[0];
244} else if(gs.lineSegments == 1 && gs.n == 1) {
245g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
246g.predef.entityB = gs.entity[0];
247// since a line segment is a vector
248} else {
249Error(_("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"));
255return;
256}
257g.type = Type::HELIX;
258g.opA = SS.GW.activeGroup;
259g.valA = 2;
260g.subtype = Subtype::ONE_SIDED;
261g.name = C_("group-name", "helix");
262break;
263
264case Command::GROUP_ROT: {
265if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) {
266g.predef.origin = gs.point[0];
267Entity *w = SK.GetEntity(SS.GW.ActiveWorkplane());
268g.predef.entityB = w->Normal()->h;
269g.activeWorkplane = w->h;
270} else if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
271g.predef.origin = gs.point[0];
272g.predef.entityB = gs.vector[0];
273} else {
274Error(_("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"));
281return;
282}
283g.type = Type::ROTATE;
284g.opA = SS.GW.activeGroup;
285g.valA = 3;
286g.subtype = Subtype::ONE_SIDED;
287g.name = C_("group-name", "rotate");
288break;
289}
290
291case Command::GROUP_TRANS:
292g.type = Type::TRANSLATE;
293g.opA = SS.GW.activeGroup;
294g.valA = 3;
295g.subtype = Subtype::ONE_SIDED;
296g.predef.entityB = SS.GW.ActiveWorkplane();
297g.activeWorkplane = SS.GW.ActiveWorkplane();
298g.name = C_("group-name", "translate");
299break;
300
301case Command::GROUP_LINK: {
302g.type = Type::LINKED;
303g.meshCombine = CombineAs::ASSEMBLE;
304if(g.linkFile.IsEmpty()) {
305Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
306dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
307dialog->ThawChoices(settings, "LinkSketch");
308if(!dialog->RunModal()) return;
309dialog->FreezeChoices(settings, "LinkSketch");
310g.linkFile = dialog->GetFilename();
311}
312
313// Assign the default name of the group based on the name of
314// the linked file.
315g.name = g.linkFile.FileStem();
316for(size_t i = 0; i < g.name.length(); i++) {
317if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) {
318// convert punctuation to dashes
319g.name[i] = '-';
320}
321}
322break;
323}
324
325default: ssassert(false, "Unexpected menu ID");
326}
327
328// Copy color from the previous mesh-contributing group.
329if(g.IsMeshGroup() && !SK.groupOrder.IsEmpty()) {
330Group *running = SK.GetRunningMeshGroupFor(SS.GW.activeGroup);
331if(running != NULL) {
332g.color = running->color;
333}
334}
335
336SS.GW.ClearSelection();
337SS.UndoRemember();
338
339bool afterActive = false;
340for(hGroup hg : SK.groupOrder) {
341Group *gi = SK.GetGroup(hg);
342if(afterActive)
343gi->order += 1;
344if(gi->h == SS.GW.activeGroup) {
345g.order = gi->order + 1;
346afterActive = true;
347}
348}
349
350SK.group.AddAndAssignId(&g);
351Group *gg = SK.GetGroup(g.h);
352
353if(gg->type == Type::LINKED) {
354SS.ReloadAllLinked(SS.saveFile);
355}
356gg->clean = false;
357SS.GW.activeGroup = gg->h;
358SS.GenerateAll();
359if(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.
362gg->activeWorkplane = gg->h.entity(0);
363}
364gg->Activate();
365TextWindow::ScreenSelectGroup(0, gg->h.v);
366SS.GW.AnimateOntoWorkplane();
367}
368
369void Group::TransformImportedBy(Vector t, Quaternion q) {
370ssassert(type == Type::LINKED, "Expected a linked group");
371
372hParam tx, ty, tz, qw, qx, qy, qz;
373tx = h.param(0);
374ty = h.param(1);
375tz = h.param(2);
376qw = h.param(3);
377qx = h.param(4);
378qy = h.param(5);
379qz = h.param(6);
380
381Quaternion qg = Quaternion::From(qw, qx, qy, qz);
382qg = q.Times(qg);
383
384Vector tg = Vector::From(tx, ty, tz);
385tg = tg.Plus(t);
386
387SK.GetParam(tx)->val = tg.x;
388SK.GetParam(ty)->val = tg.y;
389SK.GetParam(tz)->val = tg.z;
390
391SK.GetParam(qw)->val = qg.w;
392SK.GetParam(qx)->val = qg.vx;
393SK.GetParam(qy)->val = qg.vy;
394SK.GetParam(qz)->val = qg.vz;
395}
396
397bool Group::IsForcedToMeshBySource() const {
398const Group *srcg = this;
399if(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.
402srcg = SK.GetGroup(opA);
403if(srcg->forceToMesh) return true;
404}
405Group *g = srcg->RunningMeshGroup();
406if(g == NULL) return false;
407return g->forceToMesh || g->IsForcedToMeshBySource();
408}
409
410bool Group::IsForcedToMesh() const {
411return forceToMesh || IsTriangleMeshAssembly() || IsForcedToMeshBySource();
412}
413
414bool Group::IsTriangleMeshAssembly() const {
415if (type != Type::LINKED) return false;
416if (!impMesh.IsEmpty() && impShell.IsEmpty()) return true;
417return false;
418}
419
420std::string Group::DescriptionString() {
421if(name.empty()) {
422return ssprintf("g%03x-%s", h.v, _("(unnamed)"));
423} else {
424return ssprintf("g%03x-%s", h.v, name.c_str());
425}
426}
427
428void Group::Activate() {
429if(type == Type::DRAWING_WORKPLANE || type == Type::DRAWING_3D) {
430SS.GW.showFaces = SS.GW.showFacesDrawing;
431} else {
432SS.GW.showFaces = SS.GW.showFacesNonDrawing;
433}
434SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
435SS.ScheduleShowTW();
436}
437
438void Group::Generate(IdList<Entity,hEntity> *entity,
439IdList<Param,hParam> *param)
440{
441Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
442Vector gp = SS.GW.projRight.Plus(SS.GW.projUp);
443Vector gc = (SS.GW.offset).ScaledBy(-1);
444gn = gn.WithMagnitude(200/SS.GW.scale);
445gp = gp.WithMagnitude(200/SS.GW.scale);
446int a, i;
447switch(type) {
448case Type::DRAWING_3D:
449return;
450
451case Type::DRAWING_WORKPLANE: {
452Quaternion q;
453if(subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) {
454Vector u = SK.GetEntity(predef.entityB)->VectorGetNum();
455Vector v = SK.GetEntity(predef.entityC)->VectorGetNum();
456u = u.WithMagnitude(1);
457Vector n = u.Cross(v);
458v = (n.Cross(u)).WithMagnitude(1);
459
460if(predef.swapUV) swap(u, v);
461if(predef.negateU) u = u.ScaledBy(-1);
462if(predef.negateV) v = v.ScaledBy(-1);
463q = Quaternion::From(u, v);
464} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
465// Already given, numerically.
466q = predef.q;
467} else if(subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
468q = SK.GetEntity(predef.entityB)->NormalGetNum();
469} else ssassert(false, "Unexpected workplane subtype");
470
471Entity normal = {};
472normal.type = Entity::Type::NORMAL_N_COPY;
473normal.numNormal = q;
474
475normal.point[0] = h.entity(2);
476normal.group = h;
477normal.h = h.entity(1);
478entity->Add(&normal);
479
480Entity point = {};
481point.type = Entity::Type::POINT_N_COPY;
482point.numPoint = SK.GetEntity(predef.origin)->PointGetNum();
483point.construction = true;
484point.group = h;
485point.h = h.entity(2);
486entity->Add(&point);
487
488Entity wp = {};
489wp.type = Entity::Type::WORKPLANE;
490wp.normal = normal.h;
491wp.point[0] = point.h;
492wp.group = h;
493wp.h = h.entity(0);
494entity->Add(&wp);
495return;
496}
497
498case Type::EXTRUDE: {
499AddParam(param, h.param(0), gn.x);
500AddParam(param, h.param(1), gn.y);
501AddParam(param, h.param(2), gn.z);
502int ai, af;
503if(subtype == Subtype::ONE_SIDED) {
504ai = 0; af = 2;
505} else if(subtype == Subtype::TWO_SIDED) {
506ai = -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.
511hEntity pt = { 0 };
512// Not using range-for here because we're changing the size of entity in the loop.
513for(i = 0; i < entity->n; i++) {
514Entity *e = &(entity->Get(i));
515if(e->group != opA) continue;
516
517if(e->IsPoint()) pt = e->h;
518
519e->CalculateNumerical(/*forExport=*/false);
520hEntity 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.
523CopyEntity(entity, SK.GetEntity(he), ai, REMAP_BOTTOM,
524h.param(0), h.param(1), h.param(2),
525NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
526CopyAs::N_TRANS);
527CopyEntity(entity, SK.GetEntity(he), af, REMAP_TOP,
528h.param(0), h.param(1), h.param(2),
529NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
530CopyAs::N_TRANS);
531MakeExtrusionLines(entity, he);
532}
533// Remapped versions of that arbitrary point will be used to
534// provide points on the plane faces.
535MakeExtrusionTopBottomFaces(entity, pt);
536return;
537}
538
539case Type::LATHE: {
540Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
541Vector 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.
544for(i = 0; i < entity->n; i++) {
545Entity *e = &(entity->Get(i));
546if(e->group != opA) continue;
547
548e->CalculateNumerical(/*forExport=*/false);
549hEntity 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
555CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_START,
556NO_PARAM, NO_PARAM, NO_PARAM,
557NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
558CopyAs::NUMERIC);
559
560e = &(entity->Get(i)); // because we copied.
561if (e->IsPoint()) {
562// for points this copy is used for the circle centers
563CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_ARC_CENTER,
564NO_PARAM, NO_PARAM, NO_PARAM,
565NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
566CopyAs::NUMERIC);
567MakeLatheCircles(entity, param, he, axis_pos, axis_dir);
568};
569MakeLatheSurfacesSelectable(entity, he, axis_dir);
570}
571return;
572}
573
574case Type::REVOLVE: {
575// this was borrowed from LATHE and ROTATE
576Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
577Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
578
579// The center of rotation
580AddParam(param, h.param(0), axis_pos.x);
581AddParam(param, h.param(1), axis_pos.y);
582AddParam(param, h.param(2), axis_pos.z);
583// The rotation quaternion
584AddParam(param, h.param(3), 30 * PI / 180);
585AddParam(param, h.param(4), axis_dir.x);
586AddParam(param, h.param(5), axis_dir.y);
587AddParam(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.
591hEntity pt = { 0 };
592
593int ai = 0, af = 2;
594if (subtype == Subtype::TWO_SIDED)
595{
596ai = -1;
597af = 1;
598}
599// Not using range-for here because we're changing the size of entity in the loop.
600for(i = 0; i < entity->n; i++) {
601Entity *e = &(entity->Get(i));
602if(e->group != opA)
603continue;
604
605if(e->IsPoint()) pt = e->h;
606
607e->CalculateNumerical(/*forExport=*/false);
608hEntity he = e->h;
609// one copy for each end of the revolved surface
610CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0),
611h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
612h.param(6), NO_PARAM, CopyAs::N_ROT_AA);
613
614e = &(entity->Get(i)); // because we copied.
615CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0),
616h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
617h.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);
622MakeLatheSurfacesSelectable(entity, he, axis_dir);
623}
624MakeRevolveEndFaces(entity, pt, ai, af);
625return;
626}
627
628case Type::HELIX: {
629Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
630Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
631
632// The center of rotation
633AddParam(param, h.param(0), axis_pos.x);
634AddParam(param, h.param(1), axis_pos.y);
635AddParam(param, h.param(2), axis_pos.z);
636// The rotation quaternion
637AddParam(param, h.param(3), 30 * PI / 180);
638AddParam(param, h.param(4), axis_dir.x);
639AddParam(param, h.param(5), axis_dir.y);
640AddParam(param, h.param(6), axis_dir.z);
641// distance to translate along the rotation axis
642AddParam(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.
646hEntity pt = { 0 };
647
648int ai = 0, af = 2; // initial and final number of transformations
649if (subtype != Subtype::ONE_SIDED)
650{
651ai = -1;
652af = 1;
653}
654
655// Not using range-for here because we're changing the size of entity in the loop.
656for(i = 0; i < entity->n; i++) {
657Entity *e = &(entity->Get(i));
658if((e->group.v != opA.v) && !(e->h == predef.origin))
659continue;
660
661if(e->IsPoint()) pt = e->h;
662
663e->CalculateNumerical(/*forExport=*/false);
664
665// one copy for each end of the helix
666CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0),
667h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
668h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS);
669
670e = &(entity->Get(i)); // because we copied.
671CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0),
672h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
673h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS);
674
675// For point entities on the axis, create a construction line
676e = &(entity->Get(i));
677if(e->IsPoint()) {
678Vector check = e->PointGetNum().Minus(axis_pos).Cross(axis_dir);
679if (check.Dot(check) < LENGTH_EPS) {
680//! @todo isn't this the same as &(ent[i])?
681Entity *ep = SK.GetEntity(e->h);
682Entity en = {};
683// A point gets extruded to form a line segment
684en.point[0] = Remap(ep->h, REMAP_LATHE_START);
685en.point[1] = Remap(ep->h, REMAP_LATHE_END);
686en.group = h;
687en.construction = ep->construction;
688en.style = ep->style;
689en.h = Remap(ep->h, REMAP_PT_TO_LINE);
690en.type = Entity::Type::LINE_SEGMENT;
691entity->Add(&en);
692}
693}
694}
695MakeRevolveEndFaces(entity, pt, ai, af);
696return;
697}
698
699case Type::TRANSLATE: {
700// inherit meshCombine from source group
701Group *srcg = SK.GetGroup(opA);
702meshCombine = srcg->meshCombine;
703// The translation vector
704AddParam(param, h.param(0), gp.x);
705AddParam(param, h.param(1), gp.y);
706AddParam(param, h.param(2), gp.z);
707
708int n = (int)valA, a0 = 0;
709if(subtype == Subtype::ONE_SIDED && skipFirst) {
710a0++; n++;
711}
712
713for(a = a0; a < n; a++) {
714// Not using range-for here because we're changing the size of entity in the loop.
715for(i = 0; i < entity->n; i++) {
716Entity *e = &(entity->Get(i));
717if(e->group != opA) continue;
718
719e->CalculateNumerical(/*forExport=*/false);
720CopyEntity(entity, e,
721a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
722(a == (n - 1)) ? REMAP_LAST : a,
723h.param(0), h.param(1), h.param(2),
724NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
725CopyAs::N_TRANS);
726}
727}
728return;
729}
730case Type::ROTATE: {
731// inherit meshCombine from source group
732Group *srcg = SK.GetGroup(opA);
733meshCombine = srcg->meshCombine;
734// The center of rotation
735AddParam(param, h.param(0), gc.x);
736AddParam(param, h.param(1), gc.y);
737AddParam(param, h.param(2), gc.z);
738// The rotation quaternion
739AddParam(param, h.param(3), 30*PI/180);
740AddParam(param, h.param(4), gn.x);
741AddParam(param, h.param(5), gn.y);
742AddParam(param, h.param(6), gn.z);
743
744int n = (int)valA, a0 = 0;
745if(subtype == Subtype::ONE_SIDED && skipFirst) {
746a0++; n++;
747}
748
749for(a = a0; a < n; a++) {
750// Not using range-for here because we're changing the size of entity in the loop.
751for(i = 0; i < entity->n; i++) {
752Entity *e = &(entity->Get(i));
753if(e->group != opA) continue;
754
755e->CalculateNumerical(/*forExport=*/false);
756CopyEntity(entity, e,
757a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
758(a == (n - 1)) ? REMAP_LAST : a,
759h.param(0), h.param(1), h.param(2),
760h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
761CopyAs::N_ROT_AA);
762}
763}
764return;
765}
766case Type::LINKED:
767// The translation vector
768AddParam(param, h.param(0), gp.x);
769AddParam(param, h.param(1), gp.y);
770AddParam(param, h.param(2), gp.z);
771// The rotation quaternion
772AddParam(param, h.param(3), 1);
773AddParam(param, h.param(4), 0);
774AddParam(param, h.param(5), 0);
775AddParam(param, h.param(6), 0);
776
777// Not using range-for here because we're changing the size of entity in the loop.
778for(i = 0; i < impEntity.n; i++) {
779Entity *ie = &(impEntity[i]);
780CopyEntity(entity, ie, 0, 0,
781h.param(0), h.param(1), h.param(2),
782h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
783CopyAs::N_ROT_TRANS);
784}
785return;
786}
787ssassert(false, "Unexpected group type");
788}
789
790bool Group::IsSolvedOkay() {
791return this->solved.how == SolveResult::OKAY ||
792(this->allowRedundant && this->solved.how == SolveResult::REDUNDANT_OKAY);
793}
794
795void Group::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
796Equation eq;
797eq.e = expr;
798eq.h = h.equation(index);
799l->Add(&eq);
800}
801
802void Group::GenerateEquations(IdList<Equation,hEquation> *l) {
803if(type == Type::LINKED) {
804// Normalize the quaternion
805ExprQuaternion q = {
806Expr::From(h.param(3)),
807Expr::From(h.param(4)),
808Expr::From(h.param(5)),
809Expr::From(h.param(6)) };
810AddEq(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)))
815ExprVector orig = SK.GetEntity(predef.origin)->PointGetExprs();
816AddEq(l, (orig.x)->Minus(EP(0)), 0);
817AddEq(l, (orig.y)->Minus(EP(1)), 1);
818AddEq(l, (orig.z)->Minus(EP(2)), 2);
819// param 3 is the angle, which is free
820Vector axis = SK.GetEntity(predef.entityB)->VectorGetNum();
821axis = axis.WithMagnitude(1);
822AddEq(l, (EC(axis.x))->Minus(EP(4)), 3);
823AddEq(l, (EC(axis.y))->Minus(EP(5)), 4);
824AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
825#undef EC
826#undef EP
827if(type == Type::HELIX) {
828if(valB != 0.0) {
829AddEq(l, Expr::From(h.param(7))->Times(Expr::From(PI))->
830Minus(Expr::From(h.param(3))->Times(Expr::From(valB))), 6);
831}
832}
833} else if(type == Type::EXTRUDE) {
834if(predef.entityB != Entity::FREE_IN_3D) {
835// The extrusion path is locked along a line, normal to the
836// specified workplane.
837Entity *w = SK.GetEntity(predef.entityB);
838ExprVector u = w->Normal()->NormalExprsU();
839ExprVector v = w->Normal()->NormalExprsV();
840ExprVector extruden = {
841Expr::From(h.param(0)),
842Expr::From(h.param(1)),
843Expr::From(h.param(2)) };
844
845AddEq(l, u.Dot(extruden), 0);
846AddEq(l, v.Dot(extruden), 1);
847}
848} else if(type == Type::TRANSLATE) {
849if(predef.entityB != Entity::FREE_IN_3D) {
850Entity *w = SK.GetEntity(predef.entityB);
851ExprVector n = w->Normal()->NormalExprsN();
852ExprVector trans;
853trans = ExprVector::From(h.param(0), h.param(1), h.param(2));
854
855// The translation vector is parallel to the workplane
856AddEq(l, trans.Dot(n), 0);
857}
858}
859}
860
861hEntity Group::Remap(hEntity in, int copyNumber) {
862auto it = remap.find({ in, copyNumber });
863if(it == remap.end()) {
864std::tie(it, std::ignore) =
865remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() + 1 } });
866}
867return h.entity(it->second.v);
868}
869
870void Group::MakeExtrusionLines(IdList<Entity,hEntity> *el, hEntity in) {
871Entity *ep = SK.GetEntity(in);
872
873Entity en = {};
874if(ep->IsPoint()) {
875// A point gets extruded to form a line segment
876en.point[0] = Remap(ep->h, REMAP_TOP);
877en.point[1] = Remap(ep->h, REMAP_BOTTOM);
878en.group = h;
879en.construction = ep->construction;
880en.style = ep->style;
881en.h = Remap(ep->h, REMAP_PT_TO_LINE);
882en.type = Entity::Type::LINE_SEGMENT;
883el->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.
887Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
888Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
889Vector ab = b.Minus(a);
890
891en.param[0] = h.param(0);
892en.param[1] = h.param(1);
893en.param[2] = h.param(2);
894en.numPoint = a;
895en.numNormal = Quaternion::From(0, ab.x, ab.y, ab.z);
896
897en.group = h;
898en.construction = ep->construction;
899en.style = ep->style;
900en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
901en.type = Entity::Type::FACE_XPROD;
902el->Add(&en);
903}
904}
905
906void Group::MakeLatheCircles(IdList<Entity,hEntity> *el, IdList<Param,hParam> *param, hEntity in, Vector pt, Vector axis) {
907Entity *ep = SK.GetEntity(in);
908
909Entity en = {};
910if(ep->IsPoint()) {
911// A point gets revolved to form an arc.
912en.point[0] = Remap(ep->h, REMAP_LATHE_ARC_CENTER);
913en.point[1] = Remap(ep->h, REMAP_LATHE_START);
914en.point[2] = en.point[1]; //Remap(ep->h, REMAP_LATHE_END);
915
916// Get arc center and point on arc.
917Entity *pc = SK.GetEntity(en.point[0]);
918Entity *pp = SK.GetEntity(en.point[1]);
919
920// Project arc point to the revolution axis and use it for arc center.
921double k = pp->numPoint.Minus(pt).Dot(axis) / axis.Dot(axis);
922pc->numPoint = pt.Plus(axis.ScaledBy(k));
923
924// Create arc entity.
925en.group = h;
926en.construction = ep->construction;
927en.style = ep->style;
928en.h = Remap(ep->h, REMAP_PT_TO_ARC);
929en.type = Entity::Type::ARC_OF_CIRCLE;
930
931// Generate a normal.
932Entity n = {};
933n.workplane = en.workplane;
934n.h = Remap(ep->h, REMAP_PT_TO_NORMAL);
935n.group = en.group;
936n.style = en.style;
937n.type = Entity::Type::NORMAL_N_COPY;
938
939// Create basis for the normal.
940Vector nu = pp->numPoint.Minus(pc->numPoint).WithMagnitude(1.0);
941Vector nv = nu.Cross(axis).WithMagnitude(1.0);
942n.numNormal = Quaternion::From(nv, nu);
943
944// The point determines where the normal gets displayed on-screen;
945// it's entirely cosmetic.
946n.point[0] = en.point[0];
947el->Add(&n);
948en.normal = n.h;
949el->Add(&en);
950}
951}
952
953void Group::MakeLatheSurfacesSelectable(IdList<Entity, hEntity> *el, hEntity in, Vector axis) {
954Entity *ep = SK.GetEntity(in);
955
956Entity en = {};
957if(ep->type == Entity::Type::LINE_SEGMENT) {
958// An axis-perpendicular line gets revolved to form a face.
959Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
960Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
961Vector 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.
966if(fabs(u.Dot(axis) / axis.Magnitude()) < ANGLE_COS_EPS) {
967en.param[0] = h.param(0);
968en.param[1] = h.param(1);
969en.param[2] = h.param(2);
970Vector v = axis.Cross(u).WithMagnitude(1.0);
971Vector n = u.Cross(v);
972en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
973
974en.group = h;
975en.construction = ep->construction;
976en.style = ep->style;
977en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
978en.type = Entity::Type::FACE_NORMAL_PT;
979en.point[0] = ep->point[0];
980el->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.
988void Group::MakeRevolveEndFaces(IdList<Entity,hEntity> *el, hEntity pt, int ai, int af)
989{
990if(pt.v == 0) return;
991Group *src = SK.GetGroup(opA);
992Vector 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.
996if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) {
997n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN();
998}
999
1000Entity en = {};
1001en.type = Entity::Type::FACE_ROT_NORMAL_PT;
1002en.group = h;
1003// The center of rotation
1004en.param[0] = h.param(0);
1005en.param[1] = h.param(1);
1006en.param[2] = h.param(2);
1007// The rotation quaternion
1008en.param[3] = h.param(3);
1009en.param[4] = h.param(4);
1010en.param[5] = h.param(5);
1011en.param[6] = h.param(6);
1012
1013en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
1014en.point[0] = Remap(pt, REMAP_LATHE_START);
1015en.timesApplied = ai;
1016en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_START);
1017el->Add(&en);
1018
1019en.point[0] = Remap(pt, REMAP_LATHE_END);
1020en.timesApplied = af;
1021en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_END);
1022el->Add(&en);
1023}
1024
1025void Group::MakeExtrusionTopBottomFaces(IdList<Entity,hEntity> *el, hEntity pt)
1026{
1027if(pt.v == 0) return;
1028Group *src = SK.GetGroup(opA);
1029Vector 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.
1033if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) {
1034n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN();
1035}
1036
1037Entity en = {};
1038en.type = Entity::Type::FACE_NORMAL_PT;
1039en.group = h;
1040
1041en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
1042en.point[0] = Remap(pt, REMAP_TOP);
1043en.h = Remap(Entity::NO_ENTITY, REMAP_TOP);
1044el->Add(&en);
1045
1046en.point[0] = Remap(pt, REMAP_BOTTOM);
1047en.h = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
1048el->Add(&en);
1049}
1050
1051void Group::CopyEntity(IdList<Entity,hEntity> *el,
1052Entity *ep, int timesApplied, int remap,
1053hParam dx, hParam dy, hParam dz,
1054hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist,
1055CopyAs as)
1056{
1057Entity en = {};
1058en.type = ep->type;
1059en.extraPoints = ep->extraPoints;
1060en.h = Remap(ep->h, remap);
1061en.timesApplied = timesApplied;
1062en.group = h;
1063en.construction = ep->construction;
1064en.style = ep->style;
1065en.str = ep->str;
1066en.font = ep->font;
1067en.file = ep->file;
1068
1069switch(ep->type) {
1070case Entity::Type::WORKPLANE:
1071// Don't copy these.
1072return;
1073
1074case Entity::Type::POINT_N_COPY:
1075case Entity::Type::POINT_N_TRANS:
1076case Entity::Type::POINT_N_ROT_TRANS:
1077case Entity::Type::POINT_N_ROT_AA:
1078case Entity::Type::POINT_N_ROT_AXIS_TRANS:
1079case Entity::Type::POINT_IN_3D:
1080case Entity::Type::POINT_IN_2D:
1081if(as == CopyAs::N_TRANS) {
1082en.type = Entity::Type::POINT_N_TRANS;
1083en.param[0] = dx;
1084en.param[1] = dy;
1085en.param[2] = dz;
1086} else if(as == CopyAs::NUMERIC) {
1087en.type = Entity::Type::POINT_N_COPY;
1088} else {
1089if(as == CopyAs::N_ROT_AA) {
1090en.type = Entity::Type::POINT_N_ROT_AA;
1091} else if (as == CopyAs::N_ROT_AXIS_TRANS) {
1092en.type = Entity::Type::POINT_N_ROT_AXIS_TRANS;
1093} else {
1094en.type = Entity::Type::POINT_N_ROT_TRANS;
1095}
1096en.param[0] = dx;
1097en.param[1] = dy;
1098en.param[2] = dz;
1099en.param[3] = qw;
1100en.param[4] = qvx;
1101en.param[5] = qvy;
1102en.param[6] = qvz;
1103if (as == CopyAs::N_ROT_AXIS_TRANS) {
1104en.param[7] = dist;
1105}
1106}
1107en.numPoint = (ep->actPoint).ScaledBy(scale);
1108break;
1109
1110case Entity::Type::NORMAL_N_COPY:
1111case Entity::Type::NORMAL_N_ROT:
1112case Entity::Type::NORMAL_N_ROT_AA:
1113case Entity::Type::NORMAL_IN_3D:
1114case Entity::Type::NORMAL_IN_2D:
1115if(as == CopyAs::N_TRANS || as == CopyAs::NUMERIC) {
1116en.type = Entity::Type::NORMAL_N_COPY;
1117} else { // N_ROT_AXIS_TRANS probably doesn't warrant a new entity Type
1118if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) {
1119en.type = Entity::Type::NORMAL_N_ROT_AA;
1120} else {
1121en.type = Entity::Type::NORMAL_N_ROT;
1122}
1123en.param[0] = qw;
1124en.param[1] = qvx;
1125en.param[2] = qvy;
1126en.param[3] = qvz;
1127}
1128en.numNormal = ep->actNormal;
1129if(scale < 0) en.numNormal = en.numNormal.Mirror();
1130
1131en.point[0] = Remap(ep->point[0], remap);
1132break;
1133
1134case Entity::Type::DISTANCE_N_COPY:
1135case Entity::Type::DISTANCE:
1136en.type = Entity::Type::DISTANCE_N_COPY;
1137en.numDistance = ep->actDistance*fabs(scale);
1138break;
1139
1140case Entity::Type::FACE_NORMAL_PT:
1141case Entity::Type::FACE_XPROD:
1142case Entity::Type::FACE_N_ROT_TRANS:
1143case Entity::Type::FACE_N_TRANS:
1144case Entity::Type::FACE_N_ROT_AA:
1145case Entity::Type::FACE_ROT_NORMAL_PT:
1146case Entity::Type::FACE_N_ROT_AXIS_TRANS:
1147if(as == CopyAs::N_TRANS) {
1148en.type = Entity::Type::FACE_N_TRANS;
1149en.param[0] = dx;
1150en.param[1] = dy;
1151en.param[2] = dz;
1152} else if (as == CopyAs::NUMERIC) {
1153en.type = Entity::Type::FACE_NORMAL_PT;
1154} else if (as == CopyAs::N_ROT_AXIS_TRANS) {
1155en.type = Entity::Type::FACE_N_ROT_AXIS_TRANS;
1156en.param[0] = dx;
1157en.param[1] = dy;
1158en.param[2] = dz;
1159en.param[3] = qw;
1160en.param[4] = qvx;
1161en.param[5] = qvy;
1162en.param[6] = qvz;
1163en.param[7] = dist;
1164} else {
1165if(as == CopyAs::N_ROT_AA) {
1166en.type = Entity::Type::FACE_N_ROT_AA;
1167} else {
1168en.type = Entity::Type::FACE_N_ROT_TRANS;
1169}
1170en.param[0] = dx;
1171en.param[1] = dy;
1172en.param[2] = dz;
1173en.param[3] = qw;
1174en.param[4] = qvx;
1175en.param[5] = qvy;
1176en.param[6] = qvz;
1177}
1178en.numPoint = (ep->actPoint).ScaledBy(scale);
1179en.numNormal = (ep->actNormal).ScaledBy(scale);
1180break;
1181
1182default: {
1183if((Entity::Type::IMAGE == ep->type) && (true == ep->construction)) {
1184// Do not copy image entities if they are construction.
1185return;
1186}
1187
1188int i, points;
1189bool hasNormal, hasDistance;
1190EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
1191NULL, &points, &hasNormal, &hasDistance);
1192for(i = 0; i < points; i++) {
1193en.point[i] = Remap(ep->point[i], remap);
1194}
1195if(hasNormal) en.normal = Remap(ep->normal, remap);
1196if(hasDistance) en.distance = Remap(ep->distance, remap);
1197break;
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.
1205en.forceHidden = (!ep->actVisible) || ep->forceHidden;
1206
1207el->Add(&en);
1208}
1209
1210bool Group::ShouldDrawExploded() const {
1211return SS.explode && h == SS.GW.activeGroup && type == Type::DRAWING_WORKPLANE && !SS.exportMode;
1212}
1213