diff --git a/draw.cpp b/draw.cpp index e8b2bbe..9dc8840 100644 --- a/draw.cpp +++ b/draw.cpp @@ -363,7 +363,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { case MNU_RECTANGLE: { if(!SS.GW.LockedInWorkplane()) { Error("Can't draw rectangle in 3d; select a workplane first."); - break; + ClearSuper(); + break; } hRequest lns[4]; int i; diff --git a/file.cpp b/file.cpp index 96bcc58..d35248a 100644 --- a/file.cpp +++ b/file.cpp @@ -67,6 +67,8 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) }, { 'g', "Group.opB.v", 'x', &(SS.sv.g.opB.v) }, { 'g', "Group.valA", 'f', &(SS.sv.g.valA) }, + { 'g', "Group.valB", 'f', &(SS.sv.g.valB) }, + { 'g', "Group.valC", 'f', &(SS.sv.g.valB) }, { 'g', "Group.color", 'x', &(SS.sv.g.color) }, { 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) }, { 'g', "Group.skipFirst", 'b', &(SS.sv.g.skipFirst) }, diff --git a/graphicswin.cpp b/graphicswin.cpp index 55152fe..e84881d 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -48,6 +48,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "E&xtrude\tShift+Ctrl+X", MNU_GROUP_EXTRUDE, 'X'|S|C,mGrp }, { 1, "&Lathe\tShift+Ctrl+L", MNU_GROUP_LATHE, 'L'|S|C,mGrp }, { 1, "&Sweep\tShift+Ctrl+S", MNU_GROUP_SWEEP, 'S'|S|C,mGrp }, +{ 1, "&Helical Sweep\tShift+Ctrl+H", MNU_GROUP_HELICAL, 'H'|S|C,mGrp }, { 1, NULL, 0, 0, NULL }, { 1, "Import / Assemble...\tShift+Ctrl+I", MNU_GROUP_IMPORT, 'I'|S|C,mGrp }, {11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp }, diff --git a/group.cpp b/group.cpp index 11a8369..02f0ce5 100644 --- a/group.cpp +++ b/group.cpp @@ -20,6 +20,7 @@ void Group::MenuGroup(int id) { Group g; ZERO(&g); g.visible = true; + g.color = RGB(100, 100, 100); if(id >= RECENT_IMPORT && id < (RECENT_IMPORT + MAX_RECENT)) { strcpy(g.impFile, RecentFile[id-RECENT_IMPORT]); @@ -75,7 +76,6 @@ void Group::MenuGroup(int id) { case GraphicsWindow::MNU_GROUP_EXTRUDE: g.type = EXTRUDE; g.opA = SS.GW.activeGroup; - g.color = RGB(100, 100, 100); g.predef.entityB = SS.GW.ActiveWorkplane(); g.subtype = ONE_SIDED; g.name.strcpy("extrude"); @@ -95,7 +95,6 @@ void Group::MenuGroup(int id) { } g.type = LATHE; g.opA = SS.GW.activeGroup; - g.color = RGB(100, 100, 100); g.name.strcpy("lathe"); SS.GW.ClearSelection(); break; @@ -119,11 +118,38 @@ void Group::MenuGroup(int id) { } // The active group is our section g.opB = SS.GW.activeGroup; - g.color = RGB(100, 100, 100); g.name.strcpy("sweep"); break; } + case GraphicsWindow::MNU_GROUP_HELICAL: { + if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { + Vector pt = SS.GetEntity(gs.point[0])->PointGetNum(); + Entity *ln = SS.GetEntity(gs.entity[0]); + Vector lpa = SS.GetEntity(ln->point[0])->PointGetNum(); + Vector lpb = SS.GetEntity(ln->point[1])->PointGetNum(); + double d = pt.DistanceToLine(lpa, lpb.Minus(lpa)); + if(d < LENGTH_EPS) { + Error("Point on helix can't lie on helix's axis!"); + return; + } + g.predef.origin = gs.point[0]; + g.predef.entityB = gs.entity[0]; + } else { + Error("Bad selection for helical sweep."); + return; + } + g.type = HELICAL_SWEEP; + g.subtype = RIGHT_HANDED; + g.valA = 3; // turns; + g.valB = 300/SS.GW.scale; // pitch along axis + g.valC = 0; // pitch in radius + g.opA = SS.GW.activeGroup; + g.name.strcpy("helical-sweep"); + SS.GW.ClearSelection(); + break; + } + case GraphicsWindow::MNU_GROUP_ROT: { if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) { g.predef.origin = gs.point[0]; @@ -315,6 +341,10 @@ void Group::Generate(IdList *entity, break; } + case HELICAL_SWEEP: { + break; + } + case TRANSLATE: { // The translation vector AddParam(param, h.param(0), gp.x); diff --git a/groupmesh.cpp b/groupmesh.cpp index 85eaf9b..88f0558 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -38,9 +38,10 @@ void Group::GeneratePolygon(void) { void Group::GetTrajectory(hGroup hg, SContour *traj, SPolygon *section) { if(section->IsEmpty()) return; - + SEdgeList edges; ZERO(&edges); int i, j; + for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); if(e->group.v != hg.v) continue; @@ -100,7 +101,6 @@ void Group::GetTrajectory(hGroup hg, SContour *traj, SPolygon *section) { // The starting point is the endpoint that's closer to the plane traj->Reverse(); } - cleanup: edges.Clear(); } @@ -197,20 +197,41 @@ void Group::GenerateMeshForStepAndRepeat(void) { thisMesh.Clear(); } -void Group::GenerateMeshForSweep(void) { +void Group::GenerateMeshForSweep(bool helical, + Vector axisp, Vector axis, Vector onHelix) +{ STriMeta meta = { 0, color }; - SEdgeList edges; - ZERO(&edges); int a, i; // The closed section that will be swept along the curve - Group *section = SS.GetGroup(opB); + Group *section = SS.GetGroup(helical ? opA : opB); + SEdgeList edges; + ZERO(&edges); (section->poly).MakeEdgesInto(&edges); // The trajectory along which the section will be swept SContour traj; ZERO(&traj); - GetTrajectory(opA, &traj, &(section->poly)); + if(helical) { + double r0 = onHelix.DistanceToLine(axisp, axis); + int n = (int)(SS.CircleSides(r0)*valA) + 4; + Vector origin = onHelix.ClosestPointOnLine(axisp, axis); + Vector u = (onHelix.Minus(origin)).WithMagnitude(1); + Vector v = (axis.Cross(u)).WithMagnitude(1); + for(i = 0; i <= n; i++) { + double turns = (i*valA)/n; + double theta = turns*2*PI; + double r = r0 + turns*valC; + if(subtype == LEFT_HANDED) theta = -theta; + Vector p = origin.Plus( + u.ScaledBy(r*cos(theta)).Plus( + v.ScaledBy(r*sin(theta)).Plus( + axis.WithMagnitude(turns*valB)))); + traj.AddPoint(p); + } + } else { + GetTrajectory(opA, &traj, &(section->poly)); + } if(traj.l.n <= 0) { edges.Clear(); @@ -222,7 +243,17 @@ void Group::GenerateMeshForSweep(void) { Vector origNormal = (traj.l.elem[1].p).Minus(origRef); origNormal = origNormal.WithMagnitude(1); Vector oldRef = origRef, oldNormal = origNormal; - Vector oldU = oldNormal.Normal(0), oldV = oldNormal.Normal(1); + + Vector oldU, oldV; + if(helical) { + oldU = axis.WithMagnitude(1); + oldV = (oldNormal.Cross(oldU)).WithMagnitude(1); + // numerical fixup, since pwl segment isn't exactly tangent... + oldU = (oldV.Cross(oldNormal)).WithMagnitude(1); + } else { + oldU = oldNormal.Normal(0); + oldV = oldNormal.Normal(1); + } // The endcap at the start of the curve SPolygon cap; @@ -257,11 +288,18 @@ void Group::GenerateMeshForSweep(void) { useNormal = (thisNormal.Plus(oldNormal)).ScaledBy(0.5); } - // Choose a new coordinate system, normal to the trajectory and - // with the minimum possible twist about the normal. + Vector useV, useU; useNormal = useNormal.WithMagnitude(1); - Vector useV = (useNormal.Cross(oldU)).WithMagnitude(1); - Vector useU = (useV.Cross(useNormal)).WithMagnitude(1); + if(helical) { + // The axis of rotation is always a basis vector + useU = axis.WithMagnitude(1); + useV = (useNormal.Cross(useU)).WithMagnitude(1); + } else { + // Choose a new coordinate system, normal to the trajectory and + // with the minimum possible twist about the normal. + useV = (useNormal.Cross(oldU)).WithMagnitude(1); + useU = (useV.Cross(useNormal)).WithMagnitude(1); + } Quaternion qi = Quaternion::From(oldU, oldV); Quaternion qf = Quaternion::From(useU, useV); @@ -432,7 +470,14 @@ void Group::GenerateMesh(void) { } } } else if(type == SWEEP) { - GenerateMeshForSweep(); + Vector zp = Vector::From(0, 0, 0); + GenerateMeshForSweep(false, zp, zp, zp); + } else if(type == HELICAL_SWEEP) { + Entity *ln = SS.GetEntity(predef.entityB); + Vector lna = SS.GetEntity(ln->point[0])->PointGetNum(), + lnb = SS.GetEntity(ln->point[1])->PointGetNum(); + Vector onh = SS.GetEntity(predef.origin)->PointGetNum(); + GenerateMeshForSweep(true, lna, lnb.Minus(lna), onh); } else if(type == IMPORTED) { // Triangles are just copied over, with the appropriate transformation // applied. diff --git a/sketch.h b/sketch.h index 51653f4..c480365 100644 --- a/sketch.h +++ b/sketch.h @@ -83,6 +83,7 @@ public: static const int EXTRUDE = 5100; static const int LATHE = 5101; static const int SWEEP = 5102; + static const int HELICAL_SWEEP = 5103; static const int ROTATE = 5200; static const int TRANSLATE = 5201; static const int IMPORTED = 5300; @@ -94,6 +95,8 @@ public: bool clean; hEntity activeWorkplane; double valA; + double valB; + double valC; DWORD color; static const int SOLVED_OKAY = 0; @@ -104,10 +107,15 @@ public: SList remove; } solved; + // For drawings in 2d static const int WORKPLANE_BY_POINT_ORTHO = 6000; static const int WORKPLANE_BY_LINE_SEGMENTS = 6001; + // For extrudes, translates, and rotates static const int ONE_SIDED = 7000; static const int TWO_SIDED = 7001; + // For helical sweeps + static const int RIGHT_HANDED = 8000; + static const int LEFT_HANDED = 8001; int subtype; bool skipFirst; // for step and repeat ops @@ -158,8 +166,8 @@ public: void Activate(void); char *DescriptionString(void); - static void AddParam(IdList *param, hParam hp, double v); - void Generate(IdList *entity, IdList *param); + static void AddParam(ParamList *param, hParam hp, double v); + void Generate(EntityList *entity, ParamList *param); // When a request generates entities from entities, and the source // entities may have come from multiple requests, it's necessary to // remap the entity ID so that it's still unique. We do this with a @@ -170,10 +178,10 @@ public: static const int REMAP_PT_TO_LINE = 1003; static const int REMAP_LINE_TO_FACE = 1004; hEntity Remap(hEntity in, int copyNumber); - void MakeExtrusionLines(IdList *el, hEntity in); - void MakeExtrusionTopBottomFaces(IdList *el, hEntity pt); + void MakeExtrusionLines(EntityList *el, hEntity in); + void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); void TagEdgesFromLineSegments(SEdgeList *sle); - void CopyEntity(IdList *el, + void CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, hParam dx, hParam dy, hParam dz, hParam qw, hParam qvx, hParam qvy, hParam qvz, @@ -190,7 +198,8 @@ public: void AddQuadWithNormal(STriMeta meta, Vector out, Vector a, Vector b, Vector c, Vector d); void GenerateMeshForStepAndRepeat(void); - void GenerateMeshForSweep(void); + void GenerateMeshForSweep(bool helical, + Vector axisp, Vector axis, Vector onHelix); void GenerateMesh(void); void Draw(void); @@ -226,8 +235,8 @@ public: bool construction; - static hParam AddParam(IdList *param, hParam hp); - void Generate(IdList *entity, IdList *param); + static hParam AddParam(ParamList *param, hParam hp); + void Generate(EntityList *entity, ParamList *param); char *DescriptionString(void); }; diff --git a/solvespace.h b/solvespace.h index da593a0..fc54385 100644 --- a/solvespace.h +++ b/solvespace.h @@ -101,7 +101,10 @@ void vl(void); // debug function to validate class Entity; class hEntity; +class Param; +class hParam; typedef IdList EntityList; +typedef IdList ParamList; #include "sketch.h" #include "ui.h" @@ -144,8 +147,8 @@ class System { public: #define MAX_UNKNOWNS 200 - IdList entity; - IdList param; + EntityList entity; + ParamList param; IdList eq; // In general, the tag indicates the subsys that a variable/equation diff --git a/textwin.cpp b/textwin.cpp index f7b807c..3fbcb52 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -626,6 +626,39 @@ void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) { SS.GenerateAll(); SS.GW.ClearSuper(); } +void TextWindow::ScreenChangeRightLeftHanded(int link, DWORD v) { + SS.UndoRemember(); + + Group *g = SS.GetGroup(SS.TW.shown->group); + if(g->subtype == Group::RIGHT_HANDED) { + g->subtype = Group::LEFT_HANDED; + } else { + g->subtype = Group::RIGHT_HANDED; + } + SS.MarkGroupDirty(g->h); + SS.GenerateAll(); + SS.GW.ClearSuper(); +} +void TextWindow::ScreenChangeHelixParameter(int link, DWORD v) { + Group *g = SS.GetGroup(SS.TW.shown->group); + char str[1024]; + int r; + if(link == 't') { + sprintf(str, "%.3f", g->valA); + SS.TW.edit.meaning = EDIT_HELIX_TURNS; + r = 12; + } else if(link == 'p') { + strcpy(str, SS.MmToString(g->valB)); + SS.TW.edit.meaning = EDIT_HELIX_PITCH; + r = 14; + } else if(link == 'r') { + strcpy(str, SS.MmToString(g->valC)); + SS.TW.edit.meaning = EDIT_HELIX_DRADIUS; + r = 16; + } else oops(); + SS.TW.edit.group.v = v; + ShowTextEditControl(r, 9, str); +} void TextWindow::ScreenColor(int link, DWORD v) { SS.UndoRemember(); @@ -683,10 +716,6 @@ void TextWindow::ShowGroupInfo(void) { g->h.v, &TextWindow::ScreenDeleteGroup); } - if(g->type == Group::IMPORTED) { - Printf(true, "%FtIMPORT%E '%s'", g->impFile); - } - if(g->type == Group::EXTRUDE) { s = "EXTRUDE "; } else if(g->type == Group::TRANSLATE) { @@ -708,11 +737,32 @@ void TextWindow::ShowGroupInfo(void) { (one ? "" : "one side"), (one ? "one side" : ""), &TextWindow::ScreenChangeOneOrTwoSides, (!one ? "" : "two sides"), (!one ? "two sides" : "")); - } + } + if(g->type == Group::LATHE) { Printf(true, "%FtLATHE"); } - + + if(g->type == Group::SWEEP) { + Printf(true, "%FtSWEEP"); + } + + if(g->type == Group::HELICAL_SWEEP) { + bool rh = (g->subtype == Group::RIGHT_HANDED); + Printf(true, + "%FtHELICAL%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &ScreenChangeRightLeftHanded, + (rh ? "" : "right-hand"), (rh ? "right-hand" : ""), + &ScreenChangeRightLeftHanded, + (!rh ? "" : "left-hand"), (!rh ? "left-hand" : "")); + Printf(false, "%FtTHROUGH%E %@ turns %Fl%Lt%D%f[change]%E", + g->valA, g->h.v, &ScreenChangeHelixParameter); + Printf(false, "%FtPITCH%E %s per turn %Fl%Lp%D%f[change]%E", + SS.MmToString(g->valB), g->h.v, &ScreenChangeHelixParameter); + Printf(false, "%FtdRADIUS%E %s per turn %Fl%Lr%D%f[change]%E", + SS.MmToString(g->valC), g->h.v, &ScreenChangeHelixParameter); + } + if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) { bool space; if(g->subtype == Group::ONE_SIDED) { @@ -733,10 +783,15 @@ void TextWindow::ShowGroupInfo(void) { s2, times, times == 1 ? "" : "s", g->h.v, &TextWindow::ScreenChangeExprA); } + + if(g->type == Group::IMPORTED) { + Printf(true, "%FtIMPORT%E '%s'", g->impFile); + } if(g->type == Group::EXTRUDE || g->type == Group::LATHE || g->type == Group::SWEEP || + g->type == Group::HELICAL_SWEEP || g->type == Group::IMPORTED) { bool un = (g->meshCombine == Group::COMBINE_AS_UNION); @@ -744,7 +799,7 @@ void TextWindow::ShowGroupInfo(void) { bool asy = (g->meshCombine == Group::COMBINE_AS_ASSEMBLE); bool asa = (g->type == Group::IMPORTED); - Printf(false, + Printf((g->type == Group::HELICAL_SWEEP), "%FtMERGE AS%E %Fh%f%D%Ll%s%E%Fs%s%E / %Fh%f%D%Ll%s%E%Fs%s%E %s " "%Fh%f%D%Ll%s%E%Fs%s%E", &TextWindow::ScreenChangeMeshCombine, @@ -764,7 +819,8 @@ void TextWindow::ShowGroupInfo(void) { if(g->type == Group::EXTRUDE || g->type == Group::LATHE || - g->type == Group::SWEEP) + g->type == Group::SWEEP || + g->type == Group::HELICAL_SWEEP) { #define TWOX(v) v v Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))), @@ -997,6 +1053,27 @@ void TextWindow::EditControlDone(char *s) { InvalidateGraphics(); break; } + case EDIT_HELIX_TURNS: + case EDIT_HELIX_PITCH: + case EDIT_HELIX_DRADIUS: { + SS.UndoRemember(); + Group *g = SS.GetGroup(edit.group); + Expr *e = Expr::From(s); + if(!e) { + Error("Not a valid number or expression: '%s'", s); + break; + } + if(edit.meaning == EDIT_HELIX_TURNS) { + g->valA = min(30, fabs(e->Eval())); + } else if(edit.meaning == EDIT_HELIX_PITCH) { + g->valB = SS.ExprToMm(e); + } else { + g->valC = SS.ExprToMm(e); + } + SS.MarkGroupDirty(g->h); + SS.later.generateAll = true; + break; + } } SS.later.showTW = true; HideTextEditControl(); diff --git a/ui.h b/ui.h index 76590f3..d1317bd 100644 --- a/ui.h +++ b/ui.h @@ -64,6 +64,9 @@ public: static const int EDIT_COLOR = 5; static const int EDIT_MESH_TOLERANCE = 6; static const int EDIT_CAMERA_TANGENT = 7; + static const int EDIT_HELIX_TURNS = 8; + static const int EDIT_HELIX_PITCH = 9; + static const int EDIT_HELIX_DRADIUS = 10; struct { int meaning; int i; @@ -103,6 +106,8 @@ public: static void ScreenChangeOneOrTwoSides(int link, DWORD v); static void ScreenChangeSkipFirst(int link, DWORD v); static void ScreenChangeMeshCombine(int link, DWORD v); + static void ScreenChangeRightLeftHanded(int link, DWORD v); + static void ScreenChangeHelixParameter(int link, DWORD v); static void ScreenColor(int link, DWORD v); static void ScreenShowConfiguration(int link, DWORD v); @@ -164,6 +169,7 @@ public: MNU_GROUP_EXTRUDE, MNU_GROUP_LATHE, MNU_GROUP_SWEEP, + MNU_GROUP_HELICAL, MNU_GROUP_ROT, MNU_GROUP_TRANS, MNU_GROUP_IMPORT,