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