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