diff --git a/draw.cpp b/draw.cpp index 484a5bac..f73053d1 100644 --- a/draw.cpp +++ b/draw.cpp @@ -548,6 +548,11 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { ClearSuper(); Constraint *c = SS.GetConstraint(constraintBeingEdited); + if(c->reference) { + // Not meaningful to edit a reference dimension + return; + } + Vector p3 = c->GetLabelPos(); Point2d p2 = ProjectPoint(p3); ShowGraphicsEditControl((int)p2.x, (int)p2.y, c->exprA->Print()); diff --git a/dsc.h b/dsc.h index 50f0254a..92f97f2d 100644 --- a/dsc.h +++ b/dsc.h @@ -106,20 +106,26 @@ public: elem = (T *)MemRealloc(elem, elemsAllocated*sizeof(elem[0])); } - int i = 0; - if(n == 0 || elem[n-1].h.v < t->h.v) { - i = n; - } else { - while(i < n && elem[i].h.v < t->h.v) { - i++; + int first = 0, last = n; + // We know that we must insert within the closed interval [first,last] + while(first != last) { + int mid = (first + last)/2; + H hm = elem[mid].h; + if(hm.v > t->h.v) { + last = mid; + } else if(hm.v < t->h.v) { + first = mid + 1; + } else { + oops(); } } - if(i < n && elem[i].h.v == t->h.v) oops(); + int i = first; + memmove(elem+i+1, elem+i, (n-i)*sizeof(elem[0])); elem[i] = *t; n++; } - + T *FindById(H h) { T *t = FindByIdNoOops(h); if(!t) { diff --git a/entity.cpp b/entity.cpp index 057d27f2..ee6ab84c 100644 --- a/entity.cpp +++ b/entity.cpp @@ -680,6 +680,6 @@ void Entity::CalculateNumerical(void) { Vector n = FaceGetNormalNum(); numNormal = Quaternion::From(0, n.x, n.y, n.z); } - visible = IsVisible(); + actVisible = IsVisible(); } diff --git a/file.cpp b/file.cpp index 7eb1f826..64cc6c17 100644 --- a/file.cpp +++ b/file.cpp @@ -68,6 +68,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'g', "Group.exprA", 'E', &(SS.sv.g.exprA) }, { 'g', "Group.color", 'x', &(SS.sv.g.color) }, { 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) }, + { 'g', "Group.skipFirst", 'b', &(SS.sv.g.skipFirst) }, { 'g', "Group.meshCombine", 'd', &(SS.sv.g.meshCombine) }, { 'g', "Group.predef.q.w", 'f', &(SS.sv.g.predef.q.w) }, { 'g', "Group.predef.q.vx", 'f', &(SS.sv.g.predef.q.vx) }, @@ -94,7 +95,6 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'e', "Entity.h.v", 'x', &(SS.sv.e.h.v) }, { 'e', "Entity.type", 'd', &(SS.sv.e.type) }, - { 'e', "Entity.visible", 'b', &(SS.sv.e.visible) }, { 'e', "Entity.group.v", 'x', &(SS.sv.e.group.v) }, { 'e', "Entity.construction", 'b', &(SS.sv.e.construction) }, { 'e', "Entity.param[0].v", 'x', &(SS.sv.e.param[0].v) }, @@ -127,6 +127,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'e', "Entity.actNormal.vy", 'f', &(SS.sv.e.actNormal.vy) }, { 'e', "Entity.actNormal.vz", 'f', &(SS.sv.e.actNormal.vz) }, { 'e', "Entity.actDistance", 'f', &(SS.sv.e.actDistance) }, + { 'e', "Entity.actVisible", 'b', &(SS.sv.e.actVisible), }, { 'c', "Constraint.h.v", 'x', &(SS.sv.c.h.v) }, diff --git a/group.cpp b/group.cpp index b70fe34e..129bfeb8 100644 --- a/group.cpp +++ b/group.cpp @@ -130,6 +130,8 @@ void Group::MenuGroup(int id) { g.opA = SS.GW.activeGroup; g.exprA = Expr::From(3)->DeepCopyKeep(); g.subtype = ONE_SIDED; + g.predef.entityB = SS.GW.ActiveWorkplane(); + g.activeWorkplane = SS.GW.ActiveWorkplane(); g.name.strcpy("translate"); break; @@ -294,8 +296,12 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(1), gp.y); AddParam(param, h.param(2), gp.z); - int n = (int)(exprA->Eval()); - for(a = 0; a < n; a++) { + int n = (int)(exprA->Eval()), a0 = 0; + if(subtype == ONE_SIDED && skipFirst) { + a0++; n++; + } + + for(a = a0; a < n; a++) { for(i = 0; i < entity->n; i++) { Entity *e = &(entity->elem[i]); if(e->group.v != opA.v) continue; @@ -322,8 +328,12 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(5), gn.y); AddParam(param, h.param(6), gn.z); - int n = (int)(exprA->Eval()); - for(a = 0; a < n; a++) { + int n = (int)(exprA->Eval()), a0 = 0; + if(subtype == ONE_SIDED && skipFirst) { + a0++; n++; + } + + for(a = a0; a < n; a++) { for(i = 0; i < entity->n; i++) { Entity *e = &(entity->elem[i]); if(e->group.v != opA.v) continue; @@ -352,7 +362,6 @@ void Group::Generate(IdList *entity, for(i = 0; i < impEntity.n; i++) { Entity *ie = &(impEntity.elem[i]); - CopyEntity(entity, ie, 0, 0, h.param(0), h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), h.param(6), @@ -408,19 +417,39 @@ void Group::GenerateEquations(IdList *l) { AddEq(l, u.Dot(extruden), 0); AddEq(l, v.Dot(extruden), 1); } + } else if(type == TRANSLATE) { + if(predef.entityB.v != Entity::FREE_IN_3D.v) { + Entity *w = SS.GetEntity(predef.entityB); + ExprVector n = w->Normal()->NormalExprsN(); + ExprVector trans; + trans = ExprVector::From(h.param(0), h.param(1), h.param(2)); + + // The translation vector is parallel to the workplane + AddEq(l, trans.Dot(n), 0); + } } } hEntity Group::Remap(hEntity in, int copyNumber) { - int i; + // A hash table is used to accelerate the search + int hash = ((unsigned)(in.v*61 + copyNumber)) % REMAP_PRIME; + int i = remapCache[hash]; + if(i >= 0 && i < remap.n) { + EntityMap *em = &(remap.elem[i]); + if(em->input.v == in.v && em->copyNumber == copyNumber) { + return h.entity(em->h.v); + } + } + // but if we don't find it in the hash table, then linear search for(i = 0; i < remap.n; i++) { EntityMap *em = &(remap.elem[i]); if(em->input.v == in.v && em->copyNumber == copyNumber) { // We already have a mapping for this entity. + remapCache[hash] = i; return h.entity(em->h.v); } } - // We don't have a mapping yet, so create one. + // And if we still don't find it, then create a new entry. EntityMap em; em.input = in; em.copyNumber = copyNumber; diff --git a/groupmesh.cpp b/groupmesh.cpp index 02653bb4..51280858 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -18,11 +18,17 @@ void Group::GeneratePolygon(void) { } SEdge error; if(edges.AssemblePolygon(&poly, &error)) { - polyError.yes = false; + polyError.how = POLY_GOOD; poly.normal = poly.ComputeNormal(); poly.FixContourDirections(); + + if(!poly.AllPointsInPlane(&(polyError.notCoplanarAt))) { + // The edges aren't all coplanar; so not a good polygon + polyError.how = POLY_NOT_COPLANAR; + poly.Clear(); + } } else { - polyError.yes = true; + polyError.how = POLY_NOT_CLOSED; polyError.notClosedAt = error; poly.Clear(); } @@ -279,8 +285,10 @@ void Group::Draw(void) { if(SS.GW.showMesh) glxDebugMesh(&mesh); + // And finally show the polygons too if(!SS.GW.showShaded) return; - if(polyError.yes) { + if(polyError.how == POLY_NOT_CLOSED) { + glDisable(GL_DEPTH_TEST); glxColor4d(1, 0, 0, 0.2); glLineWidth(10); glBegin(GL_LINES); @@ -294,6 +302,16 @@ void Group::Draw(void) { glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp); glxWriteText("not closed contour!"); glPopMatrix(); + glEnable(GL_DEPTH_TEST); + } else if(polyError.how == POLY_NOT_COPLANAR) { + glDisable(GL_DEPTH_TEST); + glxColor3d(1, 0, 0); + glPushMatrix(); + glxTranslatev(polyError.notCoplanarAt); + glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp); + glxWriteText("points not all coplanar!"); + glPopMatrix(); + glEnable(GL_DEPTH_TEST); } else { glxColor4d(0, 0.1, 0.1, 0.5); glPolygonOffset(-1, -1); diff --git a/polygon.cpp b/polygon.cpp index 5f501a2b..3b61f9a3 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -179,6 +179,18 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { return inside; } +bool SContour::AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt) { + for(int i = 0; i < l.n; i++) { + Vector p = l.elem[i].p; + double dd = n.Dot(p) - d; + if(fabs(dd) > 10*LENGTH_EPS) { + *notCoplanarAt = p; + return false; + } + } + return true; +} + void SContour::Reverse(void) { int i; for(i = 0; i < (l.n / 2); i++) { @@ -263,6 +275,20 @@ void SPolygon::FixContourDirections(void) { } } +bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) { + if(l.n == 0 || l.elem[0].l.n == 0) return true; + + Vector p0 = l.elem[0].l.elem[0].p; + double d = normal.Dot(p0); + + for(int i = 0; i < l.n; i++) { + if(!(l.elem[i]).AllPointsInPlane(normal, d, notCoplanarAt)) { + return false; + } + } + return true; +} + static int TriMode, TriVertexCount; static Vector Tri1, TriNMinus1, TriNMinus2; static Vector TriNormal; diff --git a/polygon.h b/polygon.h index 96a5077a..281bf235 100644 --- a/polygon.h +++ b/polygon.h @@ -84,6 +84,7 @@ public: Vector ComputeNormal(void); bool IsClockwiseProjdToNormal(Vector n); bool ContainsPointProjdToNormal(Vector n, Vector p); + bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); }; class SPolygon { @@ -99,6 +100,7 @@ public: void FixContourDirections(void); void TriangulateInto(SMesh *m); void Clear(void); + bool AllPointsInPlane(Vector *notCoplanarAt); }; typedef struct { diff --git a/sketch.h b/sketch.h index bfff8df9..d238913a 100644 --- a/sketch.h +++ b/sketch.h @@ -108,6 +108,8 @@ public: static const int TWO_SIDED = 7001; int subtype; + bool skipFirst; // for step and repeat ops + struct { Quaternion q; Vector p; @@ -120,10 +122,15 @@ public: } predef; SPolygon poly; + static const int POLY_GOOD = 0; + static const int POLY_NOT_CLOSED = 1; + static const int POLY_NOT_COPLANAR = 2; struct { + int how; SEdge notClosedAt; - bool yes; + Vector notCoplanarAt; } polyError; + SMesh mesh; struct { SMesh interferesAt; @@ -136,6 +143,8 @@ public: int meshCombine; IdList remap; + static const int REMAP_PRIME = 19477; + int remapCache[REMAP_PRIME]; char impFile[MAX_PATH]; SMesh impMesh; @@ -272,8 +281,6 @@ public: double numDistance; // and a bit more state that the faces need Vector numVector; - // and the shown state also gets saved here, for later import - bool visible; // All points/normals/distances have their numerical value; this is // a convenience, to simplify the import/assembly code, so that the @@ -281,6 +288,8 @@ public: Vector actPoint; Quaternion actNormal; double actDistance; + // and the shown state also gets saved here, for later import + bool actVisible; hGroup group; hEntity workplane; // or Entity::FREE_IN_3D diff --git a/textwin.cpp b/textwin.cpp index 68977c0f..55fd1eaf 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -560,6 +560,15 @@ void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) { SS.GenerateAll(); SS.GW.ClearSuper(); } +void TextWindow::ScreenChangeSkipFirst(int link, DWORD v) { + SS.UndoRemember(); + + Group *g = SS.GetGroup(SS.TW.shown->group); + (g->skipFirst) = !(g->skipFirst); + SS.MarkGroupDirty(g->h); + SS.GenerateAll(); + SS.GW.ClearSuper(); +} void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) { SS.UndoRemember(); @@ -581,7 +590,11 @@ void TextWindow::ScreenColor(int link, DWORD v) { } void TextWindow::ScreenChangeExprA(int link, DWORD v) { Group *g = SS.GetGroup(SS.TW.shown->group); - ShowTextEditControl(13, 10, g->exprA->Print()); + + // There's an extra line for the skipFirst parameter in one-sided groups. + int r = (g->subtype == Group::ONE_SIDED) ? 15 : 13; + + ShowTextEditControl(r, 9, g->exprA->Print()); SS.TW.edit.meaning = EDIT_TIMES_REPEATED; SS.TW.edit.group.v = v; } @@ -608,7 +621,7 @@ void TextWindow::ScreenDeleteGroup(int link, DWORD v) { } void TextWindow::ShowGroupInfo(void) { Group *g = SS.group.FindById(shown->group); - char *s, *s2; + char *s, *s2, *s3; if(shown->group.v == Group::HGROUP_REFERENCES.v) { Printf(true, "%FtGROUP %E%s", g->DescriptionString()); @@ -628,10 +641,12 @@ void TextWindow::ShowGroupInfo(void) { s = "EXTRUDE "; } else if(g->type == Group::TRANSLATE) { s = "TRANSLATE"; - s2 ="REPEAT "; + s2 ="REPEAT "; + s3 ="START "; } else if(g->type == Group::ROTATE) { - s = "ROTATE"; - s2 ="REPEAT"; + s = "ROTATE "; + s2 ="REPEAT "; + s3 ="START "; } if(g->type == Group::EXTRUDE || g->type == Group::ROTATE || @@ -649,8 +664,22 @@ void TextWindow::ShowGroupInfo(void) { } if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) { + bool space; + if(g->subtype == Group::ONE_SIDED) { + bool skip = g->skipFirst; + Printf(true, "%Ft%s%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + s3, + &ScreenChangeSkipFirst, + (!skip ? "" : "with original"), (!skip ? "with original" : ""), + &ScreenChangeSkipFirst, + (skip ? "":"with copy #1"), (skip ? "with copy #1":"")); + space = false; + } else { + space = true; + } + int times = (int)(g->exprA->Eval()); - Printf(true, "%Ft%s%E %d time%s %Fl%Ll%D%f[change]%E", + Printf(space, "%Ft%s%E %d time%s %Fl%Ll%D%f[change]%E", s2, times, times == 1 ? "" : "s", g->h.v, &TextWindow::ScreenChangeExprA); } @@ -695,7 +724,14 @@ void TextWindow::ShowGroupInfo(void) { 0x80000000 | SS.modelColor[7], 7, &TextWindow::ScreenColor); } - Printf(true, "%Ftrequests in group"); + // Leave more space if the group has configuration stuff above the req/ + // constraint list (as all but the drawing groups do). + if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { + Printf(true, "%Ftrequests in group"); + } else { + Printf(false, ""); + Printf(false, "%Ftrequests in group"); + } int i, a = 0; for(i = 0; i < SS.request.n; i++) { diff --git a/ui.h b/ui.h index 5e345a03..a8962ea0 100644 --- a/ui.h +++ b/ui.h @@ -100,6 +100,7 @@ public: static void ScreenUnselectAll(int link, DWORD v); static void ScreenChangeOneOrTwoSides(int link, DWORD v); + static void ScreenChangeSkipFirst(int link, DWORD v); static void ScreenChangeMeshCombine(int link, DWORD v); static void ScreenColor(int link, DWORD v); diff --git a/wishlist.txt b/wishlist.txt index 72d48868..d65ffe87 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,15 +1,13 @@ STL check for meshes, and T intersection removal STL export -better triangle combining (Simplify()) for meshes DXF export -compress file format (binary?) partitioned subsystems in the solver TTF font text display with proper formatting/units more measurements some kind of rounding / chamfer -auto-constrain translate in then-active workplane remove back button in browser? - +clean up user-created workplanes +incremental regen of entities?