diff --git a/export.cpp b/export.cpp index 1b05e942..383a1376 100644 --- a/export.cpp +++ b/export.cpp @@ -181,17 +181,27 @@ void SolveSpace::ExportViewOrWireframeTo(char *filename, bool wireframe) { beziers.Clear(); } - void SolveSpace::ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, VectorFileWriter *out) { - sbl->ScaleSelfBy(1.0/SS.exportScale); + SBezierLoopSetSet sblss; + ZERO(&sblss); SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { - se->a = (se->a).ScaledBy(1.0/SS.exportScale); - se->b = (se->b).ScaledBy(1.0/SS.exportScale); + SBezier sb = SBezier::From( + (se->a).ScaledBy(1.0 / SS.exportScale), + (se->b).ScaledBy(1.0 / SS.exportScale)); + sblss.AddOpenPath(&sb); } - out->Output(sel, sbl, NULL); + + sbl->ScaleSelfBy(1.0/SS.exportScale); + SBezier *sb; + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + sblss.AddOpenPath(sb); + } + + out->Output(&sblss, NULL); + sblss.Clear(); } void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, @@ -327,9 +337,49 @@ void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, sel = &hlrd; } - // Now write the lines and triangles to the output file - out->Output(sel, sbl, &sms); + // We kept the line segments and Beziers separate until now; but put them + // all together, and also project everything into the xy plane, since not + // all export targets ignore the z component of the points. + for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + SBezier sb = SBezier::From(e->a, e->b); + sb.auxA = e->auxA; + sbl->l.Add(&sb); + } + for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + for(int i = 0; i <= b->deg; i++) { + b->ctrl[i].z = 0; + } + } + // If possible, then we will assemble these output curves into loops. They + // will then get exported as closed paths. + SBezierLoopSetSet sblss; + ZERO(&sblss); + SBezierList leftovers; + ZERO(&leftovers); + SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0), + Vector::From(1, 0, 0), + Vector::From(0, 1, 0)); + SPolygon spxyz; + ZERO(&spxyz); + bool allClosed; + SEdge notClosedAt; + sbl->l.ClearTags(); + sblss.FindOuterFacesFrom(sbl, &spxyz, &srf, + SS.ChordTolMm()*s, + &allClosed, ¬ClosedAt, + NULL, NULL, + &leftovers); + for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) { + sblss.AddOpenPath(b); + } + + // Now write the lines and triangles to the output file + out->Output(&sblss, &sms); + + leftovers.Clear(); + spxyz.Clear(); + sblss.Clear(); smp.Clear(); sms.Clear(); hlrd.Clear(); @@ -379,24 +429,19 @@ VectorFileWriter *VectorFileWriter::ForFile(char *filename) { static void AddUnregMessageCallback(void *fndata, Vector a, Vector b) { - SEdgeList *sel = (SEdgeList *)fndata; - sel->AddEdge(a, b, Style::SELECTED); + SBezierLoopSetSet *sblss = (SBezierLoopSetSet *)fndata; + SBezier sb = SBezier::From(a, b); + sb.auxA = Style::SELECTED; + sblss->AddOpenPath(&sb); } -void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { +void VectorFileWriter::Output(SBezierLoopSetSet *sblss, SMesh *sm) { STriangle *tr; - SEdge *e; SBezier *b; // First calculate the bounding box. ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE); - if(sel) { - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { - (e->a).MakeMaxMin(&ptMax, &ptMin); - (e->b).MakeMaxMin(&ptMax, &ptMin); - } - } if(sm) { for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { (tr->a).MakeMaxMin(&ptMax, &ptMin); @@ -404,11 +449,16 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { (tr->c).MakeMaxMin(&ptMax, &ptMin); } } - if(sbl) { - for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { - int i; - for(i = 0; i <= b->deg; i++) { - (b->ctrl[i]).MakeMaxMin(&ptMax, &ptMin); + if(sblss) { + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + SBezierLoop *sbl; + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + for(int i = 0; i <= b->deg; i++) { + (b->ctrl[i]).MakeMaxMin(&ptMax, &ptMin); + } + } } } } @@ -431,7 +481,7 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { // If the demo period has expired and there's no license, then print // a message in any exported file. - if((!SS.license.licensed) && (SS.license.trialDaysRemaining <= 0)) { + if((!SS.license.licensed) && (SS.license.trialDaysRemaining <= 0) && sblss){ char *str = "eval / nonprofit use only -- buy at http://solvespace.com/"; double aspect = (glxStrWidth(str, 1) / glxStrHeight(1)); @@ -453,7 +503,7 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { str, 0.9 * th * SS.GW.scale, t, u, v, - AddUnregMessageCallback, sel); + AddUnregMessageCallback, sblss); if(w > h) { ptMin.y -= th*3; } else { @@ -467,41 +517,45 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { Triangle(tr); } } - if(sel) { - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { - if(!Style::Exportable(e->auxA)) continue; + if(sblss) { + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + SBezierLoop *sbl; + sbl = sbls->l.First(); + if(!sbl) continue; + b = sbl->l.First(); + if(!b || !Style::Exportable(b->auxA)) continue; - DWORD rgb = Style::Color (e->auxA, true); - double w = Style::WidthMm(e->auxA)*s; - LineSegment(rgb, w, e->a, e->b); - } - } - if(sbl) { - for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { - if(!Style::Exportable(b->auxA)) continue; + hStyle hs = { b->auxA }; + Style *stl = Style::Get(hs); + double lineWidth = Style::WidthMm(b->auxA)*s; + DWORD strokeRgb = Style::Color(b->auxA, true); - DWORD rgb = Style::Color (b->auxA, true); - double w = Style::WidthMm(b->auxA)*s; - Bezier(rgb, w, b); + StartPath(strokeRgb, lineWidth, stl->filled, stl->fillColor); + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + Bezier(b); + } + } + FinishPath(strokeRgb, lineWidth, stl->filled, stl->fillColor); } } FinishAndCloseFile(); } -void VectorFileWriter::BezierAsPwl(DWORD rgb, double width, SBezier *sb) { +void VectorFileWriter::BezierAsPwl(SBezier *sb) { List lv; ZERO(&lv); sb->MakePwlInto(&lv, SS.ChordTolMm() / SS.exportScale); int i; for(i = 1; i < lv.n; i++) { - LineSegment(rgb, width, lv.elem[i-1], lv.elem[i]); + SBezier sb = SBezier::From(lv.elem[i-1], lv.elem[i]); + Bezier(&sb); } lv.Clear(); } -void VectorFileWriter::BezierAsNonrationalCubic(DWORD rgb, double width, - SBezier *sb, int depth) -{ +void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { Vector t0 = sb->TangentAt(0), t1 = sb->TangentAt(1); // The curve is correct, and the first derivatives are correct, at the // endpoints. @@ -529,12 +583,12 @@ void VectorFileWriter::BezierAsNonrationalCubic(DWORD rgb, double width, } if(closeEnough || depth > 3) { - Bezier(rgb, width, &bnr); + Bezier(&bnr); } else { SBezier bef, aft; sb->SplitAt(0.5, &bef, &aft); - BezierAsNonrationalCubic(rgb, width, &bef, depth+1); - BezierAsNonrationalCubic(rgb, width, &aft, depth+1); + BezierAsNonrationalCubic(&bef, depth+1); + BezierAsNonrationalCubic(&aft, depth+1); } } diff --git a/exportvector.cpp b/exportvector.cpp index bcb3f7f9..f4f0176e 100644 --- a/exportvector.cpp +++ b/exportvector.cpp @@ -62,8 +62,23 @@ void DxfFileWriter::StartFile(void) { "ENTITIES\r\n"); } -void DxfFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) { - fprintf(f, +void DxfFileWriter::StartPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ +} +void DxfFileWriter::FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ +} + +void DxfFileWriter::Triangle(STriangle *tr) { +} + +void DxfFileWriter::Bezier(SBezier *sb) { + Vector c, n = Vector::From(0, 0, 1); + double r; + if(sb->deg == 1) { + fprintf(f, " 0\r\n" "LINE\r\n" " 8\r\n" // Layer code @@ -80,18 +95,10 @@ void DxfFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) { "%.6f\r\n" " 31\r\n" // zB "%.6f\r\n", - 0, - ptA.x, ptA.y, ptA.z, - ptB.x, ptB.y, ptB.z); -} - -void DxfFileWriter::Triangle(STriangle *tr) { -} - -void DxfFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { - Vector c, n = Vector::From(0, 0, 1); - double r; - if(sb->IsInPlane(n, 0) && sb->IsCircle(n, &c, &r)) { + 0, + sb->ctrl[0].x, sb->ctrl[0].y, sb->ctrl[0].z, + sb->ctrl[1].x, sb->ctrl[1].y, sb->ctrl[1].z); + } else if(sb->IsInPlane(n, 0) && sb->IsCircle(n, &c, &r)) { double theta0 = atan2(sb->ctrl[0].y - c.y, sb->ctrl[0].x - c.x), theta1 = atan2(sb->ctrl[2].y - c.y, sb->ctrl[2].x - c.x), dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI); @@ -121,7 +128,7 @@ void DxfFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { r, theta0*180/PI, theta1*180/PI); } else { - BezierAsPwl(rgb, w, sb); + BezierAsPwl(sb); } } @@ -137,17 +144,6 @@ void DxfFileWriter::FinishAndCloseFile(void) { //----------------------------------------------------------------------------- // Routines for EPS output //----------------------------------------------------------------------------- -char *EpsFileWriter::StyleString(DWORD rgb, double w) { - static char ret[300]; - sprintf(ret, " %.3f setlinewidth\r\n" - " %.3f %.3f %.3f setrgbcolor\r\n" - " 1 setlinejoin\r\n" // rounded - " 1 setlinecap\r\n", // rounded - MmToPts(w), - REDf(rgb), GREENf(rgb), BLUEf(rgb)); - return ret; -} - void EpsFileWriter::StartFile(void) { fprintf(f, "%%!PS-Adobe-2.0\r\n" @@ -167,16 +163,35 @@ void EpsFileWriter::StartFile(void) { MmToPts(ptMax.y - ptMin.y)); } -void EpsFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) { - fprintf(f, -"newpath\r\n" -" %.3f %.3f moveto\r\n" -" %.3f %.3f lineto\r\n" -"%s" -"stroke\r\n", - MmToPts(ptA.x - ptMin.x), MmToPts(ptA.y - ptMin.y), - MmToPts(ptB.x - ptMin.x), MmToPts(ptB.y - ptMin.y), - StyleString(rgb, w)); +void EpsFileWriter::StartPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ + fprintf(f, "newpath\r\n"); + prevPt = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); +} +void EpsFileWriter::FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ + fprintf(f, " %.3f setlinewidth\r\n" + " %.3f %.3f %.3f setrgbcolor\r\n" + " 1 setlinejoin\r\n" // rounded + " 1 setlinecap\r\n" // rounded + " gsave stroke grestore\r\n", + MmToPts(lineWidth), + REDf(strokeRgb), GREENf(strokeRgb), BLUEf(strokeRgb)); + if(filled) { + fprintf(f, " %.3f %.3f %.3f setrgbcolor\r\n" + " gsave fill grestore\r\n", + REDf(fillRgb), GREENf(fillRgb), BLUEf(fillRgb)); + } +} + +void EpsFileWriter::MaybeMoveTo(Vector st, Vector fi) { + if(!prevPt.Equals(st)) { + fprintf(f, " %.3f %.3f moveto\r\n", + MmToPts(st.x - ptMin.x), MmToPts(st.y - ptMin.y)); + } + prevPt = fi; } void EpsFileWriter::Triangle(STriangle *tr) { @@ -187,7 +202,7 @@ void EpsFileWriter::Triangle(STriangle *tr) { " %.3f %.3f lineto\r\n" " %.3f %.3f lineto\r\n" " closepath\r\n" -"fill\r\n", +"gsave fill grestore\r\n", REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color), MmToPts(tr->a.x - ptMin.x), MmToPts(tr->a.y - ptMin.y), MmToPts(tr->b.x - ptMin.x), MmToPts(tr->b.y - ptMin.y), @@ -196,25 +211,22 @@ void EpsFileWriter::Triangle(STriangle *tr) { // same issue with cracks, stroke it to avoid them double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; fprintf(f, -"%.3f %.3f %.3f setrgbcolor\r\n" +"1 setlinejoin\r\n" +"1 setlinecap\r\n" "%.3f setlinewidth\r\n" -"newpath\r\n" -" %.3f %.3f moveto\r\n" -" %.3f %.3f lineto\r\n" -" %.3f %.3f lineto\r\n" -" closepath\r\n" -"stroke\r\n", - REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color), - MmToPts(sw), - MmToPts(tr->a.x - ptMin.x), MmToPts(tr->a.y - ptMin.y), - MmToPts(tr->b.x - ptMin.x), MmToPts(tr->b.y - ptMin.y), - MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y)); +"gsave stroke grestore\r\n", + MmToPts(sw)); } -void EpsFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { +void EpsFileWriter::Bezier(SBezier *sb) { Vector c, n = Vector::From(0, 0, 1); double r; - if(sb->IsCircle(n, &c, &r)) { + if(sb->deg == 1) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]); + fprintf(f, " %.3f %.3f lineto\r\n", + MmToPts(sb->ctrl[1].x - ptMin.x), + MmToPts(sb->ctrl[1].y - ptMin.y)); + } else if(sb->IsCircle(n, &c, &r)) { Vector p0 = sb->ctrl[0], p1 = sb->ctrl[2]; double theta0 = atan2(p0.y - c.y, p0.x - c.x), theta1 = atan2(p1.y - c.y, p1.x - c.x), @@ -223,31 +235,21 @@ void EpsFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { SWAP(double, theta0, theta1); SWAP(Vector, p0, p1); } + MaybeMoveTo(p0, p1); fprintf(f, -"newpath\r\n" -" %.3f %.3f moveto\r\n" -" %.3f %.3f %.3f %.3f %.3f arc\r\n" -"%s" -"stroke\r\n", - MmToPts(p0.x - ptMin.x), MmToPts(p0.y - ptMin.y), +" %.3f %.3f %.3f %.3f %.3f arc\r\n", MmToPts(c.x - ptMin.x), MmToPts(c.y - ptMin.y), MmToPts(r), - theta0*180/PI, theta1*180/PI, - StyleString(rgb, w)); + theta0*180/PI, theta1*180/PI); } else if(sb->deg == 3 && !sb->IsRational()) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]); fprintf(f, -"newpath\r\n" -" %.3f %.3f moveto\r\n" -" %.3f %.3f %.3f %.3f %.3f %.3f curveto\r\n" -"%s" -"stroke\r\n", - MmToPts(sb->ctrl[0].x - ptMin.x), MmToPts(sb->ctrl[0].y - ptMin.y), +" %.3f %.3f %.3f %.3f %.3f %.3f curveto\r\n", MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y), MmToPts(sb->ctrl[2].x - ptMin.x), MmToPts(sb->ctrl[2].y - ptMin.y), - MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y), - StyleString(rgb, w)); + MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y)); } else { - BezierAsNonrationalCubic(rgb, w, sb); + BezierAsNonrationalCubic(sb); } } @@ -263,16 +265,6 @@ void EpsFileWriter::FinishAndCloseFile(void) { // Routines for PDF output, some extra complexity because we have to generate // a correct xref table. //----------------------------------------------------------------------------- -char *PdfFileWriter::StyleString(DWORD rgb, double w) { - static char ret[300]; - sprintf(ret, "1 J 1 j " // round endcaps and joins - "%.3f w " - "%.3f %.3f %.3f RG\r\n", - MmToPts(w), - REDf(rgb), GREENf(rgb), BLUEf(rgb)); - return ret; -} - void PdfFileWriter::StartFile(void) { fprintf(f, "%%PDF-1.1\r\n" @@ -392,21 +384,44 @@ void PdfFileWriter::FinishAndCloseFile(void) { } -void PdfFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) { - fprintf(f, -"%s" -"%.3f %.3f m\r\n" -"%.3f %.3f l\r\n" -"S\r\n", - StyleString(rgb, w), - MmToPts(ptA.x - ptMin.x), MmToPts(ptA.y - ptMin.y), - MmToPts(ptB.x - ptMin.x), MmToPts(ptB.y - ptMin.y)); +void PdfFileWriter::StartPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ + fprintf(f, "1 J 1 j " // round endcaps and joins + "%.3f w " + "%.3f %.3f %.3f RG\r\n", + MmToPts(lineWidth), + REDf(strokeRgb), GREENf(strokeRgb), BLUEf(strokeRgb)); + if(filled) { + fprintf(f, "%.3f %.3f %.3f rg\r\n", + REDf(fillRgb), GREENf(fillRgb), BLUEf(fillRgb)); + } + + prevPt = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); +} +void PdfFileWriter::FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ + if(filled) { + fprintf(f, "b\r\n"); + } else { + fprintf(f, "S\r\n"); + } +} + +void PdfFileWriter::MaybeMoveTo(Vector st, Vector fi) { + if(!prevPt.Equals(st)) { + fprintf(f, "%.3f %.3f m\r\n", + MmToPts(st.x - ptMin.x), MmToPts(st.y - ptMin.y)); + } + prevPt = fi; } void PdfFileWriter::Triangle(STriangle *tr) { double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; fprintf(f, +"1 J 1 j\r\n" "%.3f %.3f %.3f RG\r\n" "%.3f %.3f %.3f rg\r\n" "%.3f w\r\n" @@ -422,35 +437,27 @@ void PdfFileWriter::Triangle(STriangle *tr) { MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y)); } -void PdfFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { - if(sb->deg == 3 && !sb->IsRational()) { +void PdfFileWriter::Bezier(SBezier *sb) { + if(sb->deg == 1) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]); fprintf(f, -"%s" -"%.3f %.3f m\r\n" -"%.3f %.3f %.3f %.3f %.3f %.3f c\r\n" -"S\r\n", - StyleString(rgb, w), - MmToPts(sb->ctrl[0].x - ptMin.x), MmToPts(sb->ctrl[0].y - ptMin.y), +"%.3f %.3f l\r\n", + MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y)); + } else if(sb->deg == 3 && !sb->IsRational()) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]); + fprintf(f, +"%.3f %.3f %.3f %.3f %.3f %.3f c\r\n", MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y), MmToPts(sb->ctrl[2].x - ptMin.x), MmToPts(sb->ctrl[2].y - ptMin.y), MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y)); } else { - BezierAsNonrationalCubic(rgb, w, sb); + BezierAsNonrationalCubic(sb); } } //----------------------------------------------------------------------------- // Routines for SVG output //----------------------------------------------------------------------------- -char *SvgFileWriter::StyleString(DWORD rgb, double w) { - static char ret[200]; - sprintf(ret, "stroke-width='%.3f' stroke='#%02x%02x%02x' " - "style='fill: none;' " - "stroke-linecap='round' stroke-linejoin='round' ", - w, RED(rgb), GREEN(rgb), BLUE(rgb)); - return ret; -} - void SvgFileWriter::StartFile(void) { fprintf(f, "\r\n", + lineWidth, RED(strokeRgb), GREEN(strokeRgb), BLUE(strokeRgb), + fill); +} + +void SvgFileWriter::MaybeMoveTo(Vector st, Vector fi) { // SVG uses a coordinate system with the origin at top left, +y down - fprintf(f, -"\r\n", - (ptA.x - ptMin.x), (ptMax.y - ptA.y), - (ptB.x - ptMin.x), (ptMax.y - ptB.y), - StyleString(rgb, w)); + if(!prevPt.Equals(st)) { + fprintf(f, "M%.3f %.3f ", (st.x - ptMin.x), (ptMax.y - st.y)); + } + prevPt = fi; } void SvgFileWriter::Triangle(STriangle *tr) { @@ -493,10 +522,14 @@ void SvgFileWriter::Triangle(STriangle *tr) { RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color)); } -void SvgFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { +void SvgFileWriter::Bezier(SBezier *sb) { Vector c, n = Vector::From(0, 0, 1); double r; - if(sb->IsCircle(n, &c, &r)) { + if(sb->deg == 1) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]); + fprintf(f, "L%.3f,%.3f ", + (sb->ctrl[1].x - ptMin.x), (ptMax.y - sb->ctrl[1].y)); + } else if(sb->IsCircle(n, &c, &r)) { Vector p0 = sb->ctrl[0], p1 = sb->ctrl[2]; double theta0 = atan2(p0.y - c.y, p0.x - c.x), theta1 = atan2(p1.y - c.y, p1.x - c.x), @@ -508,36 +541,25 @@ void SvgFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { if(dtheta < 0) { SWAP(Vector, p0, p1); } - fprintf(f, -"\r\n", - p0.x - ptMin.x, ptMax.y - p0.y, - r, r, - p1.x - ptMin.x, ptMax.y - p1.y, - StyleString(rgb, w)); + MaybeMoveTo(p0, p1); + fprintf(f, "A%.3f,%.3f 0 0,0 %.3f,%.3f ", + r, r, + p1.x - ptMin.x, ptMax.y - p1.y); } else if(!sb->IsRational()) { - if(sb->deg == 1) { - LineSegment(rgb, w, sb->ctrl[0], sb->ctrl[1]); - } else if(sb->deg == 2) { - fprintf(f, -"\r\n", - sb->ctrl[0].x - ptMin.x, ptMax.y - sb->ctrl[0].y, + if(sb->deg == 2) { + MaybeMoveTo(sb->ctrl[0], sb->ctrl[2]); + fprintf(f, "Q%.3f,%.3f %.3f,%.3f ", sb->ctrl[1].x - ptMin.x, ptMax.y - sb->ctrl[1].y, - sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y, - StyleString(rgb, w)); + sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y); } else if(sb->deg == 3) { - fprintf(f, -"\r\n", - sb->ctrl[0].x - ptMin.x, ptMax.y - sb->ctrl[0].y, + MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]); + fprintf(f, "C%.3f,%.3f %.3f,%.3f %.3f,%.3f ", sb->ctrl[1].x - ptMin.x, ptMax.y - sb->ctrl[1].y, sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y, - sb->ctrl[3].x - ptMin.x, ptMax.y - sb->ctrl[3].y, - StyleString(rgb, w)); + sb->ctrl[3].x - ptMin.x, ptMax.y - sb->ctrl[3].y); } } else { - BezierAsNonrationalCubic(rgb, w, sb); + BezierAsNonrationalCubic(sb); } } @@ -558,20 +580,29 @@ void HpglFileWriter::StartFile(void) { fprintf(f, "SP1;\r\n"); } -void HpglFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) { - fprintf(f, "PU%d,%d;\r\n", - (int)MmToHpglUnits(ptA.x), - (int)MmToHpglUnits(ptA.y)); - fprintf(f, "PD%d,%d;\r\n", - (int)MmToHpglUnits(ptB.x), - (int)MmToHpglUnits(ptB.y)); +void HpglFileWriter::StartPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ +} +void HpglFileWriter::FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ } void HpglFileWriter::Triangle(STriangle *tr) { } -void HpglFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { - BezierAsPwl(rgb, w, sb); +void HpglFileWriter::Bezier(SBezier *sb) { + if(sb->deg == 1) { + fprintf(f, "PU%d,%d;\r\n", + (int)MmToHpglUnits(sb->ctrl[0].x), + (int)MmToHpglUnits(sb->ctrl[0].y)); + fprintf(f, "PD%d,%d;\r\n", + (int)MmToHpglUnits(sb->ctrl[1].x), + (int)MmToHpglUnits(sb->ctrl[1].y)); + } else { + BezierAsPwl(sb); + } } void HpglFileWriter::FinishAndCloseFile(void) { @@ -591,13 +622,16 @@ void Step2dFileWriter::StartFile(void) { void Step2dFileWriter::Triangle(STriangle *tr) { } -void Step2dFileWriter::LineSegment(DWORD rgb, double w, Vector ptA, Vector ptB) +void Step2dFileWriter::StartPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) +{ +} +void Step2dFileWriter::FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) { - SBezier sb = SBezier::From(ptA, ptB); - Bezier(rgb, w, &sb); } -void Step2dFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { +void Step2dFileWriter::Bezier(SBezier *sb) { int c = sfw.ExportCurve(sb); sfw.curves.Add(&c); } diff --git a/groupmesh.cpp b/groupmesh.cpp index 66ab8b9c..62c04bd8 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -467,7 +467,8 @@ void Group::Draw(void) { glxVertex3v(polyError.notClosedAt.b); glEnd(); glxColorRGB(Style::Color(Style::DRAW_ERROR)); - glxWriteText("not closed contour!", DEFAULT_TEXT_HEIGHT, + glxWriteText("not closed contour, or not all same style!", + DEFAULT_TEXT_HEIGHT, polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp, NULL, NULL); glEnable(GL_DEPTH_TEST); @@ -492,45 +493,6 @@ void Group::Draw(void) { } } -//----------------------------------------------------------------------------- -// Verify that the Beziers in this loop set all have the same auxA, and return -// that value. If they don't, then set allSame to be false, and indicate a -// point on the non-matching curve. -//----------------------------------------------------------------------------- -DWORD Group::GetLoopSetFillColor(SBezierLoopSet *sbls, - bool *allSame, Vector *errorAt) -{ - bool first = true; - DWORD fillRgb = (DWORD)-1; - - SBezierLoop *sbl; - for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { - SBezier *sb; - for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - DWORD thisRgb = (DWORD)-1; - if(sb->auxA != 0) { - hStyle hs = { sb->auxA }; - Style *s = Style::Get(hs); - if(s->filled) { - thisRgb = s->fillColor; - } - } - if(first) { - fillRgb = thisRgb; - first = false; - } else { - if(fillRgb != thisRgb) { - *allSame = false; - *errorAt = sb->Start(); - return fillRgb; - } - } - } - } - *allSame = true; - return fillRgb; -} - void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) { SPolygon sp; ZERO(&sp); @@ -545,18 +507,16 @@ void Group::DrawFilledPaths(void) { SBezierLoopSet *sbls; SBezierLoopSetSet *sblss = &bezierLoops; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { - bool allSame; - Vector errorPt; - DWORD fillRgb = GetLoopSetFillColor(sbls, &allSame, &errorPt); - if(allSame && fillRgb != (DWORD)-1) { - glxColorRGBa(fillRgb, 1); + if(sbls->l.n == 0 || sbls->l.elem[0].l.n == 0) continue; + // In an assembled loop, all the styles should be the same; so doesn't + // matter which one we grab. + SBezier *sb = &(sbls->l.elem[0].l.elem[0]); + hStyle hs = { sb->auxA }; + Style *s = Style::Get(hs); + if(s->filled) { + // This is a filled loop, where the user specified a fill color. + glxColorRGBa(s->fillColor, 1); FillLoopSetAsPolygon(sbls); - } else if(!allSame) { - glDisable(GL_DEPTH_TEST); - glxColorRGB(Style::Color(Style::DRAW_ERROR)); - glxWriteText("not all same fill color!", DEFAULT_TEXT_HEIGHT, - errorPt, SS.GW.projRight, SS.GW.projUp, NULL, NULL); - glEnable(GL_DEPTH_TEST); } else { if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour && polyError.how == POLY_GOOD) diff --git a/solvespace.h b/solvespace.h index 9ea4a869..7c6f2099 100644 --- a/solvespace.h +++ b/solvespace.h @@ -415,33 +415,42 @@ public: static VectorFileWriter *ForFile(char *file); - void Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm); + void Output(SBezierLoopSetSet *sblss, SMesh *sm); - void BezierAsPwl(DWORD rgb, double width, SBezier *sb); - void BezierAsNonrationalCubic(DWORD rgb, double width, - SBezier *sb, int depth=0); + void BezierAsPwl(SBezier *sb); + void BezierAsNonrationalCubic(SBezier *sb, int depth=0); - virtual void Bezier(DWORD rgb, double width, SBezier *sb) = 0; - virtual void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB) - = 0; + virtual void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) = 0; + virtual void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb) = 0; + virtual void Bezier(SBezier *sb) = 0; virtual void Triangle(STriangle *tr) = 0; virtual void StartFile(void) = 0; virtual void FinishAndCloseFile(void) = 0; }; class DxfFileWriter : public VectorFileWriter { public: - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class EpsFileWriter : public VectorFileWriter { public: - static char *StyleString(DWORD rgb, double width); - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + Vector prevPt; + void MaybeMoveTo(Vector s, Vector f); + + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -449,37 +458,52 @@ class PdfFileWriter : public VectorFileWriter { public: DWORD xref[10]; DWORD bodyStart; + Vector prevPt; + void MaybeMoveTo(Vector s, Vector f); - static char *StyleString(DWORD rgb, double width); - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class SvgFileWriter : public VectorFileWriter { public: - static char *StyleString(DWORD rgb, double width); - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + Vector prevPt; + void MaybeMoveTo(Vector s, Vector f); + + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class HpglFileWriter : public VectorFileWriter { public: static double MmToHpglUnits(double mm); - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class Step2dFileWriter : public VectorFileWriter { StepFileWriter sfw; - void LineSegment(DWORD rgb, double width, Vector ptA, Vector ptB); + void StartPath( DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); + void FinishPath(DWORD strokeRgb, double lineWidth, + bool filled, DWORD fillRgb); void Triangle(STriangle *tr); - void Bezier(DWORD rgb, double width, SBezier *sb); + void Bezier(SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; diff --git a/srf/curve.cpp b/srf/curve.cpp index 4bac32cf..698d1cdf 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -402,6 +402,7 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, loop.l.Add(first); Vector start = first->Start(); Vector hanging = first->Finish(); + int auxA = first->auxA; sbl->l.RemoveTagged(); @@ -411,11 +412,11 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, for(i = 0; i < sbl->l.n; i++) { SBezier *test = &(sbl->l.elem[i]); - if((test->Finish()).Equals(hanging)) { + if((test->Finish()).Equals(hanging) && test->auxA == auxA) { test->Reverse(); // and let the next test catch it } - if((test->Start()).Equals(hanging)) { + if((test->Start()).Equals(hanging) && test->auxA == auxA) { test->tag = 1; loop.l.Add(test); hanging = test->Finish(); @@ -690,11 +691,48 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, l.Add(&outerAndInners); } } + + // If we have poorly-formed loops--for example, overlapping zero-area + // stuff--then we can end up with leftovers. We use this function to + // group stuff into closed paths for export when possible, so it's bad + // to screw up on that stuff. So just add them onto the open curve list. + // Very ugly, but better than losing curves. + for(i = 0; i < sbls.l.n; i++) { + SBezierLoop *loop = &(sbls.l.elem[i]); + if(loop->tag == USED_LOOP) continue; + + if(openContours) { + SBezier *sb; + for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { + openContours->l.Add(sb); + } + } + loop->Clear(); + // but don't free the used loops, since we shallow-copied them to + // ourself + } + + sbls.l.Clear(); // not sbls.Clear(), since that would deep-clear spuv.Clear(); - // Don't free sbls; we've shallow-copied all of its members to ourself. +} + +void SBezierLoopSetSet::AddOpenPath(SBezier *sb) { + SBezierLoop sbl; + ZERO(&sbl); + sbl.l.Add(sb); + + SBezierLoopSet sbls; + ZERO(&sbls); + sbls.l.Add(&sbl); + + l.Add(&sbls); } void SBezierLoopSetSet::Clear(void) { + SBezierLoopSet *sbls; + for(sbls = l.First(); sbls; sbls = l.NextAfter(sbls)) { + sbls->Clear(); + } l.Clear(); } diff --git a/srf/surface.h b/srf/surface.h index 09fdf1ab..33c30849 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -144,7 +144,6 @@ public: void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); void MakePwlInto(SPolygon *sp); - int GetAuxA(bool *allSame, Vector *errorAt); void Clear(void); }; @@ -157,6 +156,7 @@ public: bool *allClosed, SEdge *notClosedAt, bool *allCoplanar, Vector *notCoplanarAt, SBezierList *openContours); + void AddOpenPath(SBezier *sb); void Clear(void); };