From b2f2f90a27b00e7486f168a2a88b52c8fc39b45f Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Mon, 7 Jul 2008 22:30:13 -0800 Subject: [PATCH] Add DXF export. The complexity comes from all the different ways to specify the plane from which we want to grab the triangles. Shared edges are then removed with the same code used to check for watertight meshes, and the remaining edges are assembled into polygons. [git-p4: depot-paths = "//depot/solvespace/": change = 1823] --- graphicswin.cpp | 3 +- groupmesh.cpp | 2 +- mesh.cpp | 12 ++- polygon.h | 2 +- solvespace.cpp | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ solvespace.h | 3 + ui.h | 1 + util.cpp | 16 ++-- wishlist.txt | 3 +- 9 files changed, 234 insertions(+), 16 deletions(-) diff --git a/graphicswin.cpp b/graphicswin.cpp index 2be3f0b..89cf1f4 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -17,6 +17,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Save &As...", MNU_SAVE_AS, 0, mFile }, { 1, NULL, 0, 0, NULL }, { 1, "Export &Image...", MNU_EXPORT_PNG, 0, mFile }, +{ 1, "Export &DXF...", MNU_EXPORT_DXF, 0, mFile }, { 1, "Export &Mesh...", MNU_EXPORT_MESH, 0, mFile }, { 1, NULL, 0, 0, NULL }, { 1, "E&xit", MNU_EXIT, 0, mFile }, @@ -516,7 +517,7 @@ void GraphicsWindow::MenuRequest(int id) { case MNU_CIRCLE: s = "click center of circle"; goto c; case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c; case MNU_WORKPLANE: s = "click origin of workplane"; goto c; - case MNU_RECTANGLE: s = "click one corner of rectangular"; goto c; + case MNU_RECTANGLE: s = "click one corner of rectangle"; goto c; case MNU_TTF_TEXT: s = "click top left of text"; goto c; c: SS.GW.pending.operation = id; diff --git a/groupmesh.cpp b/groupmesh.cpp index 505b743..990bac0 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -542,7 +542,7 @@ done: if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { SKdNode *root = SKdNode::From(&runningMesh); root->SnapToMesh(&runningMesh); - root->MakeEdgesToEmphasizeInto(&emphEdges); + root->MakeCertainEdgesInto(&emphEdges, true); } } diff --git a/mesh.cpp b/mesh.cpp index 3488dae..47fcc4a 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -598,7 +598,7 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther, } } -void SKdNode::MakeEdgesToEmphasizeInto(SEdgeList *sel) { +void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, bool emphasized) { SMesh m; ZERO(&m); ClearTags(); @@ -616,11 +616,15 @@ void SKdNode::MakeEdgesToEmphasizeInto(SEdgeList *sel) { int n = 0, nOther = 0; FindEdgeOn(a, b, &n, &nOther, tr->meta, cnt++); if(n != 1) { - dbp("hanging edge: n=%d (%.3f %.3f %.3f) (%.3f %.3f %.3f)", - n, CO(a), CO(b)); + if(!emphasized) { + sel->AddEdge(a, b); + } else { + dbp("hanging: n=%d (%.3f %.3f %.3f) (%.3f %.3f %.3f)", + n, CO(a), CO(b)); + } } if(nOther > 0) { - sel->AddEdge(a, b); + if(emphasized) sel->AddEdge(a, b); } } } diff --git a/polygon.h b/polygon.h index b9ac461..34eafa9 100644 --- a/polygon.h +++ b/polygon.h @@ -244,7 +244,7 @@ public: void FindEdgeOn(Vector a, Vector b, int *n, int *nOther, STriMeta m, int cnt); - void MakeEdgesToEmphasizeInto(SEdgeList *sel); + void MakeCertainEdgesInto(SEdgeList *sel, bool emphasized); void SnapToMesh(SMesh *m); void SnapToVertex(Vector v, SMesh *extras); diff --git a/solvespace.cpp b/solvespace.cpp index 339af96..757becd 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -473,6 +473,207 @@ void SolveSpace::SolveGroup(hGroup hg) { FreeAllTemporary(); } +void SolveSpace::ExportDxfTo(char *filename) { + SPolygon *sp; + SPolygon spa; + ZERO(&spa); + + Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); + gn = gn.WithMagnitude(1); + + SS.GW.GroupSelection(); +#define gs (SS.GW.gs) + + Group *g = SS.GetGroup(SS.GW.activeGroup); + + // The plane in which the exported section lies; need this because we'll + // reorient from that plane into the xy plane before exporting. + Vector p, u, v, n; + double d; + + if(gs.n == 0 && !(g->poly.IsEmpty())) { + // Easiest case--export the polygon drawn in this group + sp = &(g->poly); + p = sp->AnyPoint(); + n = (sp->ComputeNormal()).WithMagnitude(1); + if(n.Dot(gn) < 0) n = n.ScaledBy(-1); + u = n.Normal(0); + v = n.Normal(1); + d = p.Dot(n); + goto havepoly; + } + + if(g->runningMesh.l.n > 0 && + ((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v) || + (gs.n == 1 && gs.faces == 1) || + (gs.n == 3 && gs.vectors == 2 && gs.points == 1))) + { + if(gs.n == 0) { + Entity *wrkpl = SS.GetEntity(g->activeWorkplane); + p = wrkpl->WorkplaneGetOffset(); + n = wrkpl->Normal()->NormalN(); + u = wrkpl->Normal()->NormalU(); + v = wrkpl->Normal()->NormalV(); + } else if(gs.n == 1) { + Entity *face = SS.GetEntity(gs.entity[0]); + p = face->FaceGetPointNum(); + n = face->FaceGetNormalNum(); + if(n.Dot(gn) < 0) n = n.ScaledBy(-1); + u = n.Normal(0); + v = n.Normal(1); + } else if(gs.n == 3) { + Vector ut = SS.GetEntity(gs.entity[0])->VectorGetNum(), + vt = SS.GetEntity(gs.entity[1])->VectorGetNum(); + ut = ut.WithMagnitude(1); + vt = vt.WithMagnitude(1); + + if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) { + SWAP(Vector, ut, vt); + } + if(SS.GW.projRight.Dot(ut) < 0) ut = ut.ScaledBy(-1); + if(SS.GW.projUp. Dot(vt) < 0) vt = vt.ScaledBy(-1); + + p = SS.GetEntity(gs.point[0])->PointGetNum(); + n = ut.Cross(vt); + u = ut.WithMagnitude(1); + v = (n.Cross(u)).WithMagnitude(1); + } else oops(); + n = n.WithMagnitude(1); + d = p.Dot(n); + + SMesh m; + ZERO(&m); + m.MakeFromCopy(&(g->runningMesh)); + + m.l.ClearTags(); + int i; + for(i = 0; i < m.l.n; i++) { + STriangle *tr = &(m.l.elem[i]); + + if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) || + (fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) || + (fabs(n.Dot(tr->c) - d) >= LENGTH_EPS)) + { + tr->tag = 1; + } + } + m.l.RemoveTagged(); + + SKdNode *root = SKdNode::From(&m); + root->SnapToMesh(&m); + + SEdgeList el; + ZERO(&el); + root->MakeCertainEdgesInto(&el, false); + el.AssemblePolygon(&spa, NULL); + sp = &spa; + + el.Clear(); + m.Clear(); + + SS.GW.ClearSelection(); + goto havepoly; + } + + Error("Geometry to export not specified."); + return; + +havepoly: + + FILE *f = fopen(filename, "wb"); + if(!f) { + Error("Couldn't write to '%s'", filename); + spa.Clear(); + return; + } + + // Some software, like Adobe Illustrator, insists on a header. + fprintf(f, +" 999\n" +"file created by SolveSpace\n" +" 0\n" +"SECTION\n" +" 2\n" +"HEADER\n" +" 9\n" +"$ACADVER\n" +" 1\n" +"AC1006\n" +" 9\n" +"$INSBASE\n" +" 10\n" +"0.0\n" +" 20\n" +"0.0\n" +" 30\n" +"0.0\n" +" 9\n" +"$EXTMIN\n" +" 10\n" +"0.0\n" +" 20\n" +"0.0\n" +" 9\n" +"$EXTMAX\n" +" 10\n" +"10000.0\n" +" 20\n" +"10000.0\n" +" 0\n" +"ENDSEC\n"); + + // Now begin the entities, which are just line segments reproduced from + // our piecewise linear curves. + fprintf(f, +" 0\n" +"SECTION\n" +" 2\n" +"ENTITIES\n"); + + int i, j; + for(i = 0; i < sp->l.n; i++) { + SContour *sc = &(sp->l.elem[i]); + + for(j = 1; j < sc->l.n; j++) { + Vector p0 = sc->l.elem[j-1].p, + p1 = sc->l.elem[j].p; + + Point2d e0 = p0.Project2d(u, v), + e1 = p1.Project2d(u, v); + + fprintf(f, +" 0\n" +"LINE\n" +" 8\n" // Layer code +"%d\n" +" 10\n" // xA +"%.6f\n" +" 20\n" // yA +"%.6f\n" +" 30\n" // zA +"%.6f\n" +" 11\n" // xB +"%.6f\n" +" 21\n" // yB +"%.6f\n" +" 31\n" // zB +"%.6f\n", + 0, + e0.x, e0.y, 0.0, + e1.x, e1.y, 0.0); + } + } + + fprintf(f, +" 0\n" +"ENDSEC\n" +" 0\n" +"EOF\n" ); + + spa.Clear(); + fclose(f); +} + void SolveSpace::ExportMeshTo(char *filename) { SMesh *m = &(SS.GetGroup(SS.GW.activeGroup)->runningMesh); if(m->l.n == 0) { @@ -692,6 +893,13 @@ void SolveSpace::MenuFile(int id) { break; } + case GraphicsWindow::MNU_EXPORT_DXF: { + char exportFile[MAX_PATH] = ""; + if(!GetSaveFile(exportFile, DXF_EXT, DXF_PATTERN)) break; + SS.ExportDxfTo(exportFile); + break; + } + case GraphicsWindow::MNU_EXPORT_MESH: { char exportFile[MAX_PATH] = ""; if(!GetSaveFile(exportFile, STL_EXT, STL_PATTERN)) break; diff --git a/solvespace.h b/solvespace.h index fabce78..ec16f82 100644 --- a/solvespace.h +++ b/solvespace.h @@ -60,6 +60,8 @@ int SaveFileYesNoCancel(void); #define PNG_EXT "png" #define STL_PATTERN "STL Mesh (*.stl)\0*.stl\0All Files (*)\0*\0\0" #define STL_EXT "stl" +#define DXF_PATTERN "DXF File (*.dxf)\0*.dxf\0All Files (*)\0*\0\0" +#define DXF_EXT "dxf" BOOL GetSaveFile(char *file, char *defExtension, char *selPattern); BOOL GetOpenFile(char *file, char *defExtension, char *selPattern); void GetAbsoluteFilename(char *file); @@ -400,6 +402,7 @@ public: void ReloadAllImported(void); // And the various export options void ExportAsPngTo(char *file); + void ExportDxfTo(char *file); void ExportMeshTo(char *file); void MarkGroupDirty(hGroup hg); diff --git a/ui.h b/ui.h index 0e9e03a..8754f3b 100644 --- a/ui.h +++ b/ui.h @@ -149,6 +149,7 @@ public: MNU_SAVE_AS, MNU_EXPORT_PNG, MNU_EXPORT_MESH, + MNU_EXPORT_DXF, MNU_EXIT, // View MNU_ZOOM_IN, diff --git a/util.cpp b/util.cpp index cbfb294..6875509 100644 --- a/util.cpp +++ b/util.cpp @@ -349,20 +349,22 @@ Vector Vector::Normal(int which) { // Arbitrarily choose one vector that's normal to us, pivoting // appropriately. double xa = fabs(x), ya = fabs(y), za = fabs(z); - double minc = min(min(xa, ya), za); - if(minc == xa) { + if(this->Equals(Vector::From(0, 0, 1))) { + // Make DXFs exported in the XY plane work nicely... + n = Vector::From(1, 0, 0); + } else if(xa < ya && xa < za) { n.x = 0; n.y = z; n.z = -y; - } else if(minc == ya) { + } else if(ya < za) { + n.x = -z; n.y = 0; n.z = x; - n.x = -z; - } else if(minc == za) { - n.z = 0; + } else { n.x = y; n.y = -x; - } else oops(); + n.z = 0; + } if(which == 0) { // That's the vector we return. diff --git a/wishlist.txt b/wishlist.txt index 4700ab9..fe2847e 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,5 @@ -STL export -DXF export +adaptive pwl for polynomial curves some kind of rounding / chamfer remove back button in browser? auto-generate circles and faces when lathing