diff --git a/doc/reference.txt b/doc/reference.txt
index f45f0702..10a1fbda 100644
--- a/doc/reference.txt
+++ b/doc/reference.txt
@@ -801,6 +801,67 @@ for new users; to learn about this program, see the video tutorials.
computer, it's necessary to transfer all of the imported files
as well.
+
Analysis
+
+ Trace Point
+
+ MechSketch can draw a "trail" behind a point as it moves. This
+ is useful when designing mechanisms. The sketch shown below is
+ a four-bar linkage:
+
+
+
+ As the linkage is worked, the midpoint of the top link moves
+ along the cyan curve. This image was produced by drawing the
+ linkage, tracing that midpoint, and then dragging the linkage
+ through its full range of motion with the mouse.
+
+ To start tracing, select a point, and choose Analyze -> Trace
+ Point. When the point moves, a cyan trail will be drawn behind it.
+
+ To stop tracing, choose Analyze -> Stop Tracing. A dialog
+ will appear, with the option to save the trace as a CSV
+ (comma-separated value) file. To save the trace, enter a
+ filename. To abandon the trace, choose Cancel or hit Esc.
+
+ The trace is saved as a text file, with one point per line. Each
+ point appears in the format x, y, z, separated by commas. Many
+ programs, including spreadsheets like Excel, can read this format.
+ The units for the coordinates are determined by the export
+ scale factor. (If the export scale factor is 1, then they are
+ millimeters, and if it's 25.4, then they're inches.)
+
+ If the mechanism is worked by dragging it with the mouse, then
+ the points in the trace will be unevenly spaced, because the
+ motion of the mouse is irregular. A plot of x vs. y (like the
+ cyan trace above) is not affected, but a plot of x or y vs. t
+ is useless, because the "speed" along the curve is not constant.
+
+ To avoid this problem, move the point by stepping a dimension,
+ rather than by dragging with the mouse. Select the dimension to
+ be stepped; this can be any distance or angle. Choose Analyze ->
+ Step Dimension. Enter the new final value for that dimension,
+ and the number of steps; then click "step dimension now".
+
+ The dimension will be modified in multiple steps, and solved
+ at each intermediate value. For example, consider a dimension
+ that is now set to 10 degrees. The user steps this dimension to
+ 30 degrees, in 10 steps. This means that MechSketch will solve
+ at 12 degrees, then 14 degrees, then 16, and so on, until it
+ reaches 30 degrees.
+
+ The position of the traced point will be recorded at each
+ intermediate value. When the trace is exported, it represents
+ the position of that point, as the dimensioned link rotates with
+ constant angular speed.
+
+ The step dimension feature can also improve convergence. In some
+ difficult cases, the solver will fail to find a solution when a
+ dimension is changed. If the specified constraints have multiple
+ solutions, then the solver may also find an undesired solution. In
+ this case, it may be useful to try stepping the dimension to
+ its new value, instead of changing it in a single step.
+
Export
diff --git a/draw.cpp b/draw.cpp
index 2bac1589..41a5ddae 100644
--- a/draw.cpp
+++ b/draw.cpp
@@ -918,13 +918,7 @@ void GraphicsWindow::Paint(int w, int h) {
// At the same depth, we want later lines drawn over earlier.
glDepthFunc(GL_LEQUAL);
- bool allOk = true;
- for(i = 0; i < SS.group.n; i++) {
- if(SS.group.elem[i].solved.how != Group::SOLVED_OKAY) {
- allOk = false;
- }
- }
- if(allOk) {
+ if(SS.AllGroupsOkay()) {
glClearColor(0, 0, 0, 1.0f);
} else {
// Draw a red background whenever we're having solve problems.
@@ -974,7 +968,7 @@ void GraphicsWindow::Paint(int w, int h) {
glxUnlockColor();
- // Draw the groups; this fills the polygons in a drawing group, and
+ // Draw the active group; this fills the polygons in a drawing group, and
// draws the solid mesh.
(SS.GetGroup(activeGroup))->Draw();
@@ -988,6 +982,16 @@ void GraphicsWindow::Paint(int w, int h) {
SS.constraint.elem[i].Draw();
}
+ // Draw the traced path, if one exists
+ glLineWidth(1);
+ glColor3d(0, 1, 1);
+ SContour *sc = &(SS.traced.path);
+ glBegin(GL_LINE_STRIP);
+ for(i = 0; i < sc->l.n; i++) {
+ glxVertex3v(sc->l.elem[i].p);
+ }
+ glEnd();
+
// Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST);
glxLockColorTo(1, 1, 0);
diff --git a/generate.cpp b/generate.cpp
index e58e9a40..589904e6 100644
--- a/generate.cpp
+++ b/generate.cpp
@@ -148,6 +148,9 @@ void SolveSpace::GenerateAll(void) {
void SolveSpace::GenerateAll(int first, int last) {
int i, j;
+ // 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.
while(PruneOrphans())
;
@@ -222,6 +225,16 @@ void SolveSpace::GenerateAll(int first, int last) {
}
}
+ // Make sure the point that we're tracing exists.
+ if(traced.point.v && !entity.FindByIdNoOops(traced.point)) {
+ traced.point = Entity::NO_ENTITY;
+ }
+ // And if we're tracing a point, add its new value to the path
+ if(traced.point.v) {
+ Entity *pt = GetEntity(traced.point);
+ traced.path.AddPoint(pt->PointGetNum());
+ }
+
prev.Clear();
InvalidateGraphics();
@@ -325,3 +338,14 @@ void SolveSpace::SolveGroup(hGroup hg) {
FreeAllTemporary();
}
+bool SolveSpace::AllGroupsOkay(void) {
+ int i;
+ bool allOk = true;
+ for(i = 0; i < group.n; i++) {
+ if(group.elem[i].solved.how != Group::SOLVED_OKAY) {
+ allOk = false;
+ }
+ }
+ return allOk;
+}
+
diff --git a/graphicswin.cpp b/graphicswin.cpp
index 70d5a8d2..32ac73b8 100644
--- a/graphicswin.cpp
+++ b/graphicswin.cpp
@@ -6,6 +6,7 @@
#define mCon (&Constraint::MenuConstrain)
#define mFile (&SolveSpace::MenuFile)
#define mGrp (&Group::MenuGroup)
+#define mAna (&SolveSpace::MenuAnalyze)
#define S 0x100
#define C 0x200
const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
@@ -93,6 +94,13 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 1, NULL, 0, NULL },
{ 1, "Comment\t;", MNU_COMMENT, ';', mCon },
+{ 0, "&Analyze", 0, NULL },
+{ 1, "Measure &Volume", MNU_VOLUME, 0, mAna },
+{ 1, NULL, 0, NULL },
+{ 1, "&Trace Point\tCtrl+Shift+T", MNU_TRACE_PT, 'T'|S|C,mAna },
+{ 1, "&Stop Tracing...\tCtrl+Shift+S", MNU_STOP_TRACING, 'S'|S|C,mAna },
+{ 1, "Step &Dimension...\tCtrl+Shift+D", MNU_STEP_DIM, 'D'|S|C,mAna },
+
{ 0, "&Help", 0, NULL },
{ 1, "&Load License...", 0, NULL },
{ 1, NULL, 0, NULL },
diff --git a/solvespace.cpp b/solvespace.cpp
index 0e3726bc..a0e2a7cf 100644
--- a/solvespace.cpp
+++ b/solvespace.cpp
@@ -140,9 +140,20 @@ double SolveSpace::ExprToMm(Expr *e) {
return e->Eval();
}
}
+double SolveSpace::StringToMm(char *str) {
+ if(viewUnits == UNIT_INCHES) {
+ return atof(str)*25.4;
+ } else {
+ return atof(str);
+ }
+}
void SolveSpace::AfterNewFile(void) {
+ // Clear out the traced point, which is no longer valid
+ traced.point = Entity::NO_ENTITY;
+ traced.path.l.Clear();
+
ReloadAllImported();
GenerateAll(-1, -1);
@@ -318,3 +329,73 @@ void SolveSpace::MenuFile(int id) {
SS.UpdateWindowTitle();
}
+
+void SolveSpace::MenuAnalyze(int id) {
+ SS.GW.GroupSelection();
+#define gs (SS.GW.gs)
+
+ switch(id) {
+ case GraphicsWindow::MNU_STEP_DIM:
+ if(gs.constraints == 1 && gs.n == 0) {
+ Constraint *c = SS.GetConstraint(gs.constraint[0]);
+ if(c->HasLabel() && !c->reference) {
+ SS.TW.shown.dimFinish = c->valA;
+ SS.TW.shown.dimSteps = 10;
+ SS.TW.shown.dimIsDistance =
+ (c->type != Constraint::ANGLE) &&
+ (c->type != Constraint::LENGTH_RATIO);
+ SS.TW.shown.constraint = c->h;
+ SS.TW.shown.screen = TextWindow::SCREEN_STEP_DIMENSION;
+
+ SS.later.showTW = true;
+ SS.GW.ClearSelection();
+ } else {
+ Error("Constraint must have a label, and must not be "
+ "a reference dimension.");
+ }
+ } else {
+ Error("Bad selection for step dimension; select a constraint.");
+ }
+ break;
+
+ case GraphicsWindow::MNU_VOLUME:
+ break;
+
+ case GraphicsWindow::MNU_TRACE_PT:
+ if(gs.points == 1 && gs.n == 1) {
+ SS.traced.point = gs.point[0];
+ SS.GW.ClearSelection();
+ } else {
+ Error("Bad selection for trace; select a single point.");
+ }
+ break;
+
+ case GraphicsWindow::MNU_STOP_TRACING: {
+ char exportFile[MAX_PATH] = "";
+ if(GetSaveFile(exportFile, CSV_EXT, CSV_PATTERN)) {
+ FILE *f = fopen(exportFile, "w");
+ if(f) {
+ int i;
+ SContour *sc = &(SS.traced.path);
+ for(i = 0; i < sc->l.n; i++) {
+ Vector p = sc->l.elem[i].p;
+ double s = SS.exportScale;
+ fprintf(f, "%.10f, %.10f, %.10f\n",
+ p.x/s, p.y/s, p.z/s);
+ }
+ fclose(f);
+ } else {
+ Error("Couldn't write to '%s'", exportFile);
+ }
+ }
+ // Clear the trace, and stop tracing
+ SS.traced.point = Entity::NO_ENTITY;
+ SS.traced.path.l.Clear();
+ InvalidateGraphics();
+ break;
+ }
+
+ default: oops();
+ }
+}
+
diff --git a/solvespace.h b/solvespace.h
index 8e65c9cd..842061c8 100644
--- a/solvespace.h
+++ b/solvespace.h
@@ -62,6 +62,8 @@ int SaveFileYesNoCancel(void);
#define STL_EXT "stl"
#define DXF_PATTERN "DXF File (*.dxf)\0*.dxf\0All Files (*)\0*\0\0"
#define DXF_EXT "dxf"
+#define CSV_PATTERN "CSV File (*.csv)\0*.csv\0All Files (*)\0*\0\0"
+#define CSV_EXT "csv"
BOOL GetSaveFile(char *file, char *defExtension, char *selPattern);
BOOL GetOpenFile(char *file, char *defExtension, char *selPattern);
void GetAbsoluteFilename(char *file);
@@ -370,6 +372,7 @@ public:
Unit viewUnits;
char *MmToString(double v);
double ExprToMm(Expr *e);
+ double StringToMm(char *s);
// The platform-dependent code calls this before entering the msg loop
void Init(char *cmdLine);
@@ -414,6 +417,12 @@ public:
void ExportDxfTo(char *file);
void ExportMeshTo(char *file);
+ static void MenuAnalyze(int id);
+ struct {
+ SContour path;
+ hEntity point;
+ } traced;
+
void MarkGroupDirty(hGroup hg);
void MarkGroupDirtyByEntity(hEntity he);
@@ -437,6 +446,8 @@ public:
void SolveGroup(hGroup hg);
void ForceReferences(void);
+ bool AllGroupsOkay(void);
+
// The system to be solved.
System sys;
diff --git a/textscreens.cpp b/textscreens.cpp
index 788a8905..fc30f12b 100644
--- a/textscreens.cpp
+++ b/textscreens.cpp
@@ -604,6 +604,76 @@ void TextWindow::ShowConfiguration(void) {
&ScreenChangeExportScale, 0);
}
+//-----------------------------------------------------------------------------
+// When we're stepping a dimension. User specifies the finish value, and
+// how many steps to take in between current and finish, re-solving each
+// time.
+//-----------------------------------------------------------------------------
+void TextWindow::ScreenStepDimFinish(int link, DWORD v) {
+ SS.TW.edit.meaning = EDIT_STEP_DIM_FINISH;
+ char s[1024];
+ if(SS.TW.shown.dimIsDistance) {
+ strcpy(s, SS.MmToString(SS.TW.shown.dimFinish));
+ } else {
+ sprintf(s, "%.3f", SS.TW.shown.dimFinish);
+ }
+ ShowTextEditControl(12, 11, s);
+}
+void TextWindow::ScreenStepDimSteps(int link, DWORD v) {
+ char str[1024];
+ sprintf(str, "%d", SS.TW.shown.dimSteps);
+ SS.TW.edit.meaning = EDIT_STEP_DIM_STEPS;
+ ShowTextEditControl(14, 11, str);
+}
+void TextWindow::ScreenStepDimGo(int link, DWORD v) {
+ hConstraint hc = SS.TW.shown.constraint;
+ Constraint *c = SS.constraint.FindByIdNoOops(hc);
+ if(c) {
+ SS.UndoRemember();
+ double start = c->valA, finish = SS.TW.shown.dimFinish;
+ int i, n = SS.TW.shown.dimSteps;
+ for(i = 1; i <= n; i++) {
+ c = SS.GetConstraint(hc);
+ c->valA = start + ((finish - start)*i)/n;
+ SS.MarkGroupDirty(c->group);
+ SS.GenerateAll();
+ if(!SS.AllGroupsOkay()) {
+ // Failed to solve, so quit
+ break;
+ }
+ PaintGraphics();
+ }
+ }
+ InvalidateGraphics();
+ SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS);
+}
+void TextWindow::ShowStepDimension(void) {
+ Constraint *c = SS.constraint.FindByIdNoOops(shown.constraint);
+ if(!c) {
+ shown.screen = SCREEN_LIST_OF_GROUPS;
+ Show();
+ return;
+ }
+
+ Printf(true, "%FtSTEP DIMENSION%E %s", c->DescriptionString());
+
+ if(shown.dimIsDistance) {
+ Printf(true, "%Ba %FtSTART%E %s", SS.MmToString(c->valA));
+ Printf(false, "%Bd %FtFINISH%E %s %Fl%Ll%f[change]%E",
+ SS.MmToString(shown.dimFinish), &ScreenStepDimFinish);
+ } else {
+ Printf(true, "%Ba %FtSTART%E %@", c->valA);
+ Printf(false, "%Bd %FtFINISH%E %@ %Fl%Ll%f[change]%E",
+ shown.dimFinish, &ScreenStepDimFinish);
+ }
+ Printf(false, "%Ba %FtSTEPS%E %d %Fl%Ll%f%D[change]%E",
+ shown.dimSteps, &ScreenStepDimSteps);
+
+ Printf(true, " %Fl%Ll%fstep dimension now%E", &ScreenStepDimGo);
+
+ Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
+}
+
//-----------------------------------------------------------------------------
// The edit control is visible, and the user just pressed enter.
//-----------------------------------------------------------------------------
@@ -752,6 +822,18 @@ void TextWindow::EditControlDone(char *s) {
}
break;
}
+
+ case EDIT_STEP_DIM_FINISH:
+ if(shown.dimIsDistance) {
+ shown.dimFinish = SS.StringToMm(s);
+ } else {
+ shown.dimFinish = atof(s);
+ }
+ break;
+
+ case EDIT_STEP_DIM_STEPS:
+ shown.dimSteps = min(300, max(1, atoi(s)));
+ break;
}
InvalidateGraphics();
SS.later.showTW = true;
diff --git a/textwin.cpp b/textwin.cpp
index e40f2758..6f995b05 100644
--- a/textwin.cpp
+++ b/textwin.cpp
@@ -221,6 +221,7 @@ void TextWindow::Show(void) {
case SCREEN_GROUP_INFO: ShowGroupInfo(); break;
case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;
case SCREEN_CONFIGURATION: ShowConfiguration(); break;
+ case SCREEN_STEP_DIMENSION: ShowStepDimension(); break;
}
}
Printf(false, "");
diff --git a/ui.h b/ui.h
index 2ee3f3cb..2ba0d7a6 100644
--- a/ui.h
+++ b/ui.h
@@ -46,9 +46,16 @@ public:
static const int SCREEN_GROUP_INFO = 1;
static const int SCREEN_GROUP_SOLVE_INFO = 2;
static const int SCREEN_CONFIGURATION = 3;
+ static const int SCREEN_STEP_DIMENSION = 4;
typedef struct {
int screen;
+
hGroup group;
+
+ hConstraint constraint;
+ bool dimIsDistance;
+ double dimFinish;
+ int dimSteps;
} ShownState;
ShownState shown;
@@ -66,6 +73,8 @@ public:
static const int EDIT_HELIX_PITCH = 21;
static const int EDIT_HELIX_DRADIUS = 22;
static const int EDIT_TTF_TEXT = 23;
+ static const int EDIT_STEP_DIM_FINISH = 30;
+ static const int EDIT_STEP_DIM_STEPS = 31;
struct {
int meaning;
int i;
@@ -84,6 +93,7 @@ public:
void ShowGroupInfo(void);
void ShowGroupSolveInfo(void);
void ShowConfiguration(void);
+ void ShowStepDimension(void);
// Special screen, based on selection
void DescribeSelection(void);
@@ -117,6 +127,10 @@ public:
static void ScreenShowConfiguration(int link, DWORD v);
+ static void ScreenStepDimSteps(int link, DWORD v);
+ static void ScreenStepDimFinish(int link, DWORD v);
+ static void ScreenStepDimGo(int link, DWORD v);
+
static void ScreenHome(int link, DWORD v);
// These ones do stuff with the edit control
@@ -201,6 +215,11 @@ public:
MNU_PERPENDICULAR,
MNU_ORIENTED_SAME,
MNU_COMMENT,
+ // Analyze
+ MNU_VOLUME,
+ MNU_TRACE_PT,
+ MNU_STOP_TRACING,
+ MNU_STEP_DIM,
} MenuId;
typedef void MenuHandler(int id);
typedef struct {
diff --git a/wishlist.txt b/wishlist.txt
index 457f1bc0..693eda7e 100644
--- a/wishlist.txt
+++ b/wishlist.txt
@@ -1,6 +1,5 @@
get rid of the oops() calls in the mesh codes
-trace point feature
smooth shading
leak fixing
cylindrical faces?