From 603f47692e96ed60a33acefa0dab3193410061a6 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Mon, 8 Jun 2009 08:21:33 -0800 Subject: [PATCH] When exporting STEP, identify the outer contours, and group them and their holes into their own advanced faces. So a single surface with multiple outer contours generates multiple advanced faces. Also turn the default chord tol down to 1.5 pixels, seems more likely to make the exact surface Booleans work. [git-p4: depot-paths = "//depot/solvespace/": change = 1975] --- exportstep.cpp | 220 +++++++++++++++++++++++++++++++------------- polygon.cpp | 5 + polygon.h | 1 + solvespace.cpp | 2 +- solvespace.h | 4 +- srf/surface.h | 1 + srf/triangulate.cpp | 9 +- 7 files changed, 171 insertions(+), 71 deletions(-) diff --git a/exportstep.cpp b/exportstep.cpp index 8cd4f994..bb4b668b 100644 --- a/exportstep.cpp +++ b/exportstep.cpp @@ -83,7 +83,50 @@ int StepFileWriter::ExportCurve(SBezier *sb) { return ret; } -int StepFileWriter::ExportSurface(SSurface *ss) { +int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { + List listOfTrims; + ZERO(&listOfTrims); + + SBezier *sb; + for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { + int curveId = ExportCurve(sb); + + fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", + id, CO(sb->Start())); + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); + fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", + id+2, CO(sb->Finish())); + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+3, id+2); + fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", + id+4, id+1, id+3, curveId, ".T."); + fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", + id+5, id+4); + + int oe = id+5; + listOfTrims.Add(&oe); + + id += 6; + } + + fprintf(f, "#%d=EDGE_LOOP('',(", id); + int *oe; + for(oe = listOfTrims.First(); oe; oe = listOfTrims.NextAfter(oe)) { + fprintf(f, "#%d", *oe); + if(listOfTrims.NextAfter(oe) != NULL) fprintf(f, ","); + } + fprintf(f, "));\n"); + + int fb = id + 1; + fprintf(f, "#%d=%s('',#%d,.T.);\n", + fb, inner ? "FACE_BOUND" : "FACE_OUTER_BOUND", id); + + id += 2; + listOfTrims.Clear(); + + return fb; +} + +void StepFileWriter::ExportSurface(SSurface *ss) { int i, j, srfid = id; fprintf(f, "#%d=B_SPLINE_SURFACE_WITH_KNOTS('',%d,%d,(", @@ -113,6 +156,9 @@ int StepFileWriter::ExportSurface(SSurface *ss) { id = srfid + 1 + (ss->degm + 1)*(ss->degn + 1); + // Get all of the loops of Beziers that trim our surface (with each + // Bezier split so that we use the section as t goes from 0 to 1), and + // the piecewise linearization of those loops in xyz space. SBezierList sbl; SPolygon sp; ZERO(&sbl); @@ -122,66 +168,110 @@ int StepFileWriter::ExportSurface(SSurface *ss) { ss->MakeSectionEdgesInto(shell, NULL, &sbl); SBezierLoopSet sbls = SBezierLoopSet::From(&sbl, &sp, &allClosed, &errorAt); - List listOfLoops; - ZERO(&listOfLoops); - - SBezierLoop *loop; - for(loop = sbls.l.First(); loop; loop = sbls.l.NextAfter(loop)) { - List listOfTrims; - ZERO(&listOfTrims); - - SBezier *sb; - for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { - int curveId = ExportCurve(sb); - - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - id, CO(sb->Start())); - fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - id+2, CO(sb->Finish())); - fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+3, id+2); - fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", - id+4, id+1, id+3, curveId, ".T."); - fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", - id+5, id+4); - - i = id+5; - listOfTrims.Add(&i); - - id += 6; + // Convert the xyz piecewise linear to uv piecewise linear. + SContour *contour; + for(contour = sp.l.First(); contour; contour = sp.l.NextAfter(contour)) { + SPoint *pt; + for(pt = contour->l.First(); pt; pt = contour->l.NextAfter(pt)) { + double u, v; + ss->ClosestPointTo(pt->p, &u, &v); + pt->p = Vector::From(u, v, 0); } + } + sp.normal = Vector::From(0, 0, 1); - fprintf(f, "#%d=EDGE_LOOP('',(", id); - int *ei; - for(ei = listOfTrims.First(); ei; ei = listOfTrims.NextAfter(ei)) { - fprintf(f, "#%d", *ei); - if(listOfTrims.NextAfter(ei) != NULL) fprintf(f, ","); + static const int OUTER_LOOP = 10; + static const int INNER_LOOP = 20; + static const int USED_LOOP = 30; + // Fix the contour directions; SBezierLoopSet::From() works only for + // planes, since it uses the polygon xyz space. + sp.FixContourDirections(); + for(i = 0; i < sp.l.n; i++) { + SContour *contour = &(sp.l.elem[i]); + SBezierLoop *bl = &(sbls.l.elem[i]); + if(contour->tag) { + // This contour got reversed in the polygon to make the directions + // consistent, so the same must be necessary for the Bezier loop. + bl->Reverse(); + } + if(contour->IsClockwiseProjdToNormal(sp.normal)) { + bl->tag = INNER_LOOP; + } else { + bl->tag = OUTER_LOOP; } - fprintf(f, "));\n"); - - int fb = id + 1; - fprintf(f, "#%d=FACE_OUTER_BOUND('',#%d,.T.);\n", fb, id); - listOfLoops.Add(&fb); - - id += 2; - listOfTrims.Clear(); } - int advFaceId = id; - fprintf(f, "#%d=ADVANCED_FACE('',(", advFaceId); - int *fb; - for(fb = listOfLoops.First(); fb; fb = listOfLoops.NextAfter(fb)) { - fprintf(f, "#%d", *fb); - if(listOfLoops.NextAfter(fb) != NULL) fprintf(f, ","); + + bool loopsRemaining = true; + while(loopsRemaining) { + loopsRemaining = false; + for(i = 0; i < sbls.l.n; i++) { + SBezierLoop *loop = &(sbls.l.elem[i]); + if(loop->tag != OUTER_LOOP) continue; + + // Check if this contour contains any outer loops; if it does, then + // we should do those "inner outer loops" first; otherwise we + // will steal their holes, since their holes also lie inside this + // contour. + for(j = 0; j < sbls.l.n; j++) { + SBezierLoop *outer = &(sbls.l.elem[j]); + if(i == j) continue; + if(outer->tag != OUTER_LOOP) continue; + + Vector p = sp.l.elem[j].AnyEdgeMidpoint(); + if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) { + break; + } + } + if(j < sbls.l.n) { + // It does, can't do this one yet. + continue; + } + + loopsRemaining = true; + loop->tag = USED_LOOP; + + List listOfLoops; + ZERO(&listOfLoops); + + // Create the face outer boundary from the outer loop. + int fob = ExportCurveLoop(loop, false); + listOfLoops.Add(&fob); + + // And create the face inner boundaries from any inner loops that + // lie within this contour. + for(j = 0; j < sbls.l.n; j++) { + SBezierLoop *inner = &(sbls.l.elem[j]); + if(inner->tag != INNER_LOOP) continue; + + Vector p = sp.l.elem[j].AnyEdgeMidpoint(); + if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) { + int fib = ExportCurveLoop(inner, true); + listOfLoops.Add(&fib); + + inner->tag = USED_LOOP; + } + } + + // And now create the face that corresponds to this outer loop + // and all of its holes. + int advFaceId = id; + fprintf(f, "#%d=ADVANCED_FACE('',(", advFaceId); + int *fb; + for(fb = listOfLoops.First(); fb; fb = listOfLoops.NextAfter(fb)) { + fprintf(f, "#%d", *fb); + if(listOfLoops.NextAfter(fb) != NULL) fprintf(f, ","); + } + + fprintf(f, "),#%d,.T.);\n", srfid); + fprintf(f, "\n"); + advancedFaces.Add(&advFaceId); + + id++; + + listOfLoops.Clear(); + } } - - fprintf(f, "),#%d,.T.);\n", srfid); - fprintf(f, "\n"); - - id++; - - listOfLoops.Clear(); - return advFaceId; } void StepFileWriter::ExportTo(char *file) { @@ -190,8 +280,8 @@ void StepFileWriter::ExportTo(char *file) { if(shell->surface.n == 0) { Error("The model does not contain any surfaces to export.%s", g->runningMesh.l.n > 0 ? - "\r\nThe model does contain triangles from a mesh, but a " - "triangle mesh cannot be exported as a STEP file. Try " + "\r\n\r\nThe model does contain triangles from a mesh, but " + "a triangle mesh cannot be exported as a STEP file. Try " "File -> Export Mesh... instead." : ""); return; } @@ -206,22 +296,20 @@ void StepFileWriter::ExportTo(char *file) { id = 200; - List ls; - ZERO(&ls); + ZERO(&advancedFaces); SSurface *ss; for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) { if(ss->trim.n == 0) continue; - int sid = ExportSurface(ss); - ls.Add(&sid); + ExportSurface(ss); } fprintf(f, "#%d=CLOSED_SHELL('',(", id); - int *es; - for(es = ls.First(); es; es = ls.NextAfter(es)) { - fprintf(f, "#%d", *es); - if(ls.NextAfter(es) != NULL) fprintf(f, ","); + int *af; + for(af = advancedFaces.First(); af; af = advancedFaces.NextAfter(af)) { + fprintf(f, "#%d", *af); + if(advancedFaces.NextAfter(af) != NULL) fprintf(f, ","); } fprintf(f, "));\n"); fprintf(f, "#%d=MANIFOLD_SOLID_BREP('brep_1',#%d);\n", id+1, id); @@ -238,6 +326,6 @@ void StepFileWriter::ExportTo(char *file) { ); fclose(f); - ls.Clear(); + advancedFaces.Clear(); } diff --git a/polygon.cpp b/polygon.cpp index b727cc97..cc0ee539 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -335,6 +335,11 @@ Vector SContour::ComputeNormal(void) { return n.WithMagnitude(1); } +Vector SContour::AnyEdgeMidpoint(void) { + if(l.n < 2) oops(); + return ((l.elem[0].p).Plus(l.elem[1].p)).ScaledBy(0.5); +} + bool SContour::IsClockwiseProjdToNormal(Vector n) { // Degenerate things might happen as we draw; doesn't really matter // what we do then. diff --git a/polygon.h b/polygon.h index 45a45c82..e61ba189 100644 --- a/polygon.h +++ b/polygon.h @@ -59,6 +59,7 @@ public: void OffsetInto(SContour *dest, double r); void CopyInto(SContour *dest); void FindPointWithMinX(void); + Vector AnyEdgeMidpoint(void); bool IsEar(int bp); bool BridgeToContour(SContour *sc, SEdgeList *el, List *vl); diff --git a/solvespace.cpp b/solvespace.cpp index 4a0585c6..a1a1d82f 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -40,7 +40,7 @@ void SolveSpace::Init(char *cmdLine) { lightDir[1].y = CnfThawFloat( 0.0f, "LightDir_1_Up" ); lightDir[1].z = CnfThawFloat( 0.0f, "LightDir_1_Forward" ); // Chord tolerance - chordTol = CnfThawFloat(3.0f, "ChordTolerance"); + chordTol = CnfThawFloat(1.5f, "ChordTolerance"); // Max pwl segments to generate maxSegments = CnfThawDWORD(10, "MaxSegments"); // View units diff --git a/solvespace.h b/solvespace.h index 2de289e9..cc05b14e 100644 --- a/solvespace.h +++ b/solvespace.h @@ -430,8 +430,10 @@ public: void ExportTo(char *filename); void WriteHeader(void); int ExportCurve(SBezier *sb); - int ExportSurface(SSurface *ss); + int ExportCurveLoop(SBezierLoop *loop, bool inner); + void ExportSurface(SSurface *ss); + List advancedFaces; SShell *shell; FILE *f; int id; diff --git a/srf/surface.h b/srf/surface.h index dc5b4320..5d56c5f9 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -101,6 +101,7 @@ public: class SBezierLoop { public: + int tag; List l; inline void Clear(void) { l.Clear(); } diff --git a/srf/triangulate.cpp b/srf/triangulate.cpp index 016cc527..22917aac 100644 --- a/srf/triangulate.cpp +++ b/srf/triangulate.cpp @@ -38,17 +38,20 @@ void SPolygon::UvTriangulateInto(SMesh *m, SSurface *srf) { List vl; ZERO(&vl); - // And now find all of its holes; + // And now find all of its holes. Note that we will also find any + // outer contours that lie entirely within this contour, and any + // holes for those contours. But that's okay, because we can merge + // those too. SContour *sc; for(sc = l.First(); sc; sc = l.NextAfter(sc)) { if(sc->timesEnclosed != 1) continue; if(sc->l.n < 2) continue; // Test the midpoint of an edge. Our polygon may not be self- - // intersecting, but two countours may share a vertex; so a + // intersecting, but two contours may share a vertex; so a // vertex could be on the edge of another polygon, in which // case ContainsPointProjdToNormal returns indeterminate. - Vector tp = ((sc->l.elem[0].p).Plus(sc->l.elem[1].p)).ScaledBy(0.5); + Vector tp = sc->AnyEdgeMidpoint(); if(top->ContainsPointProjdToNormal(normal, tp)) { sc->tag = 2; sc->MakeEdgesInto(&el);