From b8da4ed2b3de7d47a4b21b2f8c9e99a9dace7284 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 13 Jan 2009 21:10:42 -0800 Subject: [PATCH] Split export DXF command into export section and export view, and add framework for additional vector output formats (ps, pdf). [git-p4: depot-paths = "//depot/solvespace/": change = 1893] --- export.cpp | 295 +++++++++++++++++++++++++++++------------------- graphicswin.cpp | 3 +- solvespace.cpp | 11 +- solvespace.h | 26 ++++- srf/surface.h | 33 +++++- textscreens.cpp | 2 +- ui.h | 3 +- 7 files changed, 250 insertions(+), 123 deletions(-) diff --git a/export.cpp b/export.cpp index fa53e8c0..adb32076 100644 --- a/export.cpp +++ b/export.cpp @@ -1,123 +1,152 @@ #include "solvespace.h" #include -void SolveSpace::ExportDxfTo(char *filename) { +void SolveSpace::ExportSectionTo(char *filename) { SPolygon sp; ZERO(&sp); 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); - + if(g->runningMesh.l.n == 0) { + Error("No solid model present; draw one with extrudes and revolves, " + "or use Export 2d View to export bare lines and curves."); + return; + } + // 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; + Vector origin, u, v, n; double d; - // Don't use the assembled polygon from the group data structure; that - // one gets cleared if the curves aren't all closed. - g->AssemblePolygon(&sp, NULL); - - if(gs.n == 0 && !(sp.IsEmpty())) { - // Easiest case--export the polygon drawn in this group - p = sp.AnyPoint(); - n = (sp.ComputeNormal()).WithMagnitude(1); + SS.GW.GroupSelection(); +#define gs (SS.GW.gs) + if((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v)) { + Entity *wrkpl = SS.GetEntity(g->activeWorkplane); + origin = wrkpl->WorkplaneGetOffset(); + n = wrkpl->Normal()->NormalN(); + u = wrkpl->Normal()->NormalU(); + v = wrkpl->Normal()->NormalV(); + } else if(gs.n == 1 && gs.faces == 1) { + Entity *face = SS.GetEntity(gs.entity[0]); + origin = face->FaceGetPointNum(); + n = face->FaceGetNormalNum(); if(n.Dot(gn) < 0) n = n.ScaledBy(-1); u = n.Normal(0); v = n.Normal(1); - d = p.Dot(n); - goto havepoly; + } else if(gs.n == 3 && gs.vectors == 2 && gs.points == 1) { + 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); + + origin = SS.GetEntity(gs.point[0])->PointGetNum(); + n = ut.Cross(vt); + u = ut.WithMagnitude(1); + v = (n.Cross(u)).WithMagnitude(1); + } else { + Error("Bad selection for export section. Please select:\r\n\r\n" + " * nothing, with an active workplane " + "(workplane is section plane)\r\n" + " * a face (section plane through face)\r\n" + " * a point and two line segments " + "(plane through point and parallel to lines)\r\n"); + return; + } + SS.GW.ClearSelection(); + + n = n.WithMagnitude(1); + d = origin.Dot(n); + + SMesh m; + ZERO(&m); + m.MakeFromCopy(&(g->runningMesh)); + + // Delete all triangles in the mesh that do not lie in our export plane. + 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(); + + // Select the naked edges in our resulting open mesh. + SKdNode *root = SKdNode::From(&m); + root->SnapToMesh(&m); + SEdgeList el; + ZERO(&el); + root->MakeCertainEdgesInto(&el, false); + // Assemble those edges into a polygon, and clear the edge list + el.AssemblePolygon(&sp, NULL); + el.Clear(); + m.Clear(); + + // And write the polygon. + VectorFileWriter *out = VectorFileWriter::ForFile(filename); + if(out) { + ExportPolygon(&sp, u, v, n, origin, out); } sp.Clear(); +} - 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); +void SolveSpace::ExportViewTo(char *filename) { + int i; + SEdgeList edges; + ZERO(&edges); + for(i = 0; i < SS.entity.n; i++) { + Entity *e = &(SS.entity.elem[i]); + if(!e->IsVisible()) continue; - 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(&sp, NULL); - - el.Clear(); - m.Clear(); - - SS.GW.ClearSelection(); - goto havepoly; + e->GenerateEdges(&edges); } + + SPolygon sp; + ZERO(&sp); + edges.AssemblePolygon(&sp, NULL); - Error("Geometry to export not specified."); - return; + Vector u = SS.GW.projRight, + v = SS.GW.projUp, + n = u.Cross(v), + origin = SS.GW.offset.ScaledBy(-1); -havepoly: + VectorFileWriter *out = VectorFileWriter::ForFile(filename); + if(out) { + ExportPolygon(&sp, u, v, n, origin, out); + } + edges.Clear(); + sp.Clear(); +} +void SolveSpace::ExportPolygon(SPolygon *sp, + Vector u, Vector v, Vector n, Vector origin, + VectorFileWriter *out) +{ int i, j; // Project into the export plane; so when we're done, z doesn't matter, // and x and y are what goes in the DXF. - for(i = 0; i < sp.l.n; i++) { - SContour *sc = &(sp.l.elem[i]); + for(i = 0; i < sp->l.n; i++) { + SContour *sc = &(sp->l.elem[i]); for(j = 0; j < sc->l.n; j++) { Vector *p = &(sc->l.elem[j].p); + *p = p->Minus(origin); *p = p->DotInToCsys(u, v, n); + // and apply the export scale factor + double s = SS.exportScale; + *p = p->ScaledBy(1.0/s); } } @@ -125,20 +154,62 @@ havepoly: if(fabs(SS.exportOffset) > LENGTH_EPS) { SPolygon compd; ZERO(&compd); - sp.normal = Vector::From(0, 0, -1); - sp.FixContourDirections(); - sp.OffsetInto(&compd, SS.exportOffset); - sp.Clear(); - sp = compd; + sp->normal = Vector::From(0, 0, -1); + sp->FixContourDirections(); + sp->OffsetInto(&compd, SS.exportOffset); + sp->Clear(); + *sp = compd; + } + + // Now begin the entities, which are just line segments reproduced from + // our piecewise linear curves. + out->StartFile(); + 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; + + out->LineSegment(p0.x, p0.y, p1.x, p1.y); + } + } + out->FinishAndCloseFile(); +} + +bool VectorFileWriter::StringEndsIn(char *str, char *ending) { + int i, ls = strlen(str), le = strlen(ending); + + if(ls < le) return false; + + for(i = 0; i < le; i++) { + if(tolower(ending[le-i-1]) != tolower(str[ls-i-1])) { + return false; + } + } + return true; +} + +VectorFileWriter *VectorFileWriter::ForFile(char *filename) { + VectorFileWriter *ret; + if(StringEndsIn(filename, ".dxf")) { + static DxfFileWriter DxfWriter; + ret = &DxfWriter; + } else { + Error("Can't identify output file type from file extension."); + return NULL; } FILE *f = fopen(filename, "wb"); if(!f) { Error("Couldn't write to '%s'", filename); - sp.Clear(); - return; + return NULL; } + ret->f = f; + return ret; +} +void DxfFileWriter::StartFile(void) { // Some software, like Adobe Illustrator, insists on a header. fprintf(f, " 999\r\n" @@ -174,24 +245,19 @@ havepoly: " 0\r\n" "ENDSEC\r\n"); - // Now begin the entities, which are just line segments reproduced from - // our piecewise linear curves. + // Then start the entities. fprintf(f, " 0\r\n" "SECTION\r\n" " 2\r\n" "ENTITIES\r\n"); +} - for(i = 0; i < sp.l.n; i++) { - SContour *sc = &(sp.l.elem[i]); +void DxfFileWriter::SetLineWidth(double mm) { +} - for(j = 1; j < sc->l.n; j++) { - Vector p0 = sc->l.elem[j-1].p, - p1 = sc->l.elem[j].p; - - double s = SS.exportScale; - - fprintf(f, +void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { + fprintf(f, " 0\r\n" "LINE\r\n" " 8\r\n" // Layer code @@ -209,18 +275,16 @@ havepoly: " 31\r\n" // zB "%.6f\r\n", 0, - p0.x/s, p0.y/s, 0.0, - p1.x/s, p1.y/s, 0.0); - } - } + x0, y0, 0.0, + x1, y1, 0.0); +} +void DxfFileWriter::FinishAndCloseFile(void) { fprintf(f, " 0\r\n" "ENDSEC\r\n" " 0\r\n" "EOF\r\n" ); - - sp.Clear(); fclose(f); } @@ -279,8 +343,11 @@ void SolveSpace::ExportMeshTo(char *filename) { void SolveSpace::ExportAsPngTo(char *filename) { int w = (int)SS.GW.width, h = (int)SS.GW.height; // No guarantee that the back buffer contains anything valid right now, - // so repaint the scene. + // so repaint the scene. And hide the toolbar too. + int prevShowToolbar = SS.showToolbar; + SS.showToolbar = false; SS.GW.Paint(w, h); + SS.showToolbar = prevShowToolbar; FILE *f = fopen(filename, "wb"); if(!f) goto err; diff --git a/graphicswin.cpp b/graphicswin.cpp index 30bcd859..32d546e7 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -20,7 +20,8 @@ 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 2d &View...", MNU_EXPORT_VIEW, 0, mFile }, +{ 1, "Export 2d &Section...", MNU_EXPORT_SECTION, 0, mFile }, { 1, "Export &Mesh...", MNU_EXPORT_MESH, 0, mFile }, { 1, NULL, 0, 0, NULL }, { 1, "E&xit", MNU_EXIT, 0, mFile }, diff --git a/solvespace.cpp b/solvespace.cpp index 1070ef43..8a8c11eb 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -335,10 +335,17 @@ void SolveSpace::MenuFile(int id) { break; } - case GraphicsWindow::MNU_EXPORT_DXF: { + case GraphicsWindow::MNU_EXPORT_VIEW: { char exportFile[MAX_PATH] = ""; if(!GetSaveFile(exportFile, DXF_EXT, DXF_PATTERN)) break; - SS.ExportDxfTo(exportFile); + SS.ExportViewTo(exportFile); + break; + } + + case GraphicsWindow::MNU_EXPORT_SECTION: { + char exportFile[MAX_PATH] = ""; + if(!GetSaveFile(exportFile, DXF_EXT, DXF_PATTERN)) break; + SS.ExportSectionTo(exportFile); break; } diff --git a/solvespace.h b/solvespace.h index a808d4c2..c2e60ecf 100644 --- a/solvespace.h +++ b/solvespace.h @@ -327,6 +327,26 @@ public: hEntity he, Vector origin, Vector u, Vector v); }; +class VectorFileWriter { +public: + FILE *f; + + static bool StringEndsIn(char *str, char *ending); + static VectorFileWriter *ForFile(char *file); + + virtual void SetLineWidth(double mm) = 0; + virtual void LineSegment(double x0, double y0, double x1, double y1) = 0; + virtual void StartFile(void) = 0; + virtual void FinishAndCloseFile(void) = 0; +}; +class DxfFileWriter : public VectorFileWriter { +public: + void SetLineWidth(double mm); + void LineSegment(double x0, double y0, double x1, double y1); + void StartFile(void); + void FinishAndCloseFile(void); +}; + class SolveSpace { public: TextWindow TW; @@ -438,8 +458,12 @@ public: void ReloadAllImported(void); // And the various export options void ExportAsPngTo(char *file); - void ExportDxfTo(char *file); void ExportMeshTo(char *file); + void ExportViewTo(char *file); + void ExportSectionTo(char *file); + void ExportPolygon(SPolygon *sp, + Vector u, Vector v, Vector n, Vector origin, + VectorFileWriter *out); static void MenuAnalyze(int id); struct { diff --git a/srf/surface.h b/srf/surface.h index 80ed4689..aee20e26 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -5,6 +5,28 @@ class hSCurve; class hSSurface; +// Stuff for rational polynomial curves, of degree one to three. These are +// our inputs. +class SPolyCurve { +public: + int deg; + Vector ctrl[4]; + double weight[4]; + + Vector EvalAt(double t); + void GeneratePwlInto(SEdgeList *el); + + static SPolyCurve From(Vector p0, Vector p1, Vector p2, Vector p3); + static SPolyCurve From(Vector p0, Vector p1); +}; + +class SPolyCurveList { +public: + List l; +}; + + +// Stuff for the surface trim curves: piecewise linear class hSCurve { public: DWORD v; @@ -19,12 +41,16 @@ public: hSSurface srfB; }; +// A segment of a curve by which a surface is trimmed: indicates which curve, +// by its handle, and the starting and ending points of our segment of it. +// The vector out points out of the surface; it, the surface outer normal, +// and a tangent to the beginning of the curve are all orthogonal. class STrimBy { public: hSCurve curve; Vector start; Vector finish; - Vector out; // a vector pointing out of the contour + Vector out; }; class hSSurface { @@ -36,15 +62,16 @@ class SSurface { public: hSSurface h; + int degm, degn; Vector ctrl[4][4]; - double weight[4]; + double weight[4][4]; SList trim; }; class SShell { public: - IdList allCurves; + IdList curve; IdList surface; }; diff --git a/textscreens.cpp b/textscreens.cpp index 84992739..82bb781f 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -660,7 +660,7 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", (double)SS.exportScale, &ScreenChangeExportScale, 0); - Printf(false, "%Ft cutter radius offset (always in mm) "); + Printf(false, "%Ft cutter radius offset (in export units) "); Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E", (double)SS.exportOffset, &ScreenChangeExportOffset, 0); diff --git a/ui.h b/ui.h index f49562fb..3b81f991 100644 --- a/ui.h +++ b/ui.h @@ -178,7 +178,8 @@ public: MNU_SAVE_AS, MNU_EXPORT_PNG, MNU_EXPORT_MESH, - MNU_EXPORT_DXF, + MNU_EXPORT_VIEW, + MNU_EXPORT_SECTION, MNU_EXIT, // View MNU_ZOOM_IN,