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