Add code to evaluate the volume of a mesh, thanks to the divergence
theorem; it's evaluated as a surface integral over each triangle. And don't regenerate the emphasized edges unless we have to; specifically, don't do it when the only dirty group is the drawing group. [git-p4: depot-paths = "//depot/solvespace/": change = 1849]
This commit is contained in:
parent
115dbce61b
commit
7b7d2f92e9
@ -862,6 +862,18 @@ for new users; to learn about this program, see the video tutorials.
|
|||||||
this case, it may be useful to try stepping the dimension to
|
this case, it may be useful to try stepping the dimension to
|
||||||
its new value, instead of changing it in a single step.
|
its new value, instead of changing it in a single step.
|
||||||
|
|
||||||
|
<h3>Measure Volume</h3>
|
||||||
|
|
||||||
|
This feature reports the volume of the part. Depending on
|
||||||
|
the active units, the volume is given in cubic inches, or in
|
||||||
|
millilitres and cubic millimeters.
|
||||||
|
|
||||||
|
If the part contains smooth curves (e.g. circles), then the
|
||||||
|
mesh is not an exact representation of the geometry. This means
|
||||||
|
that the measured volume is only an approximation; for a mesh
|
||||||
|
that looks fairly smooth on-screen, expect an error around one
|
||||||
|
percent. To decrease this error, choose a finer chord tolerance.
|
||||||
|
|
||||||
|
|
||||||
<h2>Export</h2>
|
<h2>Export</h2>
|
||||||
|
|
||||||
|
1
file.cpp
1
file.cpp
@ -62,6 +62,7 @@ void SolveSpace::NewFile(void) {
|
|||||||
const SolveSpace::SaveTable SolveSpace::SAVED[] = {
|
const SolveSpace::SaveTable SolveSpace::SAVED[] = {
|
||||||
{ 'g', "Group.h.v", 'x', &(SS.sv.g.h.v) },
|
{ 'g', "Group.h.v", 'x', &(SS.sv.g.h.v) },
|
||||||
{ 'g', "Group.type", 'd', &(SS.sv.g.type) },
|
{ 'g', "Group.type", 'd', &(SS.sv.g.type) },
|
||||||
|
{ 'g', "Group.order", 'd', &(SS.sv.g.order) },
|
||||||
{ 'g', "Group.name", 'N', &(SS.sv.g.name) },
|
{ 'g', "Group.name", 'N', &(SS.sv.g.name) },
|
||||||
{ 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) },
|
{ 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) },
|
||||||
{ 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) },
|
{ 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) },
|
||||||
|
15
generate.cpp
15
generate.cpp
@ -127,16 +127,31 @@ bool SolveSpace::PruneConstraints(hGroup hg) {
|
|||||||
void SolveSpace::GenerateAll(void) {
|
void SolveSpace::GenerateAll(void) {
|
||||||
int i;
|
int i;
|
||||||
int firstDirty = INT_MAX, lastVisible = 0;
|
int firstDirty = INT_MAX, lastVisible = 0;
|
||||||
|
bool markVvMeshDirty = false;
|
||||||
// Start from the first dirty group, and solve until the active group,
|
// Start from the first dirty group, and solve until the active group,
|
||||||
// since all groups after the active group are hidden.
|
// since all groups after the active group are hidden.
|
||||||
for(i = 0; i < group.n; i++) {
|
for(i = 0; i < group.n; i++) {
|
||||||
Group *g = &(group.elem[i]);
|
Group *g = &(group.elem[i]);
|
||||||
|
g->order = i;
|
||||||
if((!g->clean) || (g->solved.how != Group::SOLVED_OKAY)) {
|
if((!g->clean) || (g->solved.how != Group::SOLVED_OKAY)) {
|
||||||
firstDirty = min(firstDirty, i);
|
firstDirty = min(firstDirty, i);
|
||||||
|
markVvMeshDirty = true;
|
||||||
}
|
}
|
||||||
if(g->h.v == SS.GW.activeGroup.v) {
|
if(g->h.v == SS.GW.activeGroup.v) {
|
||||||
lastVisible = i;
|
lastVisible = i;
|
||||||
}
|
}
|
||||||
|
if(markVvMeshDirty) {
|
||||||
|
if(firstDirty == i &&
|
||||||
|
(g->type == Group::DRAWING_3D ||
|
||||||
|
g->type == Group::DRAWING_WORKPLANE))
|
||||||
|
{
|
||||||
|
// These groups don't change the mesh, so there's no need
|
||||||
|
// to regenerate the vertex-to-vertex mesh if they're the
|
||||||
|
// first dirty one.
|
||||||
|
} else {
|
||||||
|
g->vvMeshClean = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(firstDirty == INT_MAX || lastVisible == 0) {
|
if(firstDirty == INT_MAX || lastVisible == 0) {
|
||||||
// All clean; so just regenerate the entities, and don't solve anything.
|
// All clean; so just regenerate the entities, and don't solve anything.
|
||||||
|
@ -538,12 +538,15 @@ void Group::GenerateMesh(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
if(!vvMeshClean) {
|
||||||
emphEdges.Clear();
|
emphEdges.Clear();
|
||||||
if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) {
|
if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) {
|
||||||
SKdNode *root = SKdNode::From(&runningMesh);
|
SKdNode *root = SKdNode::From(&runningMesh);
|
||||||
root->SnapToMesh(&runningMesh);
|
root->SnapToMesh(&runningMesh);
|
||||||
root->MakeCertainEdgesInto(&emphEdges, true);
|
root->MakeCertainEdgesInto(&emphEdges, true);
|
||||||
}
|
}
|
||||||
|
vvMeshClean = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SMesh *Group::PreviousGroupMesh(void) {
|
SMesh *Group::PreviousGroupMesh(void) {
|
||||||
|
3
sketch.h
3
sketch.h
@ -89,10 +89,13 @@ public:
|
|||||||
static const int IMPORTED = 5300;
|
static const int IMPORTED = 5300;
|
||||||
int type;
|
int type;
|
||||||
|
|
||||||
|
int order;
|
||||||
|
|
||||||
hGroup opA;
|
hGroup opA;
|
||||||
hGroup opB;
|
hGroup opB;
|
||||||
bool visible;
|
bool visible;
|
||||||
bool clean;
|
bool clean;
|
||||||
|
bool vvMeshClean;
|
||||||
hEntity activeWorkplane;
|
hEntity activeWorkplane;
|
||||||
double valA;
|
double valA;
|
||||||
double valB;
|
double valB;
|
||||||
|
@ -358,8 +358,66 @@ void SolveSpace::MenuAnalyze(int id) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GraphicsWindow::MNU_VOLUME:
|
case GraphicsWindow::MNU_VOLUME: {
|
||||||
|
SMesh *m = &(SS.GetGroup(SS.GW.activeGroup)->runningMesh);
|
||||||
|
|
||||||
|
double vol = 0;
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < m->l.n; i++) {
|
||||||
|
STriangle tr = m->l.elem[i];
|
||||||
|
|
||||||
|
// Translate to place vertex A at (x, y, 0)
|
||||||
|
Vector trans = Vector::From(tr.a.x, tr.a.y, 0);
|
||||||
|
tr.a = (tr.a).Minus(trans);
|
||||||
|
tr.b = (tr.b).Minus(trans);
|
||||||
|
tr.c = (tr.c).Minus(trans);
|
||||||
|
|
||||||
|
// Rotate to place vertex B on the y-axis. Depending on
|
||||||
|
// whether the triangle is CW or CCW, C is either to the
|
||||||
|
// right or to the left of the y-axis. This handles the
|
||||||
|
// sign of our normal.
|
||||||
|
Vector u = Vector::From(-tr.b.y, tr.b.x, 0);
|
||||||
|
u = u.WithMagnitude(1);
|
||||||
|
Vector v = Vector::From(tr.b.x, tr.b.y, 0);
|
||||||
|
v = v.WithMagnitude(1);
|
||||||
|
Vector n = Vector::From(0, 0, 1);
|
||||||
|
|
||||||
|
tr.a = (tr.a).DotInToCsys(u, v, n);
|
||||||
|
tr.b = (tr.b).DotInToCsys(u, v, n);
|
||||||
|
tr.c = (tr.c).DotInToCsys(u, v, n);
|
||||||
|
|
||||||
|
n = tr.Normal().WithMagnitude(1);
|
||||||
|
|
||||||
|
// Triangles on edge don't contribute
|
||||||
|
if(fabs(n.z) < LENGTH_EPS) continue;
|
||||||
|
|
||||||
|
// The plane has equation p dot n = a dot n
|
||||||
|
double d = (tr.a).Dot(n);
|
||||||
|
// nx*x + ny*y + nz*z = d
|
||||||
|
// nz*z = d - nx*x - ny*y
|
||||||
|
double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z;
|
||||||
|
|
||||||
|
double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x;
|
||||||
|
double xc = tr.c.x, yb = tr.b.y;
|
||||||
|
|
||||||
|
// I asked Maple for
|
||||||
|
// int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc);
|
||||||
|
double integral =
|
||||||
|
(1.0/3)*(
|
||||||
|
A*(mbc-mac)+
|
||||||
|
(1.0/2)*B*(mbc*mbc-mac*mac)
|
||||||
|
)*(xc*xc*xc)+
|
||||||
|
(1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+
|
||||||
|
C*yb*xc+
|
||||||
|
(1.0/2)*B*yb*yb*xc;
|
||||||
|
|
||||||
|
vol += integral;
|
||||||
|
}
|
||||||
|
SS.TW.shown.volume = vol;
|
||||||
|
SS.TW.GoToScreen(TextWindow::SCREEN_MESH_VOLUME);
|
||||||
|
SS.later.showTW = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case GraphicsWindow::MNU_TRACE_PT:
|
case GraphicsWindow::MNU_TRACE_PT:
|
||||||
if(gs.points == 1 && gs.n == 1) {
|
if(gs.points == 1 && gs.n == 1) {
|
||||||
|
@ -674,6 +674,23 @@ void TextWindow::ShowStepDimension(void) {
|
|||||||
Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
|
Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// A report of the volume of the mesh. No interaction, output-only.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void TextWindow::ShowMeshVolume(void) {
|
||||||
|
Printf(true, "%FtMESH VOLUME");
|
||||||
|
|
||||||
|
if(SS.viewUnits == SolveSpace::UNIT_INCHES) {
|
||||||
|
Printf(true, " %3 in^3", shown.volume/(25.4*25.4*25.4));
|
||||||
|
} else {
|
||||||
|
Printf(true, " %2 mm^3", shown.volume);
|
||||||
|
Printf(false, " %2 mL", shown.volume/(10*10*10));
|
||||||
|
}
|
||||||
|
|
||||||
|
Printf(true, "%Fl%Ll%f(back)%E", &ScreenHome);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// The edit control is visible, and the user just pressed enter.
|
// The edit control is visible, and the user just pressed enter.
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -222,6 +222,7 @@ void TextWindow::Show(void) {
|
|||||||
case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;
|
case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;
|
||||||
case SCREEN_CONFIGURATION: ShowConfiguration(); break;
|
case SCREEN_CONFIGURATION: ShowConfiguration(); break;
|
||||||
case SCREEN_STEP_DIMENSION: ShowStepDimension(); break;
|
case SCREEN_STEP_DIMENSION: ShowStepDimension(); break;
|
||||||
|
case SCREEN_MESH_VOLUME: ShowMeshVolume(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Printf(false, "");
|
Printf(false, "");
|
||||||
|
4
ui.h
4
ui.h
@ -47,6 +47,7 @@ public:
|
|||||||
static const int SCREEN_GROUP_SOLVE_INFO = 2;
|
static const int SCREEN_GROUP_SOLVE_INFO = 2;
|
||||||
static const int SCREEN_CONFIGURATION = 3;
|
static const int SCREEN_CONFIGURATION = 3;
|
||||||
static const int SCREEN_STEP_DIMENSION = 4;
|
static const int SCREEN_STEP_DIMENSION = 4;
|
||||||
|
static const int SCREEN_MESH_VOLUME = 5;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int screen;
|
int screen;
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ public:
|
|||||||
bool dimIsDistance;
|
bool dimIsDistance;
|
||||||
double dimFinish;
|
double dimFinish;
|
||||||
int dimSteps;
|
int dimSteps;
|
||||||
|
|
||||||
|
double volume;
|
||||||
} ShownState;
|
} ShownState;
|
||||||
ShownState shown;
|
ShownState shown;
|
||||||
|
|
||||||
@ -94,6 +97,7 @@ public:
|
|||||||
void ShowGroupSolveInfo(void);
|
void ShowGroupSolveInfo(void);
|
||||||
void ShowConfiguration(void);
|
void ShowConfiguration(void);
|
||||||
void ShowStepDimension(void);
|
void ShowStepDimension(void);
|
||||||
|
void ShowMeshVolume(void);
|
||||||
// Special screen, based on selection
|
// Special screen, based on selection
|
||||||
void DescribeSelection(void);
|
void DescribeSelection(void);
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
|
|||||||
// And then clean up all the stuff that needs to be a deep copy,
|
// And then clean up all the stuff that needs to be a deep copy,
|
||||||
// and zero out all the dynamic stuff that will get regenerated.
|
// and zero out all the dynamic stuff that will get regenerated.
|
||||||
dest.clean = false;
|
dest.clean = false;
|
||||||
|
dest.vvMeshClean = false;
|
||||||
ZERO(&(dest.solved));
|
ZERO(&(dest.solved));
|
||||||
ZERO(&(dest.poly));
|
ZERO(&(dest.poly));
|
||||||
ZERO(&(dest.polyError));
|
ZERO(&(dest.polyError));
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
get rid of the oops() calls in the mesh codes
|
get rid of the oops() calls in the mesh codes
|
||||||
smooth shading
|
|
||||||
leak fixing
|
leak fixing
|
||||||
cylindrical faces?
|
|
||||||
|
|
||||||
|
|
||||||
long term
|
long term
|
||||||
|
Loading…
Reference in New Issue
Block a user