2013-07-28 22:08:34 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Generate our model based on its parametric description, by solving each
|
|
|
|
// sketch, generating surfaces from the resulting entities, performing any
|
|
|
|
// requested surface operations (e.g. Booleans) with our model so far, and
|
|
|
|
// then repeating this process for each subsequent group.
|
|
|
|
//
|
|
|
|
// Copyright 2008-2013 Jonathan Westhues.
|
|
|
|
//-----------------------------------------------------------------------------
|
2008-07-08 07:45:47 +00:00
|
|
|
#include "solvespace.h"
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
void SolveSpaceUI::MarkGroupDirtyByEntity(hEntity he) {
|
2009-04-19 05:53:16 +00:00
|
|
|
Entity *e = SK.GetEntity(he);
|
2008-07-08 07:45:47 +00:00
|
|
|
MarkGroupDirty(e->group);
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
void SolveSpaceUI::MarkGroupDirty(hGroup hg) {
|
2008-07-08 07:45:47 +00:00
|
|
|
int i;
|
|
|
|
bool go = false;
|
2016-02-17 10:03:07 +00:00
|
|
|
for(i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(g->h.v == hg.v) {
|
|
|
|
go = true;
|
|
|
|
}
|
|
|
|
if(go) {
|
|
|
|
g->clean = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unsaved = true;
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::PruneOrphans(void) {
|
2008-07-08 07:45:47 +00:00
|
|
|
int i;
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.request.n; i++) {
|
|
|
|
Request *r = &(SK.request.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(GroupExists(r->group)) continue;
|
|
|
|
|
|
|
|
(deleted.requests)++;
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.request.RemoveById(r->h);
|
2008-07-08 07:45:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.constraint.n; i++) {
|
|
|
|
Constraint *c = &(SK.constraint.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(GroupExists(c->group)) continue;
|
|
|
|
|
|
|
|
(deleted.constraints)++;
|
2009-01-03 12:27:33 +00:00
|
|
|
(deleted.nonTrivialConstraints)++;
|
|
|
|
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.constraint.RemoveById(c->h);
|
2008-07-08 07:45:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::GroupsInOrder(hGroup before, hGroup after) {
|
2008-07-08 07:45:47 +00:00
|
|
|
if(before.v == 0) return true;
|
|
|
|
if(after.v == 0) return true;
|
|
|
|
|
|
|
|
int beforep = -1, afterp = -1;
|
|
|
|
int i;
|
2016-02-17 10:03:07 +00:00
|
|
|
for(i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(g->h.v == before.v) beforep = i;
|
|
|
|
if(g->h.v == after.v) afterp = i;
|
|
|
|
}
|
|
|
|
if(beforep < 0 || afterp < 0) return false;
|
|
|
|
if(beforep >= afterp) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::GroupExists(hGroup hg) {
|
2008-07-08 07:45:47 +00:00
|
|
|
// A nonexistent group is not acceptable
|
2009-04-19 05:53:16 +00:00
|
|
|
return SK.group.FindByIdNoOops(hg) ? true : false;
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::EntityExists(hEntity he) {
|
2008-07-08 07:45:47 +00:00
|
|
|
// A nonexstient entity is acceptable, though, usually just means it
|
|
|
|
// doesn't apply.
|
|
|
|
if(he.v == Entity::NO_ENTITY.v) return true;
|
2009-04-19 05:53:16 +00:00
|
|
|
return SK.entity.FindByIdNoOops(he) ? true : false;
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::PruneGroups(hGroup hg) {
|
2009-04-19 05:53:16 +00:00
|
|
|
Group *g = SK.GetGroup(hg);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(GroupsInOrder(g->opA, hg) &&
|
|
|
|
EntityExists(g->predef.origin) &&
|
|
|
|
EntityExists(g->predef.entityB) &&
|
|
|
|
EntityExists(g->predef.entityC))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
(deleted.groups)++;
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.group.RemoveById(g->h);
|
2008-07-08 07:45:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::PruneRequests(hGroup hg) {
|
2008-07-08 07:45:47 +00:00
|
|
|
int i;
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.entity.n; i++) {
|
|
|
|
Entity *e = &(SK.entity.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(e->group.v != hg.v) continue;
|
|
|
|
|
|
|
|
if(EntityExists(e->workplane)) continue;
|
|
|
|
|
|
|
|
if(!e->h.isFromRequest()) oops();
|
|
|
|
|
|
|
|
(deleted.requests)++;
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.request.RemoveById(e->h.request());
|
2008-07-08 07:45:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
bool SolveSpaceUI::PruneConstraints(hGroup hg) {
|
2008-07-08 07:45:47 +00:00
|
|
|
int i;
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.constraint.n; i++) {
|
|
|
|
Constraint *c = &(SK.constraint.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(c->group.v != hg.v) continue;
|
|
|
|
|
|
|
|
if(EntityExists(c->workplane) &&
|
|
|
|
EntityExists(c->ptA) &&
|
|
|
|
EntityExists(c->ptB) &&
|
|
|
|
EntityExists(c->entityA) &&
|
2008-07-20 12:24:43 +00:00
|
|
|
EntityExists(c->entityB) &&
|
|
|
|
EntityExists(c->entityC) &&
|
|
|
|
EntityExists(c->entityD))
|
2008-07-08 07:45:47 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
(deleted.constraints)++;
|
2009-01-03 12:27:33 +00:00
|
|
|
if(c->type != Constraint::POINTS_COINCIDENT &&
|
|
|
|
c->type != Constraint::HORIZONTAL &&
|
|
|
|
c->type != Constraint::VERTICAL)
|
|
|
|
{
|
|
|
|
(deleted.nonTrivialConstraints)++;
|
|
|
|
}
|
|
|
|
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.constraint.RemoveById(c->h);
|
2008-07-08 07:45:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-02-17 10:03:07 +00:00
|
|
|
void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForBBox) {
|
|
|
|
int first, last, i, j;
|
|
|
|
|
|
|
|
SK.groupOrder.Clear();
|
|
|
|
for(int i = 0; i < SK.group.n; i++)
|
|
|
|
SK.groupOrder.Add(&SK.group.elem[i].h);
|
|
|
|
std::sort(&SK.groupOrder.elem[0], &SK.groupOrder.elem[SK.groupOrder.n],
|
|
|
|
[](const hGroup &ha, const hGroup &hb) {
|
|
|
|
return SK.GetGroup(ha)->order < SK.GetGroup(hb)->order;
|
|
|
|
});
|
2008-07-08 07:45:47 +00:00
|
|
|
|
2016-01-27 09:09:45 +00:00
|
|
|
switch(type) {
|
2016-02-17 10:03:07 +00:00
|
|
|
case GENERATE_DIRTY: {
|
|
|
|
first = INT_MAX;
|
|
|
|
last = 0;
|
|
|
|
|
|
|
|
// Start from the first dirty group, and solve until the active group,
|
|
|
|
// since all groups after the active group are hidden.
|
|
|
|
for(i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
|
|
|
|
if((!g->clean) || (g->solved.how != System::SOLVED_OKAY)) {
|
|
|
|
first = min(first, i);
|
|
|
|
}
|
|
|
|
if(g->h.v == SS.GW.activeGroup.v) {
|
|
|
|
last = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(first == INT_MAX || last == 0) {
|
|
|
|
// All clean; so just regenerate the entities, and don't solve anything.
|
|
|
|
first = -1;
|
|
|
|
last = -1;
|
|
|
|
} else {
|
|
|
|
SS.nakedEdges.Clear();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-01-27 09:09:45 +00:00
|
|
|
|
2016-02-17 10:03:07 +00:00
|
|
|
case GENERATE_ALL:
|
|
|
|
first = 0;
|
|
|
|
last = INT_MAX;
|
|
|
|
break;
|
2008-07-08 07:45:47 +00:00
|
|
|
|
2016-02-17 10:03:07 +00:00
|
|
|
case GENERATE_REGEN:
|
|
|
|
first = -1;
|
|
|
|
last = -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GENERATE_UNTIL_ACTIVE: {
|
|
|
|
for(i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
if(SK.groupOrder.elem[i].v == SS.GW.activeGroup.v)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
first = 0;
|
|
|
|
last = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: oops();
|
2016-01-27 08:19:40 +00:00
|
|
|
}
|
|
|
|
|
Use relative chord tolerance instead of absolute.
Commit 89eb208 has improved the overall situation with chord
tolerance, but it changed the display chord tolerance to use
an absolute value in millimeters as a stopgap measure.
This commit changes the display chord tolerance to be specified
in percents of entity bounding box instead of millimeters.
As a result, the linearized curves are both zoom level and sketch
scale independent.
In order to compute the bounding box, all entities are generated
twice. However, this shouldn't result in a noticeable slowdown,
since the bounding box calculation does not need the expensive
triangle mesh generation and the solver will converge immediately
on the second run.
Since the meaning of the preference has changed, a new name is
used (ChordTolerancePct instead of ChordTolerance), so that it
would be reset to the default value after updating SolveSpace.
The default value, 0.5%, was selected using trial and error by
judging whether cylinders of moderate dimensions were looking
aesthetically pleasing enough.
After this change, the only real function of the spacebar
shortcut is to reload imported groups, since manual regeneration
should not change anything anymore unless there is a bug.
2016-01-29 10:33:56 +00:00
|
|
|
// If we're generating entities for display, first we need to find
|
|
|
|
// the bounding box to turn relative chord tolerance to absolute.
|
|
|
|
if(!SS.exportMode && !genForBBox) {
|
2016-02-17 10:03:07 +00:00
|
|
|
GenerateAll(type, /*andFindFree=*/false, /*genForBBox=*/true);
|
2016-02-18 10:09:29 +00:00
|
|
|
BBox box = SK.CalculateEntityBBox(/*includeInvisibles=*/true);
|
Use relative chord tolerance instead of absolute.
Commit 89eb208 has improved the overall situation with chord
tolerance, but it changed the display chord tolerance to use
an absolute value in millimeters as a stopgap measure.
This commit changes the display chord tolerance to be specified
in percents of entity bounding box instead of millimeters.
As a result, the linearized curves are both zoom level and sketch
scale independent.
In order to compute the bounding box, all entities are generated
twice. However, this shouldn't result in a noticeable slowdown,
since the bounding box calculation does not need the expensive
triangle mesh generation and the solver will converge immediately
on the second run.
Since the meaning of the preference has changed, a new name is
used (ChordTolerancePct instead of ChordTolerance), so that it
would be reset to the default value after updating SolveSpace.
The default value, 0.5%, was selected using trial and error by
judging whether cylinders of moderate dimensions were looking
aesthetically pleasing enough.
After this change, the only real function of the spacebar
shortcut is to reload imported groups, since manual regeneration
should not change anything anymore unless there is a bug.
2016-01-29 10:33:56 +00:00
|
|
|
Vector size = box.maxp.Minus(box.minp);
|
|
|
|
double maxSize = std::max({ size.x, size.y, size.z });
|
|
|
|
chordTolCalculated = maxSize * chordTol / 100.0;
|
|
|
|
}
|
|
|
|
|
2008-07-20 11:27:22 +00:00
|
|
|
// Remove any requests or constraints that refer to a nonexistent
|
|
|
|
// group; can check those immediately, since we know what the list
|
|
|
|
// of groups should be.
|
2008-07-08 07:45:47 +00:00
|
|
|
while(PruneOrphans())
|
|
|
|
;
|
|
|
|
|
|
|
|
// Don't lose our numerical guesses when we regenerate.
|
2015-03-27 15:31:23 +00:00
|
|
|
IdList<Param,hParam> prev = {};
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.param.MoveSelfInto(&prev);
|
|
|
|
SK.entity.Clear();
|
2008-07-08 07:45:47 +00:00
|
|
|
|
2013-10-28 04:28:39 +00:00
|
|
|
int64_t inTime = GetMilliseconds();
|
2009-06-14 04:36:38 +00:00
|
|
|
|
|
|
|
bool displayedStatusMessage = false;
|
2016-02-17 10:03:07 +00:00
|
|
|
for(i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
|
2013-10-28 04:28:39 +00:00
|
|
|
int64_t now = GetMilliseconds();
|
2009-06-14 04:36:38 +00:00
|
|
|
// Display the status message if we've taken more than 400 ms, or
|
|
|
|
// if we've taken 200 ms but we're not even halfway done, or if
|
|
|
|
// we've already started displaying the status message.
|
|
|
|
if( (now - inTime > 400) ||
|
2016-02-17 10:03:07 +00:00
|
|
|
((now - inTime > 200) && i < (SK.groupOrder.n / 2)) ||
|
2009-06-14 04:36:38 +00:00
|
|
|
displayedStatusMessage)
|
|
|
|
{
|
|
|
|
displayedStatusMessage = true;
|
2016-02-17 10:03:07 +00:00
|
|
|
std::string msg = ssprintf("generating group %d/%d", i, SK.groupOrder.n);
|
2009-06-14 04:36:38 +00:00
|
|
|
|
|
|
|
int w, h;
|
|
|
|
GetGraphicsWindowSize(&w, &h);
|
|
|
|
glDrawBuffer(GL_FRONT);
|
|
|
|
glViewport(0, 0, w, h);
|
|
|
|
glLoadIdentity();
|
|
|
|
glTranslated(-1, 1, 0);
|
|
|
|
glScaled(2.0/w, 2.0/h, 1.0);
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
|
|
|
|
double left = 80, top = -20, width = 240, height = 24;
|
|
|
|
glColor3d(0.9, 0.8, 0.8);
|
2013-10-22 04:45:06 +00:00
|
|
|
ssglAxisAlignedQuad(left, left+width, top, top-height);
|
2015-03-22 13:39:12 +00:00
|
|
|
ssglLineWidth(1);
|
2009-06-14 04:36:38 +00:00
|
|
|
glColor3d(0.0, 0.0, 0.0);
|
2013-10-22 04:45:06 +00:00
|
|
|
ssglAxisAlignedLineLoop(left, left+width, top, top-height);
|
2009-06-14 04:36:38 +00:00
|
|
|
|
2015-11-05 19:39:27 +00:00
|
|
|
ssglInitializeBitmapFont();
|
2009-06-14 04:36:38 +00:00
|
|
|
glColor3d(0, 0, 0);
|
|
|
|
glPushMatrix();
|
2010-04-26 07:52:49 +00:00
|
|
|
glTranslated(left+8, top-20, 0);
|
|
|
|
glScaled(1, -1, 1);
|
2016-02-14 20:13:40 +00:00
|
|
|
ssglBitmapText(msg, Vector::From(0, 0, 0));
|
2009-06-14 04:36:38 +00:00
|
|
|
glPopMatrix();
|
|
|
|
glFlush();
|
|
|
|
glDrawBuffer(GL_BACK);
|
|
|
|
}
|
|
|
|
|
2008-07-08 07:45:47 +00:00
|
|
|
// The group may depend on entities or other groups, to define its
|
|
|
|
// workplane geometry or for its operands. Those must already exist
|
|
|
|
// in a previous group, so check them before generating.
|
|
|
|
if(PruneGroups(g->h))
|
|
|
|
goto pruned;
|
|
|
|
|
2009-04-19 05:53:16 +00:00
|
|
|
for(j = 0; j < SK.request.n; j++) {
|
|
|
|
Request *r = &(SK.request.elem[j]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(r->group.v != g->h.v) continue;
|
|
|
|
|
2009-04-19 05:53:16 +00:00
|
|
|
r->Generate(&(SK.entity), &(SK.param));
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
2009-04-19 05:53:16 +00:00
|
|
|
g->Generate(&(SK.entity), &(SK.param));
|
2008-07-08 07:45:47 +00:00
|
|
|
|
|
|
|
// The requests and constraints depend on stuff in this or the
|
|
|
|
// previous group, so check them after generating.
|
|
|
|
if(PruneRequests(g->h) || PruneConstraints(g->h))
|
|
|
|
goto pruned;
|
|
|
|
|
|
|
|
// Use the previous values for params that we've seen before, as
|
|
|
|
// initial guesses for the solver.
|
2009-04-19 05:53:16 +00:00
|
|
|
for(j = 0; j < SK.param.n; j++) {
|
|
|
|
Param *newp = &(SK.param.elem[j]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(newp->known) continue;
|
|
|
|
|
|
|
|
Param *prevp = prev.FindByIdNoOops(newp->h);
|
|
|
|
if(prevp) newp->val = prevp->val;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(g->h.v == Group::HGROUP_REFERENCES.v) {
|
|
|
|
ForceReferences();
|
2009-04-20 07:30:09 +00:00
|
|
|
g->solved.how = System::SOLVED_OKAY;
|
2008-07-08 07:45:47 +00:00
|
|
|
g->clean = true;
|
|
|
|
} else {
|
|
|
|
if(i >= first && i <= last) {
|
|
|
|
// The group falls inside the range, so really solve it,
|
|
|
|
// and then regenerate the mesh based on the solved stuff.
|
Use relative chord tolerance instead of absolute.
Commit 89eb208 has improved the overall situation with chord
tolerance, but it changed the display chord tolerance to use
an absolute value in millimeters as a stopgap measure.
This commit changes the display chord tolerance to be specified
in percents of entity bounding box instead of millimeters.
As a result, the linearized curves are both zoom level and sketch
scale independent.
In order to compute the bounding box, all entities are generated
twice. However, this shouldn't result in a noticeable slowdown,
since the bounding box calculation does not need the expensive
triangle mesh generation and the solver will converge immediately
on the second run.
Since the meaning of the preference has changed, a new name is
used (ChordTolerancePct instead of ChordTolerance), so that it
would be reset to the default value after updating SolveSpace.
The default value, 0.5%, was selected using trial and error by
judging whether cylinders of moderate dimensions were looking
aesthetically pleasing enough.
After this change, the only real function of the spacebar
shortcut is to reload imported groups, since manual regeneration
should not change anything anymore unless there is a bug.
2016-01-29 10:33:56 +00:00
|
|
|
if(genForBBox) {
|
|
|
|
SolveGroup(g->h, andFindFree);
|
|
|
|
} else {
|
|
|
|
g->GenerateLoops();
|
|
|
|
g->GenerateShellAndMesh();
|
|
|
|
g->clean = true;
|
|
|
|
}
|
2008-07-08 07:45:47 +00:00
|
|
|
} else {
|
|
|
|
// The group falls outside the range, so just assume that
|
|
|
|
// it's good wherever we left it. The mesh is unchanged,
|
|
|
|
// and the parameters must be marked as known.
|
2009-04-19 05:53:16 +00:00
|
|
|
for(j = 0; j < SK.param.n; j++) {
|
|
|
|
Param *newp = &(SK.param.elem[j]);
|
2008-07-08 07:45:47 +00:00
|
|
|
|
|
|
|
Param *prevp = prev.FindByIdNoOops(newp->h);
|
|
|
|
if(prevp) newp->known = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And update any reference dimensions with their new values
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.constraint.n; i++) {
|
|
|
|
Constraint *c = &(SK.constraint.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(c->reference) {
|
|
|
|
c->ModifyToSatisfy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-20 11:27:22 +00:00
|
|
|
// Make sure the point that we're tracing exists.
|
2009-04-19 05:53:16 +00:00
|
|
|
if(traced.point.v && !SK.entity.FindByIdNoOops(traced.point)) {
|
2008-07-20 11:27:22 +00:00
|
|
|
traced.point = Entity::NO_ENTITY;
|
|
|
|
}
|
|
|
|
// And if we're tracing a point, add its new value to the path
|
|
|
|
if(traced.point.v) {
|
2009-04-19 05:53:16 +00:00
|
|
|
Entity *pt = SK.GetEntity(traced.point);
|
2008-07-20 11:27:22 +00:00
|
|
|
traced.path.AddPoint(pt->PointGetNum());
|
|
|
|
}
|
|
|
|
|
2008-07-08 07:45:47 +00:00
|
|
|
prev.Clear();
|
|
|
|
InvalidateGraphics();
|
|
|
|
|
|
|
|
// Remove nonexistent selection items, for same reason we waited till
|
|
|
|
// the end to put up a dialog box.
|
|
|
|
GW.ClearNonexistentSelectionItems();
|
|
|
|
|
|
|
|
if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) {
|
|
|
|
// All sorts of interesting things could have happened; for example,
|
|
|
|
// the active group or active workplane could have been deleted. So
|
|
|
|
// clear all that out.
|
|
|
|
if(deleted.groups > 0) {
|
|
|
|
SS.TW.ClearSuper();
|
|
|
|
}
|
2015-03-18 17:02:11 +00:00
|
|
|
ScheduleShowTW();
|
2008-07-08 07:45:47 +00:00
|
|
|
GW.ClearSuper();
|
2009-01-03 12:27:33 +00:00
|
|
|
|
|
|
|
// People get annoyed if I complain whenever they delete any request,
|
|
|
|
// and I otherwise will, since those always come with pt-coincident
|
|
|
|
// constraints.
|
|
|
|
if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 ||
|
|
|
|
deleted.groups > 0)
|
|
|
|
{
|
|
|
|
// Don't display any errors until we've regenerated fully. The
|
|
|
|
// sketch is not necessarily in a consistent state until we've
|
|
|
|
// pruned any orphaned etc. objects, and the message loop for the
|
|
|
|
// messagebox could allow us to repaint and crash. But now we must
|
|
|
|
// be fine.
|
2010-06-14 04:42:47 +00:00
|
|
|
Message("Additional sketch elements were deleted, because they "
|
|
|
|
"depend on the element that was just deleted explicitly. "
|
|
|
|
"These include: \n"
|
|
|
|
" %d request%s\n"
|
|
|
|
" %d constraint%s\n"
|
2016-01-11 11:00:07 +00:00
|
|
|
" %d group%s"
|
|
|
|
"%s",
|
2010-06-14 04:42:47 +00:00
|
|
|
deleted.requests, deleted.requests == 1 ? "" : "s",
|
|
|
|
deleted.constraints, deleted.constraints == 1 ? "" : "s",
|
2016-01-11 11:00:07 +00:00
|
|
|
deleted.groups, deleted.groups == 1 ? "" : "s",
|
|
|
|
undo.cnt > 0 ? "\n\nChoose Edit -> Undo to undelete all elements." : "");
|
2009-01-03 12:27:33 +00:00
|
|
|
}
|
2015-03-27 15:31:23 +00:00
|
|
|
|
|
|
|
deleted = {};
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
2015-03-29 00:30:52 +00:00
|
|
|
|
2008-07-08 07:45:47 +00:00
|
|
|
FreeAllTemporary();
|
|
|
|
allConsistent = true;
|
|
|
|
return;
|
|
|
|
|
|
|
|
pruned:
|
|
|
|
// Restore the numerical guesses
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.param.Clear();
|
|
|
|
prev.MoveSelfInto(&(SK.param));
|
2008-07-08 07:45:47 +00:00
|
|
|
// Try again
|
2016-02-17 10:03:07 +00:00
|
|
|
GenerateAll(type, andFindFree, genForBBox);
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
void SolveSpaceUI::ForceReferences(void) {
|
2010-05-10 01:06:09 +00:00
|
|
|
// Force the values of the parameters that define the three reference
|
2008-07-08 07:45:47 +00:00
|
|
|
// coordinate systems.
|
|
|
|
static const struct {
|
|
|
|
hRequest hr;
|
|
|
|
Quaternion q;
|
|
|
|
} Quat[] = {
|
|
|
|
{ Request::HREQUEST_REFERENCE_XY, { 1, 0, 0, 0, } },
|
|
|
|
{ Request::HREQUEST_REFERENCE_YZ, { 0.5, 0.5, 0.5, 0.5, } },
|
|
|
|
{ Request::HREQUEST_REFERENCE_ZX, { 0.5, -0.5, -0.5, -0.5, } },
|
|
|
|
};
|
|
|
|
for(int i = 0; i < 3; i++) {
|
|
|
|
hRequest hr = Quat[i].hr;
|
2009-04-19 05:53:16 +00:00
|
|
|
Entity *wrkpl = SK.GetEntity(hr.entity(0));
|
2008-07-08 07:45:47 +00:00
|
|
|
// The origin for our coordinate system, always zero
|
2009-04-19 05:53:16 +00:00
|
|
|
Entity *origin = SK.GetEntity(wrkpl->point[0]);
|
2008-07-08 07:45:47 +00:00
|
|
|
origin->PointForceTo(Vector::From(0, 0, 0));
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.GetParam(origin->param[0])->known = true;
|
|
|
|
SK.GetParam(origin->param[1])->known = true;
|
|
|
|
SK.GetParam(origin->param[2])->known = true;
|
2008-07-08 07:45:47 +00:00
|
|
|
// The quaternion that defines the rotation, from the table.
|
2015-03-29 00:30:52 +00:00
|
|
|
Entity *normal = SK.GetEntity(wrkpl->normal);
|
2008-07-08 07:45:47 +00:00
|
|
|
normal->NormalForceTo(Quat[i].q);
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.GetParam(normal->param[0])->known = true;
|
|
|
|
SK.GetParam(normal->param[1])->known = true;
|
|
|
|
SK.GetParam(normal->param[2])->known = true;
|
|
|
|
SK.GetParam(normal->param[3])->known = true;
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
void SolveSpaceUI::MarkDraggedParams(void) {
|
2009-11-03 18:54:49 +00:00
|
|
|
sys.dragged.Clear();
|
|
|
|
|
|
|
|
for(int i = -1; i < SS.GW.pending.points.n; i++) {
|
|
|
|
hEntity hp;
|
|
|
|
if(i == -1) {
|
|
|
|
hp = SS.GW.pending.point;
|
|
|
|
} else {
|
|
|
|
hp = SS.GW.pending.points.elem[i];
|
|
|
|
}
|
|
|
|
if(!hp.v) continue;
|
2009-04-20 07:30:09 +00:00
|
|
|
|
|
|
|
// The pending point could be one in a group that has not yet
|
|
|
|
// been processed, in which case the lookup will fail; but
|
|
|
|
// that's not an error.
|
2009-11-03 18:54:49 +00:00
|
|
|
Entity *pt = SK.entity.FindByIdNoOops(hp);
|
2009-04-20 07:30:09 +00:00
|
|
|
if(pt) {
|
|
|
|
switch(pt->type) {
|
|
|
|
case Entity::POINT_N_TRANS:
|
|
|
|
case Entity::POINT_IN_3D:
|
2009-11-03 18:54:49 +00:00
|
|
|
sys.dragged.Add(&(pt->param[0]));
|
|
|
|
sys.dragged.Add(&(pt->param[1]));
|
|
|
|
sys.dragged.Add(&(pt->param[2]));
|
2009-04-20 07:30:09 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Entity::POINT_IN_2D:
|
2009-11-03 18:54:49 +00:00
|
|
|
sys.dragged.Add(&(pt->param[0]));
|
|
|
|
sys.dragged.Add(&(pt->param[1]));
|
2009-04-20 07:30:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(SS.GW.pending.circle.v) {
|
|
|
|
Entity *circ = SK.entity.FindByIdNoOops(SS.GW.pending.circle);
|
|
|
|
if(circ) {
|
|
|
|
Entity *dist = SK.GetEntity(circ->distance);
|
|
|
|
switch(dist->type) {
|
|
|
|
case Entity::DISTANCE:
|
2009-11-03 18:54:49 +00:00
|
|
|
sys.dragged.Add(&(dist->param[0]));
|
2009-04-20 07:30:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(SS.GW.pending.normal.v) {
|
|
|
|
Entity *norm = SK.entity.FindByIdNoOops(SS.GW.pending.normal);
|
|
|
|
if(norm) {
|
|
|
|
switch(norm->type) {
|
|
|
|
case Entity::NORMAL_IN_3D:
|
2009-11-03 18:54:49 +00:00
|
|
|
sys.dragged.Add(&(norm->param[0]));
|
|
|
|
sys.dragged.Add(&(norm->param[1]));
|
|
|
|
sys.dragged.Add(&(norm->param[2]));
|
|
|
|
sys.dragged.Add(&(norm->param[3]));
|
2009-04-20 07:30:09 +00:00
|
|
|
break;
|
|
|
|
// other types are locked, so not draggable
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-23 17:49:04 +00:00
|
|
|
void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) {
|
2008-07-08 07:45:47 +00:00
|
|
|
int i;
|
|
|
|
// Clear out the system to be solved.
|
|
|
|
sys.entity.Clear();
|
|
|
|
sys.param.Clear();
|
|
|
|
sys.eq.Clear();
|
|
|
|
// And generate all the params for requests in this group
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.request.n; i++) {
|
|
|
|
Request *r = &(SK.request.elem[i]);
|
2008-07-08 07:45:47 +00:00
|
|
|
if(r->group.v != hg.v) continue;
|
|
|
|
|
|
|
|
r->Generate(&(sys.entity), &(sys.param));
|
|
|
|
}
|
|
|
|
// And for the group itself
|
2009-04-19 05:53:16 +00:00
|
|
|
Group *g = SK.GetGroup(hg);
|
2008-07-08 07:45:47 +00:00
|
|
|
g->Generate(&(sys.entity), &(sys.param));
|
|
|
|
// Set the initial guesses for all the params
|
|
|
|
for(i = 0; i < sys.param.n; i++) {
|
|
|
|
Param *p = &(sys.param.elem[i]);
|
|
|
|
p->known = false;
|
2009-04-19 05:53:16 +00:00
|
|
|
p->val = SK.GetParam(p->h)->val;
|
2008-07-08 07:45:47 +00:00
|
|
|
}
|
|
|
|
|
2009-04-20 07:30:09 +00:00
|
|
|
MarkDraggedParams();
|
|
|
|
g->solved.remove.Clear();
|
|
|
|
int how = sys.Solve(g, &(g->solved.dof),
|
2009-04-21 07:56:17 +00:00
|
|
|
&(g->solved.remove), true, andFindFree);
|
2016-01-21 15:01:43 +00:00
|
|
|
bool isOkay = how == System::SOLVED_OKAY ||
|
|
|
|
(g->allowRedundant && how == System::REDUNDANT_OKAY);
|
|
|
|
if(!isOkay || (isOkay && !g->IsSolvedOkay()))
|
2009-04-20 07:30:09 +00:00
|
|
|
{
|
|
|
|
TextWindow::ReportHowGroupSolved(g->h);
|
|
|
|
}
|
|
|
|
g->solved.how = how;
|
2008-07-08 07:45:47 +00:00
|
|
|
FreeAllTemporary();
|
|
|
|
}
|
|
|
|
|
Only consider groups until active when checking for solver failure.
After commit 2f734d9, inactive groups are no longer regenerated
for trivial changes, e.g. changing parameters, so it's possible to
switch to an earlier group and work on it without incurring
the computational (slowdown) and cognitive (annoyance by red
background) overhead of later groups failing to solve.
However, if a group--any group anywhere--was not solved OK,
the interface reacted accordingly, which diminished usefulness of
the change, especially given that, if we have groups A and B with
B depending on A, if B is broken by a change in A and we activate A
and fix it, B will not be regenerated.
After this commit, only active groups are considered when deciding
if generating the entire sketch would fail.
2016-02-13 22:14:02 +00:00
|
|
|
bool SolveSpaceUI::ActiveGroupsOkay() {
|
2016-02-17 10:03:07 +00:00
|
|
|
for(int i = 0; i < SK.groupOrder.n; i++) {
|
|
|
|
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
|
|
|
|
if(!g->IsSolvedOkay())
|
2016-01-21 15:01:43 +00:00
|
|
|
return false;
|
2016-02-17 10:03:07 +00:00
|
|
|
if(g->h.v == SS.GW.activeGroup.v)
|
Only consider groups until active when checking for solver failure.
After commit 2f734d9, inactive groups are no longer regenerated
for trivial changes, e.g. changing parameters, so it's possible to
switch to an earlier group and work on it without incurring
the computational (slowdown) and cognitive (annoyance by red
background) overhead of later groups failing to solve.
However, if a group--any group anywhere--was not solved OK,
the interface reacted accordingly, which diminished usefulness of
the change, especially given that, if we have groups A and B with
B depending on A, if B is broken by a change in A and we activate A
and fix it, B will not be regenerated.
After this commit, only active groups are considered when deciding
if generating the entire sketch would fail.
2016-02-13 22:14:02 +00:00
|
|
|
break;
|
2008-07-20 11:27:22 +00:00
|
|
|
}
|
2016-01-21 15:01:43 +00:00
|
|
|
return true;
|
2008-07-20 11:27:22 +00:00
|
|
|
}
|
|
|
|
|