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?