diff --git a/draw.cpp b/draw.cpp index 54f24380..40bfaf56 100644 --- a/draw.cpp +++ b/draw.cpp @@ -1021,6 +1021,17 @@ void GraphicsWindow::Paint(int w, int h) { } glEnd(); + // And the naked edges, if the user did Analyze -> Show Naked Edges. + glLineWidth(7); + glEnable(GL_LINE_STIPPLE); + glLineStipple(4, 0x5555); + glColor3d(1, 0, 0); + glxDrawEdges(&(SS.nakedEdges)); + glLineStipple(4, 0xaaaa); + glColor3d(0, 0, 0); + glxDrawEdges(&(SS.nakedEdges)); + glDisable(GL_LINE_STIPPLE); + // Then redraw whatever the mouse is hovering over, highlighted. glDisable(GL_DEPTH_TEST); glxLockColorTo(1, 1, 0); diff --git a/export.cpp b/export.cpp index 4b12c1e4..39525697 100644 --- a/export.cpp +++ b/export.cpp @@ -88,7 +88,7 @@ void SolveSpace::ExportSectionTo(char *filename) { SKdNode *root = SKdNode::From(&m); SEdgeList el; ZERO(&el); - root->MakeCertainEdgesInto(&el, false); + root->MakeNakedEdgesInto(&el); // Assemble those edges into a polygon, and clear the edge list el.AssemblePolygon(&sp, NULL); el.Clear(); diff --git a/expr.cpp b/expr.cpp index 52544266..cf750586 100644 --- a/expr.cpp +++ b/expr.cpp @@ -598,7 +598,9 @@ int Expr::Precedence(Expr *e) { if(e->op != BINARY_OP && e->op != UNARY_OP) oops(); switch(e->x.c) { + case 'q': case 's': + case 'c': case 'n': return 30; case '*': @@ -629,7 +631,9 @@ c: break; case 'n': n = PopOperand()->Negate(); break; - case 's': n = PopOperand()->Sqrt(); break; + case 'q': n = PopOperand()->Sqrt(); break; + case 's': n = (PopOperand()->Times(Expr::From(PI/180)))->Sin(); break; + case 'c': n = (PopOperand()->Times(Expr::From(PI/180)))->Cos(); break; default: oops(); } @@ -721,6 +725,12 @@ void Expr::Lex(char *in) { Expr *e = AllocExpr(); if(strcmp(name, "sqrt")==0) { + e->op = UNARY_OP; + e->x.c = 'q'; + } else if(strcmp(name, "cos")==0) { + e->op = UNARY_OP; + e->x.c = 'c'; + } else if(strcmp(name, "sin")==0) { e->op = UNARY_OP; e->x.c = 's'; } else { diff --git a/glhelper.cpp b/glhelper.cpp index aa331fbc..2936ffb7 100644 --- a/glhelper.cpp +++ b/glhelper.cpp @@ -295,14 +295,9 @@ void glxDebugPolygon(SPolygon *p) void glxDrawEdges(SEdgeList *el) { - int i; - glLineWidth(1); - glxDepthRangeOffset(2); - glxColor3d(REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor)); - + SEdge *se; glBegin(GL_LINES); - for(i = 0; i < el->l.n; i++) { - SEdge *se = &(el->l.elem[i]); + for(se = el->l.First(); se; se = el->l.NextAfter(se)) { glxVertex3v(se->a); glxVertex3v(se->b); } diff --git a/graphicswin.cpp b/graphicswin.cpp index f3a1391b..a3f9e473 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -558,6 +558,7 @@ void GraphicsWindow::MenuEdit(int id) { } SS.GW.ClearSuper(); HideTextEditControl(); + SS.nakedEdges.Clear(); break; case MNU_DELETE: { diff --git a/groupmesh.cpp b/groupmesh.cpp index 8d7f442a..472d49aa 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -87,7 +87,6 @@ void Group::GenerateShellForStepAndRepeat(void) { void Group::GenerateShellAndMesh(void) { thisShell.Clear(); - STriMeta meta = { 0, color }; if(type == TRANSLATE || type == ROTATE) { GenerateShellForStepAndRepeat(); @@ -105,7 +104,7 @@ void Group::GenerateShellAndMesh(void) { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); } - thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop); + thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color); } else if(type == LATHE) { Group *src = SS.GetGroup(opA); @@ -223,6 +222,11 @@ void Group::Draw(void) { glxFillMesh(specColor, &runningMesh, mh, ms1, ms2); glDisable(GL_LIGHTING); + glLineWidth(1); + glxDepthRangeOffset(2); + glxColor3d(REDf (SS.edgeColor), + GREENf(SS.edgeColor), + BLUEf (SS.edgeColor)); glxDrawEdges(&emphEdges); } diff --git a/mesh.cpp b/mesh.cpp index e39bb8bc..cea98473 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -476,21 +476,19 @@ void SKdNode::MakeMeshInto(SMesh *m) { } } -void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther, - STriMeta m, int cnt) -{ +void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt) { if(gt && lt) { double ac = a.Element(which), bc = b.Element(which); if(ac < c + KDTREE_EPS || bc < c + KDTREE_EPS) { - lt->FindEdgeOn(a, b, n, nOther, m, cnt); + lt->FindEdgeOn(a, b, n, cnt); } if(ac > c - KDTREE_EPS || bc > c - KDTREE_EPS) { - gt->FindEdgeOn(a, b, n, nOther, m, cnt); + gt->FindEdgeOn(a, b, n, cnt); } } else { STriangleLl *ll; @@ -499,43 +497,20 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther, if(tr->tag == cnt) continue; - if((a.Equals(tr->b, KDTREE_EPS) && b.Equals(tr->a, KDTREE_EPS)) || - (a.Equals(tr->c, KDTREE_EPS) && b.Equals(tr->b, KDTREE_EPS)) || - (a.Equals(tr->a, KDTREE_EPS) && b.Equals(tr->c, KDTREE_EPS))) + if((a.Equals(tr->b) && b.Equals(tr->a)) || + (a.Equals(tr->c) && b.Equals(tr->b)) || + (a.Equals(tr->a) && b.Equals(tr->c))) { (*n)++; - if(tr->meta.face != m.face) { - if(tr->meta.color == m.color && - tr->meta.face != 0 && m.face != 0) - { - hEntity hf0 = { tr->meta.face }, - hf1 = { m.face }; - Entity *f0 = SS.GetEntity(hf0), - *f1 = SS.GetEntity(hf1); - - Vector n0 = f0->FaceGetNormalNum().WithMagnitude(1), - n1 = f1->FaceGetNormalNum().WithMagnitude(1); - - if(n0.Equals(n1) || n0.Equals(n1.ScaledBy(-1))) { - // faces are coincident, skip - // (If the planes are parallel, and the edge - // lies in both planes, then they're also - // coincident.) - } else { - (*nOther)++; - } - } else { - (*nOther)++; - } - } } - + // Ensure that we don't count this triangle twice if it appears + // in two buckets of the kd tree. tr->tag = cnt; } } } -void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, bool emphasized) { +void SKdNode::MakeNakedEdgesInto(SEdgeList *sel) { SMesh m; ZERO(&m); ClearTags(); @@ -551,20 +526,11 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, bool emphasized) { Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a); int n = 0, nOther = 0; - FindEdgeOn(a, b, &n, &nOther, tr->meta, cnt++); + FindEdgeOn(a, b, &n, cnt); if(n != 1) { - if(!emphasized) { - if(n == 0 && (a.Minus(b).Magnitude()) > KDTREE_EPS) { - sel->AddEdge(a, b); - } - } else { -// dbp("hanging: n=%d (%.3f %.3f %.3f) (%.3f %.3f %.3f)", -// n, CO(a), CO(b)); - } - } - if(nOther > 0) { - if(emphasized) sel->AddEdge(a, b); + sel->AddEdge(a, b); } + cnt++; } } diff --git a/polygon.cpp b/polygon.cpp index aefafaa0..f6974bc0 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -68,8 +68,8 @@ void SEdgeList::AddEdge(Vector a, Vector b) { l.Add(&e); } -bool SEdgeList::AssembleContour(Vector first, Vector last, - SContour *dest, SEdge *errorAt) +bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, + SEdge *errorAt, bool keepDir) { int i; @@ -87,7 +87,8 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, se->tag = 1; break; } - if(se->b.Equals(last)) { + // Don't allow backwards edges if keepDir is true. + if(!keepDir && se->b.Equals(last)) { dest->AddPoint(se->a); last = se->a; se->tag = 1; @@ -108,7 +109,7 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, return true; } -bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) { +bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) { dest->Clear(); bool allClosed = true; @@ -130,8 +131,11 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) { // Create a new empty contour in our polygon, and finish assembling // into that contour. dest->AddEmptyContour(); - if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), errorAt)) + if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), + errorAt, keepDir)) + { allClosed = false; + } // But continue assembling, even if some of the contours are open } } diff --git a/polygon.h b/polygon.h index 25e05642..dee5b5cb 100644 --- a/polygon.h +++ b/polygon.h @@ -21,9 +21,9 @@ public: void Clear(void); void AddEdge(Vector a, Vector b); - bool AssemblePolygon(SPolygon *dest, SEdge *errorAt); + bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false); bool AssembleContour(Vector first, Vector last, SContour *dest, - SEdge *errorAt); + SEdge *errorAt, bool keepDir); int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL); }; @@ -218,9 +218,8 @@ public: void MakeMeshInto(SMesh *m); void ClearTags(void); - void FindEdgeOn(Vector a, Vector b, int *n, int *nOther, - STriMeta m, int cnt); - void MakeCertainEdgesInto(SEdgeList *sel, bool emphasized); + void FindEdgeOn(Vector a, Vector b, int *n, int cnt); + void MakeNakedEdgesInto(SEdgeList *sel); }; #endif diff --git a/solvespace.cpp b/solvespace.cpp index 9c3f5c81..803a9f85 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -183,6 +183,8 @@ void SolveSpace::AfterNewFile(void) { // Clear out the traced point, which is no longer valid traced.point = Entity::NO_ENTITY; traced.path.l.Clear(); + // and the naked edges + nakedEdges.Clear(); ReloadAllImported(); GenerateAll(-1, -1); @@ -400,6 +402,20 @@ void SolveSpace::MenuAnalyze(int id) { break; case GraphicsWindow::MNU_NAKED_EDGES: { + SS.nakedEdges.Clear(); + + SMesh *m = &(SS.GetGroup(SS.GW.activeGroup)->runningMesh); + SKdNode *root = SKdNode::From(m); + root->MakeNakedEdgesInto(&(SS.nakedEdges)); + InvalidateGraphics(); + + if(SS.nakedEdges.l.n == 0) { + Error("Zero naked edges; the model is watertight. " + "An exported STL file will be valid."); + } else { + Error("Found %d naked edges, now highlighted.", + SS.nakedEdges.l.n); + } break; } diff --git a/solvespace.h b/solvespace.h index 4ad632e7..9bd141e3 100644 --- a/solvespace.h +++ b/solvespace.h @@ -471,6 +471,7 @@ public: SContour path; hEntity point; } traced; + SEdgeList nakedEdges; void MarkGroupDirty(hGroup hg); void MarkGroupDirtyByEntity(hEntity he); diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index 37a769ff..d134de2a 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -320,13 +320,21 @@ void SCurve::Clear(void) { pts.Clear(); } -STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc) { +STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) { STrimBy stb; ZERO(&stb); stb.curve = hsc; SCurve *sc = shell->curve.FindById(hsc); - stb.start = sc->pts.elem[0]; - stb.finish = sc->pts.elem[sc->pts.n - 1]; + + if(backwards) { + stb.finish = sc->pts.elem[0]; + stb.start = sc->pts.elem[sc->pts.n - 1]; + stb.backwards = true; + } else { + stb.start = sc->pts.elem[0]; + stb.finish = sc->pts.elem[sc->pts.n - 1]; + stb.backwards = false; + } return stb; } @@ -377,6 +385,8 @@ SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, ZERO(&ret); ret.h = a->h; + ret.color = a->color; + ret.face = a->face; ret.degm = a->degm; ret.degn = a->degn; @@ -511,9 +521,20 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) { Vector prev, prevuv, ptuv; bool inCurve = false; - Vector *pt; double u = 0, v = 0; - for(pt = sc->pts.First(); pt; pt = sc->pts.NextAfter(pt)) { + + int i, first, last, increment; + if(stb->backwards) { + first = sc->pts.n - 1; + last = 0; + increment = -1; + } else { + first = 0; + last = sc->pts.n - 1; + increment = 1; + } + for(i = first; i != (last + increment); i += increment) { + Vector *pt = &(sc->pts.elem[i]); if(asUv) { ClosestPointTo(*pt, &u, &v); ptuv = Vector::From(u, v, 0); @@ -542,14 +563,14 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { SPolygon poly; ZERO(&poly); - if(!el.AssemblePolygon(&poly, NULL)) { + if(!el.AssemblePolygon(&poly, NULL, true)) { dbp("failed to assemble polygon to trim nurbs surface in uv space"); } int i, start = sm->l.n; poly.UvTriangulateInto(sm); - STriMeta meta = { 0, 0x888888 }; + STriMeta meta = { face, color }; for(i = start; i < sm->l.n; i++) { STriangle *st = &(sm->l.elem[i]); st->meta = meta; @@ -559,10 +580,9 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { st->a = PointAt(st->a.x, st->a.y); st->b = PointAt(st->b.x, st->b.y); st->c = PointAt(st->c.x, st->c.y); - if((st->Normal()).Dot(st->an) < 0) { - // Have to get the vertices in the right order - st->FlipNormal(); - } + // Works out that my chosen contour direction is inconsistent with + // the triangle direction, sigh. + st->FlipNormal(); } el.Clear(); @@ -573,7 +593,9 @@ void SSurface::Clear(void) { trim.Clear(); } -void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { +void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, + int color) +{ ZERO(this); // Make the extrusion direction consistent with respect to the normal @@ -586,7 +608,9 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { // planes. SSurface s0, s1; s0 = SSurface::FromPlane(sbls->point.Plus(t0), sbls->normal.ScaledBy(-1)); + s0.color = color; s1 = SSurface::FromPlane(sbls->point.Plus(t1), sbls->normal.ScaledBy( 1)); + s1.color = color; hSSurface hs0 = surface.AddAndAssignId(&s0), hs1 = surface.AddAndAssignId(&s1); @@ -598,7 +622,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { SBezier *sb; typedef struct { - STrimBy trim; + hSCurve hc; hSSurface hs; } TrimLine; List trimLines; @@ -608,6 +632,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { // Generate the surface of extrusion of this curve, and add // it to the list SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1); + ss.color = color; hSSurface hsext = surface.AddAndAssignId(&ss); // Translate the curve by t0 and t1 to produce two trim curves @@ -615,19 +640,21 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { ZERO(&sc); sb->MakePwlInto(&(sc.pts), t0); hSCurve hc0 = curve.AddAndAssignId(&sc); - STrimBy stb0 = STrimBy::EntireCurve(this, hc0); + STrimBy stb0 = STrimBy::EntireCurve(this, hc0, false); ZERO(&sc); sb->MakePwlInto(&(sc.pts), t1); hSCurve hc1 = curve.AddAndAssignId(&sc); - STrimBy stb1 = STrimBy::EntireCurve(this, hc1); + STrimBy stb1 = STrimBy::EntireCurve(this, hc1, true); // The translated curves trim the flat top and bottom surfaces. (surface.FindById(hs0))->trim.Add(&stb0); (surface.FindById(hs1))->trim.Add(&stb1); // The translated curves also trim the surface of extrusion. + stb0 = STrimBy::EntireCurve(this, hc0, true); (surface.FindById(hsext))->trim.Add(&stb0); + stb1 = STrimBy::EntireCurve(this, hc1, false); (surface.FindById(hsext))->trim.Add(&stb1); // And form the trim line @@ -639,7 +666,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { hSCurve hl = curve.AddAndAssignId(&sc); // save this for later TrimLine tl; - tl.trim = STrimBy::EntireCurve(this, hl); + tl.hc = hl; tl.hs = hsext; trimLines.Add(&tl); } @@ -651,8 +678,11 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); - ss->trim.Add(&(tl->trim)); - ss->trim.Add(&(tlp->trim)); + STrimBy stb; + stb = STrimBy::EntireCurve(this, tl->hc, true); + ss->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, tlp->hc, false); + ss->trim.Add(&stb); } trimLines.Clear(); } diff --git a/srf/surface.h b/srf/surface.h index 7fd4f8b5..c67a591f 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -97,11 +97,14 @@ public: class STrimBy { public: hSCurve curve; + bool backwards; + // If a trim runs backwards, then start and finish still correspond to + // the actual start and finish, but they appear in reverse order in + // the referenced curve. Vector start; Vector finish; - Vector out; - static STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc); + static STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool bkwds); }; // A rational polynomial surface in Bezier form. @@ -109,6 +112,9 @@ class SSurface { public: hSSurface h; + int color; + DWORD face; + int degm, degn; Vector ctrl[4][4]; double weight[4][4]; @@ -136,7 +142,8 @@ public: IdList curve; IdList surface; - void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1); + void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, + int color); void MakeFromUnionOf(SShell *a, SShell *b); void MakeFromDifferenceOf(SShell *a, SShell *b); void MakeFromCopyOf(SShell *a); diff --git a/srf/triangulate.cpp b/srf/triangulate.cpp index 75e586a2..6152d360 100644 --- a/srf/triangulate.cpp +++ b/srf/triangulate.cpp @@ -51,7 +51,7 @@ void SPolygon::UvTriangulateInto(SMesh *m) { } } - dbp("finished finding holes: %d ms", GetMilliseconds() - in); +// dbp("finished finding holes: %d ms", GetMilliseconds() - in); for(;;) { double xmin = 1e10; SContour *scmin = NULL; @@ -70,13 +70,13 @@ void SPolygon::UvTriangulateInto(SMesh *m) { dbp("couldn't merge our hole"); return; } - dbp(" bridged to contour: %d ms", GetMilliseconds() - in); +// dbp(" bridged to contour: %d ms", GetMilliseconds() - in); scmin->tag = 3; } - dbp("finished merging holes: %d ms", GetMilliseconds() - in); +// dbp("finished merging holes: %d ms", GetMilliseconds() - in); merged.UvTriangulateInto(m); - dbp("finished ear clippping: %d ms", GetMilliseconds() - in); +// dbp("finished ear clippping: %d ms", GetMilliseconds() - in); merged.l.Clear(); el.Clear(); vl.Clear();