1 //-----------------------------------------------------------------------------
2 // Generate our model based on its parametric description, by solving each
3 // sketch, generating surfaces from the resulting entities, performing any
4 // requested surface operations (e.g. Booleans) with our model so far, and
5 // then repeating this process for each subsequent group.
6 //
7 // Copyright 2008-2013 Jonathan Westhues.
8 //-----------------------------------------------------------------------------
9 #include "solvespace.h"
10 
MarkGroupDirtyByEntity(hEntity he)11 void SolveSpaceUI::MarkGroupDirtyByEntity(hEntity he) {
12     Entity *e = SK.GetEntity(he);
13     MarkGroupDirty(e->group);
14 }
15 
MarkGroupDirty(hGroup hg)16 void SolveSpaceUI::MarkGroupDirty(hGroup hg) {
17     int i;
18     bool go = false;
19     for(i = 0; i < SK.groupOrder.n; i++) {
20         Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
21         if(g->h.v == hg.v) {
22             go = true;
23         }
24         if(go) {
25             g->clean = false;
26         }
27     }
28     unsaved = true;
29 }
30 
PruneOrphans(void)31 bool SolveSpaceUI::PruneOrphans(void) {
32     int i;
33     for(i = 0; i < SK.request.n; i++) {
34         Request *r = &(SK.request.elem[i]);
35         if(GroupExists(r->group)) continue;
36 
37         (deleted.requests)++;
38         SK.request.RemoveById(r->h);
39         return true;
40     }
41 
42     for(i = 0; i < SK.constraint.n; i++) {
43         Constraint *c = &(SK.constraint.elem[i]);
44         if(GroupExists(c->group)) continue;
45 
46         (deleted.constraints)++;
47         (deleted.nonTrivialConstraints)++;
48 
49         SK.constraint.RemoveById(c->h);
50         return true;
51     }
52     return false;
53 }
54 
GroupsInOrder(hGroup before,hGroup after)55 bool SolveSpaceUI::GroupsInOrder(hGroup before, hGroup after) {
56     if(before.v == 0) return true;
57     if(after.v  == 0) return true;
58 
59     int beforep = -1, afterp = -1;
60     int i;
61     for(i = 0; i < SK.groupOrder.n; i++) {
62         Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
63         if(g->h.v == before.v) beforep = i;
64         if(g->h.v == after.v)  afterp  = i;
65     }
66     if(beforep < 0 || afterp < 0) return false;
67     if(beforep >= afterp) return false;
68     return true;
69 }
70 
GroupExists(hGroup hg)71 bool SolveSpaceUI::GroupExists(hGroup hg) {
72     // A nonexistent group is not acceptable
73     return SK.group.FindByIdNoOops(hg) ? true : false;
74 }
EntityExists(hEntity he)75 bool SolveSpaceUI::EntityExists(hEntity he) {
76     // A nonexstient entity is acceptable, though, usually just means it
77     // doesn't apply.
78     if(he.v == Entity::NO_ENTITY.v) return true;
79     return SK.entity.FindByIdNoOops(he) ? true : false;
80 }
81 
PruneGroups(hGroup hg)82 bool SolveSpaceUI::PruneGroups(hGroup hg) {
83     Group *g = SK.GetGroup(hg);
84     if(GroupsInOrder(g->opA, hg) &&
85        EntityExists(g->predef.origin) &&
86        EntityExists(g->predef.entityB) &&
87        EntityExists(g->predef.entityC))
88     {
89         return false;
90     }
91     (deleted.groups)++;
92     SK.group.RemoveById(g->h);
93     return true;
94 }
95 
PruneRequests(hGroup hg)96 bool SolveSpaceUI::PruneRequests(hGroup hg) {
97     int i;
98     for(i = 0; i < SK.entity.n; i++) {
99         Entity *e = &(SK.entity.elem[i]);
100         if(e->group.v != hg.v) continue;
101 
102         if(EntityExists(e->workplane)) continue;
103 
104         if(!e->h.isFromRequest()) oops();
105 
106         (deleted.requests)++;
107         SK.request.RemoveById(e->h.request());
108         return true;
109     }
110     return false;
111 }
112 
PruneConstraints(hGroup hg)113 bool SolveSpaceUI::PruneConstraints(hGroup hg) {
114     int i;
115     for(i = 0; i < SK.constraint.n; i++) {
116         Constraint *c = &(SK.constraint.elem[i]);
117         if(c->group.v != hg.v) continue;
118 
119         if(EntityExists(c->workplane) &&
120            EntityExists(c->ptA) &&
121            EntityExists(c->ptB) &&
122            EntityExists(c->entityA) &&
123            EntityExists(c->entityB) &&
124            EntityExists(c->entityC) &&
125            EntityExists(c->entityD))
126         {
127             continue;
128         }
129 
130         (deleted.constraints)++;
131         if(c->type != Constraint::POINTS_COINCIDENT &&
132            c->type != Constraint::HORIZONTAL &&
133            c->type != Constraint::VERTICAL)
134         {
135             (deleted.nonTrivialConstraints)++;
136         }
137 
138         SK.constraint.RemoveById(c->h);
139         return true;
140     }
141     return false;
142 }
143 
GenerateAll(GenerateType type,bool andFindFree,bool genForBBox)144 void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForBBox) {
145     int first, last, i, j;
146 
147     SK.groupOrder.Clear();
148     for(int i = 0; i < SK.group.n; i++)
149         SK.groupOrder.Add(&SK.group.elem[i].h);
150     std::sort(&SK.groupOrder.elem[0], &SK.groupOrder.elem[SK.groupOrder.n],
151         [](const hGroup &ha, const hGroup &hb) {
152             return SK.GetGroup(ha)->order < SK.GetGroup(hb)->order;
153         });
154 
155     switch(type) {
156         case GENERATE_DIRTY: {
157             first = INT_MAX;
158             last  = 0;
159 
160             // Start from the first dirty group, and solve until the active group,
161             // since all groups after the active group are hidden.
162             for(i = 0; i < SK.groupOrder.n; i++) {
163                 Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
164                 if((!g->clean) || !g->IsSolvedOkay()) {
165                     first = min(first, i);
166                 }
167                 if(g->h.v == SS.GW.activeGroup.v) {
168                     last = i;
169                 }
170             }
171             if(first == INT_MAX || last == 0) {
172                 // All clean; so just regenerate the entities, and don't solve anything.
173                 first = -1;
174                 last  = -1;
175             } else {
176                 SS.nakedEdges.Clear();
177             }
178             break;
179         }
180 
181         case GENERATE_ALL:
182             first = 0;
183             last  = INT_MAX;
184             break;
185 
186         case GENERATE_REGEN:
187             first = -1;
188             last  = -1;
189             break;
190 
191         case GENERATE_UNTIL_ACTIVE: {
192             for(i = 0; i < SK.groupOrder.n; i++) {
193                 if(SK.groupOrder.elem[i].v == SS.GW.activeGroup.v)
194                     break;
195             }
196 
197             first = 0;
198             last  = i;
199             break;
200         }
201 
202         default: oops();
203     }
204 
205     // If we're generating entities for display, first we need to find
206     // the bounding box to turn relative chord tolerance to absolute.
207     if(!SS.exportMode && !genForBBox) {
208         GenerateAll(type, andFindFree, /*genForBBox=*/true);
209         BBox box = SK.CalculateEntityBBox(/*includeInvisibles=*/true);
210         Vector size = box.maxp.Minus(box.minp);
211         double maxSize = std::max({ size.x, size.y, size.z });
212         chordTolCalculated = maxSize * chordTol / 100.0;
213     }
214 
215     // Remove any requests or constraints that refer to a nonexistent
216     // group; can check those immediately, since we know what the list
217     // of groups should be.
218     while(PruneOrphans())
219         ;
220 
221     // Don't lose our numerical guesses when we regenerate.
222     IdList<Param,hParam> prev = {};
223     SK.param.MoveSelfInto(&prev);
224     SK.entity.Clear();
225 
226     int64_t inTime = GetMilliseconds();
227 
228     bool displayedStatusMessage = false;
229     for(i = 0; i < SK.groupOrder.n; i++) {
230         Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
231 
232         int64_t now = GetMilliseconds();
233         // Display the status message if we've taken more than 400 ms, or
234         // if we've taken 200 ms but we're not even halfway done, or if
235         // we've already started displaying the status message.
236         if( (now - inTime > 400) ||
237            ((now - inTime > 200) && i < (SK.groupOrder.n / 2)) ||
238            displayedStatusMessage)
239         {
240             displayedStatusMessage = true;
241             std::string msg = ssprintf("generating group %d/%d", i, SK.groupOrder.n);
242 
243             int w, h;
244             GetGraphicsWindowSize(&w, &h);
245             glDrawBuffer(GL_FRONT);
246             glViewport(0, 0, w, h);
247             glLoadIdentity();
248             glTranslated(-1, 1, 0);
249             glScaled(2.0/w, 2.0/h, 1.0);
250             glDisable(GL_DEPTH_TEST);
251 
252             double left = 80, top = -20, width = 240, height = 24;
253             glColor3d(0.9, 0.8, 0.8);
254             ssglAxisAlignedQuad(left, left+width, top, top-height);
255             ssglLineWidth(1);
256             glColor3d(0.0, 0.0, 0.0);
257             ssglAxisAlignedLineLoop(left, left+width, top, top-height);
258 
259             ssglInitializeBitmapFont();
260             glColor3d(0, 0, 0);
261             glPushMatrix();
262                 glTranslated(left+8, top-20, 0);
263                 glScaled(1, -1, 1);
264                 ssglBitmapText(msg, Vector::From(0, 0, 0));
265             glPopMatrix();
266             glFlush();
267             glDrawBuffer(GL_BACK);
268         }
269 
270         // The group may depend on entities or other groups, to define its
271         // workplane geometry or for its operands. Those must already exist
272         // in a previous group, so check them before generating.
273         if(PruneGroups(g->h))
274             goto pruned;
275 
276         for(j = 0; j < SK.request.n; j++) {
277             Request *r = &(SK.request.elem[j]);
278             if(r->group.v != g->h.v) continue;
279 
280             r->Generate(&(SK.entity), &(SK.param));
281         }
282         g->Generate(&(SK.entity), &(SK.param));
283 
284         // The requests and constraints depend on stuff in this or the
285         // previous group, so check them after generating.
286         if(PruneRequests(g->h) || PruneConstraints(g->h))
287             goto pruned;
288 
289         // Use the previous values for params that we've seen before, as
290         // initial guesses for the solver.
291         for(j = 0; j < SK.param.n; j++) {
292             Param *newp = &(SK.param.elem[j]);
293             if(newp->known) continue;
294 
295             Param *prevp = prev.FindByIdNoOops(newp->h);
296             if(prevp) {
297                 newp->val = prevp->val;
298                 newp->free = prevp->free;
299             }
300         }
301 
302         if(g->h.v == Group::HGROUP_REFERENCES.v) {
303             ForceReferences();
304             g->solved.how = System::SOLVED_OKAY;
305             g->clean = true;
306         } else {
307             if(i >= first && i <= last) {
308                 // The group falls inside the range, so really solve it,
309                 // and then regenerate the mesh based on the solved stuff.
310                 if(genForBBox) {
311                     SolveGroupAndReport(g->h, andFindFree);
312                 } else {
313                     g->GenerateLoops();
314                     g->GenerateShellAndMesh();
315                     g->clean = true;
316                 }
317             } else {
318                 // The group falls outside the range, so just assume that
319                 // it's good wherever we left it. The mesh is unchanged,
320                 // and the parameters must be marked as known.
321                 for(j = 0; j < SK.param.n; j++) {
322                     Param *newp = &(SK.param.elem[j]);
323 
324                     Param *prevp = prev.FindByIdNoOops(newp->h);
325                     if(prevp) newp->known = true;
326                 }
327             }
328         }
329     }
330 
331     // And update any reference dimensions with their new values
332     for(i = 0; i < SK.constraint.n; i++) {
333         Constraint *c = &(SK.constraint.elem[i]);
334         if(c->reference) {
335             c->ModifyToSatisfy();
336         }
337     }
338 
339     // Make sure the point that we're tracing exists.
340     if(traced.point.v && !SK.entity.FindByIdNoOops(traced.point)) {
341         traced.point = Entity::NO_ENTITY;
342     }
343     // And if we're tracing a point, add its new value to the path
344     if(traced.point.v) {
345         Entity *pt = SK.GetEntity(traced.point);
346         traced.path.AddPoint(pt->PointGetNum());
347     }
348 
349     prev.Clear();
350     InvalidateGraphics();
351 
352     // Remove nonexistent selection items, for same reason we waited till
353     // the end to put up a dialog box.
354     GW.ClearNonexistentSelectionItems();
355 
356     if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) {
357         // All sorts of interesting things could have happened; for example,
358         // the active group or active workplane could have been deleted. So
359         // clear all that out.
360         if(deleted.groups > 0) {
361             SS.TW.ClearSuper();
362         }
363         ScheduleShowTW();
364         GW.ClearSuper();
365 
366         // People get annoyed if I complain whenever they delete any request,
367         // and I otherwise will, since those always come with pt-coincident
368         // constraints.
369         if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 ||
370            deleted.groups > 0)
371         {
372             // Don't display any errors until we've regenerated fully. The
373             // sketch is not necessarily in a consistent state until we've
374             // pruned any orphaned etc. objects, and the message loop for the
375             // messagebox could allow us to repaint and crash. But now we must
376             // be fine.
377             Message("Additional sketch elements were deleted, because they "
378                     "depend on the element that was just deleted explicitly. "
379                     "These include: \n"
380                     "     %d request%s\n"
381                     "     %d constraint%s\n"
382                     "     %d group%s"
383                     "%s",
384                        deleted.requests, deleted.requests == 1 ? "" : "s",
385                        deleted.constraints, deleted.constraints == 1 ? "" : "s",
386                        deleted.groups, deleted.groups == 1 ? "" : "s",
387                        undo.cnt > 0 ? "\n\nChoose Edit -> Undo to undelete all elements." : "");
388         }
389 
390         deleted = {};
391     }
392 
393     FreeAllTemporary();
394     allConsistent = true;
395     return;
396 
397 pruned:
398     // Restore the numerical guesses
399     SK.param.Clear();
400     prev.MoveSelfInto(&(SK.param));
401     // Try again
402     GenerateAll(type, andFindFree, genForBBox);
403 }
404 
ForceReferences(void)405 void SolveSpaceUI::ForceReferences(void) {
406     // Force the values of the parameters that define the three reference
407     // coordinate systems.
408     static const struct {
409         hRequest    hr;
410         Quaternion  q;
411     } Quat[] = {
412         { Request::HREQUEST_REFERENCE_XY, { 1,    0,    0,    0,   } },
413         { Request::HREQUEST_REFERENCE_YZ, { 0.5,  0.5,  0.5,  0.5, } },
414         { Request::HREQUEST_REFERENCE_ZX, { 0.5, -0.5, -0.5, -0.5, } },
415     };
416     for(int i = 0; i < 3; i++) {
417         hRequest hr = Quat[i].hr;
418         Entity *wrkpl = SK.GetEntity(hr.entity(0));
419         // The origin for our coordinate system, always zero
420         Entity *origin = SK.GetEntity(wrkpl->point[0]);
421         origin->PointForceTo(Vector::From(0, 0, 0));
422         SK.GetParam(origin->param[0])->known = true;
423         SK.GetParam(origin->param[1])->known = true;
424         SK.GetParam(origin->param[2])->known = true;
425         // The quaternion that defines the rotation, from the table.
426         Entity *normal = SK.GetEntity(wrkpl->normal);
427         normal->NormalForceTo(Quat[i].q);
428         SK.GetParam(normal->param[0])->known = true;
429         SK.GetParam(normal->param[1])->known = true;
430         SK.GetParam(normal->param[2])->known = true;
431         SK.GetParam(normal->param[3])->known = true;
432     }
433 }
434 
MarkDraggedParams(void)435 void SolveSpaceUI::MarkDraggedParams(void) {
436     sys.dragged.Clear();
437 
438     for(int i = -1; i < SS.GW.pending.points.n; i++) {
439         hEntity hp;
440         if(i == -1) {
441             hp = SS.GW.pending.point;
442         } else {
443             hp = SS.GW.pending.points.elem[i];
444         }
445         if(!hp.v) continue;
446 
447         // The pending point could be one in a group that has not yet
448         // been processed, in which case the lookup will fail; but
449         // that's not an error.
450         Entity *pt = SK.entity.FindByIdNoOops(hp);
451         if(pt) {
452             switch(pt->type) {
453                 case Entity::POINT_N_TRANS:
454                 case Entity::POINT_IN_3D:
455                     sys.dragged.Add(&(pt->param[0]));
456                     sys.dragged.Add(&(pt->param[1]));
457                     sys.dragged.Add(&(pt->param[2]));
458                     break;
459 
460                 case Entity::POINT_IN_2D:
461                     sys.dragged.Add(&(pt->param[0]));
462                     sys.dragged.Add(&(pt->param[1]));
463                     break;
464             }
465         }
466     }
467     if(SS.GW.pending.circle.v) {
468         Entity *circ = SK.entity.FindByIdNoOops(SS.GW.pending.circle);
469         if(circ) {
470             Entity *dist = SK.GetEntity(circ->distance);
471             switch(dist->type) {
472                 case Entity::DISTANCE:
473                     sys.dragged.Add(&(dist->param[0]));
474                     break;
475             }
476         }
477     }
478     if(SS.GW.pending.normal.v) {
479         Entity *norm = SK.entity.FindByIdNoOops(SS.GW.pending.normal);
480         if(norm) {
481             switch(norm->type) {
482                 case Entity::NORMAL_IN_3D:
483                     sys.dragged.Add(&(norm->param[0]));
484                     sys.dragged.Add(&(norm->param[1]));
485                     sys.dragged.Add(&(norm->param[2]));
486                     sys.dragged.Add(&(norm->param[3]));
487                     break;
488                 // other types are locked, so not draggable
489             }
490         }
491     }
492 }
493 
SolveGroupAndReport(hGroup hg,bool andFindFree)494 void SolveSpaceUI::SolveGroupAndReport(hGroup hg, bool andFindFree) {
495     SolveGroup(hg, andFindFree);
496 
497     Group *g = SK.GetGroup(hg);
498     if(g->solved.how == System::REDUNDANT_OKAY) {
499         // Solve again, in case we lost a degree of freedom because of a numeric error.
500         SolveGroup(hg, andFindFree);
501     }
502 
503     bool isOkay = g->solved.how == System::SOLVED_OKAY ||
504                   (g->allowRedundant && g->solved.how == System::REDUNDANT_OKAY);
505 
506     if(!isOkay || (isOkay && !g->IsSolvedOkay())) {
507         TextWindow::ReportHowGroupSolved(g->h);
508     }
509 }
510 
SolveGroup(hGroup hg,bool andFindFree)511 void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) {
512     int i;
513     // Clear out the system to be solved.
514     sys.entity.Clear();
515     sys.param.Clear();
516     sys.eq.Clear();
517     // And generate all the params for requests in this group
518     for(i = 0; i < SK.request.n; i++) {
519         Request *r = &(SK.request.elem[i]);
520         if(r->group.v != hg.v) continue;
521 
522         r->Generate(&(sys.entity), &(sys.param));
523     }
524     // And for the group itself
525     Group *g = SK.GetGroup(hg);
526     g->Generate(&(sys.entity), &(sys.param));
527     // Set the initial guesses for all the params
528     for(i = 0; i < sys.param.n; i++) {
529         Param *p = &(sys.param.elem[i]);
530         p->known = false;
531         p->val = SK.GetParam(p->h)->val;
532     }
533 
534     MarkDraggedParams();
535     g->solved.remove.Clear();
536     int how = sys.Solve(g, &(g->solved.dof),
537                            &(g->solved.remove), true, andFindFree);
538     g->solved.how = how;
539     FreeAllTemporary();
540 }
541 
ActiveGroupsOkay()542 bool SolveSpaceUI::ActiveGroupsOkay() {
543     for(int i = 0; i < SK.groupOrder.n; i++) {
544         Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
545         if(!g->IsSolvedOkay())
546             return false;
547         if(g->h.v == SS.GW.activeGroup.v)
548             break;
549     }
550     return true;
551 }
552 
553