diff --git a/doc/reference.txt b/doc/reference.txt
index 10a1fbd..9f8db15 100644
--- a/doc/reference.txt
+++ b/doc/reference.txt
@@ -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
its new value, instead of changing it in a single step.
+
Measure Volume
+
+ 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.
+
Export
diff --git a/file.cpp b/file.cpp
index e23d392..c61c44f 100644
--- a/file.cpp
+++ b/file.cpp
@@ -62,6 +62,7 @@ void SolveSpace::NewFile(void) {
const SolveSpace::SaveTable SolveSpace::SAVED[] = {
{ 'g', "Group.h.v", 'x', &(SS.sv.g.h.v) },
{ '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.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) },
{ 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) },
diff --git a/generate.cpp b/generate.cpp
index 1a77aac..20d6b0f 100644
--- a/generate.cpp
+++ b/generate.cpp
@@ -127,16 +127,31 @@ bool SolveSpace::PruneConstraints(hGroup hg) {
void SolveSpace::GenerateAll(void) {
int i;
int firstDirty = INT_MAX, lastVisible = 0;
+ bool markVvMeshDirty = false;
// 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 < group.n; i++) {
Group *g = &(group.elem[i]);
+ g->order = i;
if((!g->clean) || (g->solved.how != Group::SOLVED_OKAY)) {
firstDirty = min(firstDirty, i);
+ markVvMeshDirty = true;
}
if(g->h.v == SS.GW.activeGroup.v) {
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) {
// All clean; so just regenerate the entities, and don't solve anything.
diff --git a/groupmesh.cpp b/groupmesh.cpp
index d4dc3aa..00547a1 100644
--- a/groupmesh.cpp
+++ b/groupmesh.cpp
@@ -538,11 +538,14 @@ void Group::GenerateMesh(void) {
}
done:
- emphEdges.Clear();
- if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) {
- SKdNode *root = SKdNode::From(&runningMesh);
- root->SnapToMesh(&runningMesh);
- root->MakeCertainEdgesInto(&emphEdges, true);
+ if(!vvMeshClean) {
+ emphEdges.Clear();
+ if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) {
+ SKdNode *root = SKdNode::From(&runningMesh);
+ root->SnapToMesh(&runningMesh);
+ root->MakeCertainEdgesInto(&emphEdges, true);
+ }
+ vvMeshClean = true;
}
}
diff --git a/sketch.h b/sketch.h
index e568182..4174ff6 100644
--- a/sketch.h
+++ b/sketch.h
@@ -89,10 +89,13 @@ public:
static const int IMPORTED = 5300;
int type;
+ int order;
+
hGroup opA;
hGroup opB;
bool visible;
bool clean;
+ bool vvMeshClean;
hEntity activeWorkplane;
double valA;
double valB;
diff --git a/solvespace.cpp b/solvespace.cpp
index a0e2a7c..a27ff9e 100644
--- a/solvespace.cpp
+++ b/solvespace.cpp
@@ -358,8 +358,66 @@ void SolveSpace::MenuAnalyze(int id) {
}
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;
+ }
case GraphicsWindow::MNU_TRACE_PT:
if(gs.points == 1 && gs.n == 1) {
diff --git a/textscreens.cpp b/textscreens.cpp
index fc30f12..4913d28 100644
--- a/textscreens.cpp
+++ b/textscreens.cpp
@@ -674,6 +674,23 @@ void TextWindow::ShowStepDimension(void) {
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.
//-----------------------------------------------------------------------------
diff --git a/textwin.cpp b/textwin.cpp
index 6f995b0..a7676ba 100644
--- a/textwin.cpp
+++ b/textwin.cpp
@@ -222,6 +222,7 @@ void TextWindow::Show(void) {
case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;
case SCREEN_CONFIGURATION: ShowConfiguration(); break;
case SCREEN_STEP_DIMENSION: ShowStepDimension(); break;
+ case SCREEN_MESH_VOLUME: ShowMeshVolume(); break;
}
}
Printf(false, "");
diff --git a/ui.h b/ui.h
index 2ba0d7a..9241141 100644
--- a/ui.h
+++ b/ui.h
@@ -47,6 +47,7 @@ public:
static const int SCREEN_GROUP_SOLVE_INFO = 2;
static const int SCREEN_CONFIGURATION = 3;
static const int SCREEN_STEP_DIMENSION = 4;
+ static const int SCREEN_MESH_VOLUME = 5;
typedef struct {
int screen;
@@ -56,6 +57,8 @@ public:
bool dimIsDistance;
double dimFinish;
int dimSteps;
+
+ double volume;
} ShownState;
ShownState shown;
@@ -94,6 +97,7 @@ public:
void ShowGroupSolveInfo(void);
void ShowConfiguration(void);
void ShowStepDimension(void);
+ void ShowMeshVolume(void);
// Special screen, based on selection
void DescribeSelection(void);
diff --git a/undoredo.cpp b/undoredo.cpp
index 45b72d2..00928cc 100644
--- a/undoredo.cpp
+++ b/undoredo.cpp
@@ -46,6 +46,7 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
// 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.
dest.clean = false;
+ dest.vvMeshClean = false;
ZERO(&(dest.solved));
ZERO(&(dest.poly));
ZERO(&(dest.polyError));
diff --git a/wishlist.txt b/wishlist.txt
index 693eda7..446fd7f 100644
--- a/wishlist.txt
+++ b/wishlist.txt
@@ -1,8 +1,6 @@
get rid of the oops() calls in the mesh codes
-smooth shading
leak fixing
-cylindrical faces?
long term