From 9b8f32dad75e193320ed96f36301acf3157bec35 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Mon, 21 Sep 2009 21:46:30 -0800 Subject: [PATCH] Now actually export the line styles, for PDF, EPS, and SVG file formats, with the proper color and width. This may need a bit of cleanup for stuff like the hidden line removal, which currently loses the style. Also fix a bug in the test for arcs of a circle. A second-order Bezier with collinear control points really is an arc, but it's an arc with infinite radius so stuff tends to blow up. So return false for that one. [git-p4: depot-paths = "//depot/solvespace/": change = 2030] --- drawconstraint.cpp | 2 +- drawentity.cpp | 16 +++++- export.cpp | 40 +++++++++++---- exportvector.cpp | 118 ++++++++++++++++++++++++++++++--------------- group.cpp | 5 ++ polygon.cpp | 8 +-- sketch.h | 2 + solvespace.cpp | 4 ++ solvespace.h | 46 +++++++++++------- srf/curve.cpp | 7 +++ srf/surface.h | 2 + style.cpp | 66 +++++++++++++++++++++++++ textscreens.cpp | 12 ++++- textwin.cpp | 31 ++++++++++++ ui.h | 2 + wishlist.txt | 3 +- 16 files changed, 285 insertions(+), 79 deletions(-) diff --git a/drawconstraint.cpp b/drawconstraint.cpp index 49b22c0..0cc035c 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -20,7 +20,7 @@ bool Constraint::HasLabel(void) { void Constraint::LineDrawOrGetDistance(Vector a, Vector b) { if(dogd.drawing) { if(dogd.sel) { - dogd.sel->AddEdge(a, b); + dogd.sel->AddEdge(a, b, Style::CONSTRAINT); } else { glBegin(GL_LINE_STRIP); glxVertex3v(a); diff --git a/drawentity.cpp b/drawentity.cpp index 19360a6..819984f 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -179,7 +179,7 @@ void Entity::GenerateEdges(SEdgeList *el, bool includingConstruction) { ZERO(&lv); sb->MakePwlInto(&lv); for(j = 1; j < lv.n; j++) { - el->AddEdge(lv.elem[j-1], lv.elem[j]); + el->AddEdge(lv.elem[j-1], lv.elem[j], style.v); } lv.Clear(); } @@ -230,6 +230,11 @@ bool Entity::IsVisible(void) { } } + if(style.v) { + Style *s = Style::Get(style); + if(!s->visible) return false; + } + if(forceHidden) return false; return true; @@ -259,9 +264,11 @@ bool Entity::PointIsFromReferences(void) { return h.request().IsFromReferences(); } -void Entity::GenerateBezierCurves(SBezierList *sbl) { +void Entity::GenerateBezierCurves(SBezierList *sbl) { SBezier sb; + int i = sbl->l.n; + switch(type) { case LINE_SEGMENT: { Vector a = SK.GetEntity(point[0])->PointGetNum(); @@ -357,6 +364,11 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { // Not a problem, points and normals and such don't generate curves break; } + + // Record our style for all of the Beziers that we just created. + for(; i < sbl->l.n; i++) { + sbl->l.elem[i].auxA = style.v; + } } void Entity::DrawOrGetDistance(void) { diff --git a/export.cpp b/export.cpp index 4d1af4a..bf12d81 100644 --- a/export.cpp +++ b/export.cpp @@ -82,6 +82,16 @@ void SolveSpace::ExportSectionTo(char *filename) { &el, (SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl); + // All of these are solid model edges, so use the appropriate style. + SEdge *se; + for(se = el.l.First(); se; se = el.l.NextAfter(se)) { + se->auxA = Style::SOLID_EDGE; + } + SBezier *sb; + for(sb = bl.l.First(); sb; sb = bl.l.NextAfter(sb)) { + sb->auxA = Style::SOLID_EDGE; + } + el.CullExtraneousEdges(); bl.CullIdenticalBeziers(); @@ -137,7 +147,7 @@ void SolveSpace::ExportViewTo(char *filename) { SEdgeList *selr = &(g->displayEdges); SEdge *se; for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) { - edges.AddEdge(se->a, se->b); + edges.AddEdge(se->a, se->b, Style::SOLID_EDGE); } } @@ -406,30 +416,40 @@ void VectorFileWriter::Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm) { } if(sel) { for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { - LineSegment(e->a.x, e->a.y, e->b.x, e->b.y); + if(!Style::Exportable(e->auxA)) continue; + + DWORD rgb = Style::Color (e->auxA, true); + double w = Style::WidthMm(e->auxA); + LineSegment(rgb, w, e->a.x, e->a.y, e->b.x, e->b.y); } } if(sbl) { for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { - Bezier(b); + if(!Style::Exportable(b->auxA)) continue; + + DWORD rgb = Style::Color (b->auxA, true); + double w = Style::WidthMm(b->auxA); + Bezier(rgb, w, b); } } FinishAndCloseFile(); } -void VectorFileWriter::BezierAsPwl(SBezier *sb) { +void VectorFileWriter::BezierAsPwl(DWORD rgb, double width, SBezier *sb) { List lv; ZERO(&lv); sb->MakePwlInto(&lv); int i; for(i = 1; i < lv.n; i++) { - LineSegment(lv.elem[i-1].x, lv.elem[i-1].y, - lv.elem[i ].x, lv.elem[i ].y); + LineSegment(rgb, width, lv.elem[i-1].x, lv.elem[i-1].y, + lv.elem[i ].x, lv.elem[i ].y); } lv.Clear(); } -void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { +void VectorFileWriter::BezierAsNonrationalCubic(DWORD rgb, double width, + 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. @@ -457,12 +477,12 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { } if(closeEnough || depth > 3) { - Bezier(&bnr); + Bezier(rgb, width, &bnr); } else { SBezier bef, aft; sb->SplitAt(0.5, &bef, &aft); - BezierAsNonrationalCubic(&bef, depth+1); - BezierAsNonrationalCubic(&aft, depth+1); + BezierAsNonrationalCubic(rgb, width, &bef, depth+1); + BezierAsNonrationalCubic(rgb, width, &aft, depth+1); } } diff --git a/exportvector.cpp b/exportvector.cpp index 6f054da..fe6632c 100644 --- a/exportvector.cpp +++ b/exportvector.cpp @@ -62,7 +62,9 @@ void DxfFileWriter::StartFile(void) { "ENTITIES\r\n"); } -void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void DxfFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ fprintf(f, " 0\r\n" "LINE\r\n" @@ -88,7 +90,7 @@ void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { void DxfFileWriter::Triangle(STriangle *tr) { } -void DxfFileWriter::Bezier(SBezier *sb) { +void DxfFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { Vector c, n = Vector::From(0, 0, 1); double r; if(sb->IsCircle(n, &c, &r)) { @@ -121,7 +123,7 @@ void DxfFileWriter::Bezier(SBezier *sb) { r, theta0*180/PI, theta1*180/PI); } else { - BezierAsPwl(sb); + BezierAsPwl(rgb, w, sb); } } @@ -137,6 +139,17 @@ 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" @@ -156,16 +169,18 @@ void EpsFileWriter::StartFile(void) { MmToPts(ptMax.y - ptMin.y)); } -void EpsFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void EpsFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ fprintf(f, "newpath\r\n" " %.3f %.3f moveto\r\n" " %.3f %.3f lineto\r\n" -" 1 setlinewidth\r\n" -" 0 setgray\r\n" +"%s" "stroke\r\n", MmToPts(x0 - ptMin.x), MmToPts(y0 - ptMin.y), - MmToPts(x1 - ptMin.x), MmToPts(y1 - ptMin.y)); + MmToPts(x1 - ptMin.x), MmToPts(y1 - ptMin.y), + StyleString(rgb, w)); } void EpsFileWriter::Triangle(STriangle *tr) { @@ -200,7 +215,7 @@ void EpsFileWriter::Triangle(STriangle *tr) { MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y)); } -void EpsFileWriter::Bezier(SBezier *sb) { +void EpsFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { Vector c, n = Vector::From(0, 0, 1); double r; if(sb->IsCircle(n, &c, &r)) { @@ -216,27 +231,27 @@ void EpsFileWriter::Bezier(SBezier *sb) { "newpath\r\n" " %.3f %.3f moveto\r\n" " %.3f %.3f %.3f %.3f %.3f arc\r\n" -" 1 setlinewidth\r\n" -" 0 setgray\r\n" +"%s" "stroke\r\n", MmToPts(p0.x - ptMin.x), MmToPts(p0.y - ptMin.y), MmToPts(c.x - ptMin.x), MmToPts(c.y - ptMin.y), MmToPts(r), - theta0*180/PI, theta1*180/PI); + theta0*180/PI, theta1*180/PI, + StyleString(rgb, w)); } else if(sb->deg == 3 && !sb->IsRational()) { fprintf(f, "newpath\r\n" " %.3f %.3f moveto\r\n" " %.3f %.3f %.3f %.3f %.3f %.3f curveto\r\n" -" 1 setlinewidth\r\n" -" 0 setgray\r\n" +"%s" "stroke\r\n", MmToPts(sb->ctrl[0].x - ptMin.x), MmToPts(sb->ctrl[0].y - ptMin.y), 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)); + MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y), + StyleString(rgb, w)); } else { - BezierAsNonrationalCubic(sb); + BezierAsNonrationalCubic(rgb, w, sb); } } @@ -252,6 +267,16 @@ 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" @@ -371,12 +396,15 @@ void PdfFileWriter::FinishAndCloseFile(void) { } -void PdfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void PdfFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ fprintf(f, -"1 w 0 0 0 RG\r\n" +"%s" "%.3f %.3f m\r\n" "%.3f %.3f l\r\n" "S\r\n", + StyleString(rgb, w), MmToPts(x0 - ptMin.x), MmToPts(y0 - ptMin.y), MmToPts(x1 - ptMin.x), MmToPts(y1 - ptMin.y)); } @@ -400,29 +428,34 @@ void PdfFileWriter::Triangle(STriangle *tr) { MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y)); } -void PdfFileWriter::Bezier(SBezier *sb) { +void PdfFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { if(sb->deg == 3 && !sb->IsRational()) { fprintf(f, -"1 w 0 0 0 RG\r\n" +"%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), 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(sb); + BezierAsNonrationalCubic(rgb, w, sb); } } - //----------------------------------------------------------------------------- // Routines for SVG output //----------------------------------------------------------------------------- - -const char *SvgFileWriter::SVG_STYLE = - "stroke-width='0.1' stroke='black' style='fill: none;'"; +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, @@ -440,13 +473,15 @@ void SvgFileWriter::StartFile(void) { // A little bit of extra space for the stroke width. } -void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void SvgFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ // SVG uses a coordinate system with the origin at top left, +y down fprintf(f, "\r\n", (x0 - ptMin.x), (ptMax.y - y0), (x1 - ptMin.x), (ptMax.y - y1), - SVG_STYLE); + StyleString(rgb, w)); } void SvgFileWriter::Triangle(STriangle *tr) { @@ -466,7 +501,7 @@ void SvgFileWriter::Triangle(STriangle *tr) { RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color)); } -void SvgFileWriter::Bezier(SBezier *sb) { +void SvgFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { Vector c, n = Vector::From(0, 0, 1); double r; if(sb->IsCircle(n, &c, &r)) { @@ -487,11 +522,11 @@ void SvgFileWriter::Bezier(SBezier *sb) { p0.x - ptMin.x, ptMax.y - p0.y, r, r, p1.x - ptMin.x, ptMax.y - p1.y, - SVG_STYLE); + StyleString(rgb, w)); } else if(!sb->IsRational()) { if(sb->deg == 1) { - LineSegment(sb->ctrl[0].x, sb->ctrl[0].y, - sb->ctrl[1].x, sb->ctrl[1].y); + LineSegment(rgb, w, sb->ctrl[0].x, sb->ctrl[0].y, + sb->ctrl[1].x, sb->ctrl[1].y); } else if(sb->deg == 2) { fprintf(f, "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, - SVG_STYLE); + StyleString(rgb, w)); } } else { - BezierAsNonrationalCubic(sb); - + BezierAsNonrationalCubic(rgb, w, sb); } } @@ -533,7 +567,9 @@ void HpglFileWriter::StartFile(void) { fprintf(f, "SP1;\r\n"); } -void HpglFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void HpglFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ fprintf(f, "PU%d,%d;\r\n", (int)MmToHpglUnits(x0), (int)MmToHpglUnits(y0)); fprintf(f, "PD%d,%d;\r\n", (int)MmToHpglUnits(x1), (int)MmToHpglUnits(y1)); } @@ -541,8 +577,8 @@ void HpglFileWriter::LineSegment(double x0, double y0, double x1, double y1) { void HpglFileWriter::Triangle(STriangle *tr) { } -void HpglFileWriter::Bezier(SBezier *sb) { - BezierAsPwl(sb); +void HpglFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { + BezierAsPwl(rgb, w, sb); } void HpglFileWriter::FinishAndCloseFile(void) { @@ -562,13 +598,15 @@ void Step2dFileWriter::StartFile(void) { void Step2dFileWriter::Triangle(STriangle *tr) { } -void Step2dFileWriter::LineSegment(double x0, double y0, double x1, double y1) { +void Step2dFileWriter::LineSegment(DWORD rgb, double w, + double x0, double y0, double x1, double y1) +{ SBezier sb = SBezier::From(Vector::From(x0, y0, 0), Vector::From(x1, y1, 0)); - Bezier(&sb); + Bezier(rgb, w, &sb); } -void Step2dFileWriter::Bezier(SBezier *sb) { +void Step2dFileWriter::Bezier(DWORD rgb, double w, SBezier *sb) { int c = sfw.ExportCurve(sb); sfw.curves.Add(&c); } diff --git a/group.cpp b/group.cpp index aeb1c24..85aef48 100644 --- a/group.cpp +++ b/group.cpp @@ -579,6 +579,8 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { en.point[0] = Remap(ep->h, REMAP_TOP); en.point[1] = Remap(ep->h, REMAP_BOTTOM); en.group = h; + en.construction = ep->construction; + en.style = ep->style; en.h = Remap(ep->h, REMAP_PT_TO_LINE); en.type = Entity::LINE_SEGMENT; el->Add(&en); @@ -596,6 +598,8 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { en.numNormal = Quaternion::From(0, ab.x, ab.y, ab.z); en.group = h; + en.construction = ep->construction; + en.style = ep->style; en.h = Remap(ep->h, REMAP_LINE_TO_FACE); en.type = Entity::FACE_XPROD; el->Add(&en); @@ -636,6 +640,7 @@ void Group::CopyEntity(IdList *el, en.timesApplied = timesApplied; en.group = h; en.construction = ep->construction; + en.style = ep->style; switch(ep->type) { case Entity::WORKPLANE: diff --git a/polygon.cpp b/polygon.cpp index b1ce9f0..9f1bafd 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -353,12 +353,8 @@ void SContour::AddPoint(Vector p) { void SContour::MakeEdgesInto(SEdgeList *el) { int i; - for(i = 0; i < (l.n-1); i++) { - SEdge e; - e.tag = 0; - e.a = l.elem[i].p; - e.b = l.elem[i+1].p; - el->l.Add(&e); + for(i = 0; i < (l.n - 1); i++) { + el->AddEdge(l.elem[i].p, l.elem[i+1].p); } } diff --git a/sketch.h b/sketch.h index 5eb23a1..6f68992 100644 --- a/sketch.h +++ b/sketch.h @@ -659,6 +659,8 @@ public: static float Width(hStyle hs); static DWORD Color(int hs, bool forExport=false); static float Width(int hs); + static double WidthMm(int hs); + static bool Exportable(int hs); static hStyle ForEntity(hEntity he); char *DescriptionString(void); diff --git a/solvespace.cpp b/solvespace.cpp index 579bc60..9c24c62 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -68,6 +68,8 @@ void SolveSpace::Init(char *cmdLine) { exportScale = CnfThawFloat(1.0f, "ExportScale"); // Export offset (cutter radius comp) exportOffset = CnfThawFloat(0.0f, "ExportOffset"); + // Rewrite exported colors close to white into black (assuming white bg) + fixExportColors = CnfThawDWORD(1, "FixExportColors"); // Draw back faces of triangles (when mesh is leaky/self-intersecting) drawBackFaces = CnfThawDWORD(1, "DrawBackFaces"); // Export shaded triangles in a 2d view @@ -152,6 +154,8 @@ void SolveSpace::Exit(void) { CnfFreezeFloat(exportScale, "ExportScale"); // Export offset (cutter radius comp) CnfFreezeFloat(exportOffset, "ExportOffset"); + // Rewrite exported colors close to white into black (assuming white bg) + CnfFreezeDWORD(fixExportColors, "FixExportColors"); // Draw back faces of triangles (when mesh is leaky/self-intersecting) CnfFreezeDWORD(drawBackFaces, "DrawBackFaces"); // Export shaded triangles in a 2d view diff --git a/solvespace.h b/solvespace.h index 1adba2c..f4c0c5d 100644 --- a/solvespace.h +++ b/solvespace.h @@ -395,28 +395,34 @@ public: static VectorFileWriter *ForFile(char *file); void Output(SEdgeList *sel, SBezierList *sbl, SMesh *sm); - void BezierAsPwl(SBezier *sb); - void BezierAsNonrationalCubic(SBezier *sb, int depth=0); - virtual void Bezier(SBezier *sb) = 0; - virtual void LineSegment(double x0, double y0, double x1, double y1) = 0; + void BezierAsPwl(DWORD rgb, double width, SBezier *sb); + void BezierAsNonrationalCubic(DWORD rgb, double width, + SBezier *sb, int depth=0); + + virtual void Bezier(DWORD rgb, double width, SBezier *sb) = 0; + virtual void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1) = 0; virtual void Triangle(STriangle *tr) = 0; virtual void StartFile(void) = 0; virtual void FinishAndCloseFile(void) = 0; }; class DxfFileWriter : public VectorFileWriter { public: - void LineSegment(double x0, double y0, double x1, double y1); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class EpsFileWriter : public VectorFileWriter { public: - void LineSegment(double x0, double y0, double x1, double y1); + static char *StyleString(DWORD rgb, double width); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -425,35 +431,40 @@ public: DWORD xref[10]; DWORD bodyStart; - void LineSegment(double x0, double y0, double x1, double y1); + static char *StyleString(DWORD rgb, double width); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class SvgFileWriter : public VectorFileWriter { public: - static const char *SVG_STYLE; - void LineSegment(double x0, double y0, double x1, double y1); + static char *StyleString(DWORD rgb, double width); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class HpglFileWriter : public VectorFileWriter { public: static double MmToHpglUnits(double mm); - void LineSegment(double x0, double y0, double x1, double y1); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; class Step2dFileWriter : public VectorFileWriter { StepFileWriter sfw; - void LineSegment(double x0, double y0, double x1, double y1); + void LineSegment(DWORD rgb, double width, + double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); - void Bezier(SBezier *sb); + void Bezier(DWORD rgb, double width, SBezier *sb); void StartFile(void); void FinishAndCloseFile(void); }; @@ -530,6 +541,7 @@ public: double cameraTangent; float exportScale; float exportOffset; + int fixExportColors; int drawBackFaces; int showToolbar; DWORD backgroundColor; diff --git a/srf/curve.cpp b/srf/curve.cpp index 5c9166f..81d4cc6 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -104,6 +104,13 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) { bool SBezier::IsCircle(Vector axis, Vector *center, double *r) { if(deg != 2) return false; + if(ctrl[1].DistanceToLine(ctrl[0], ctrl[2].Minus(ctrl[0])) < LENGTH_EPS) { + // This is almost a line segment. So it's a circle with very large + // radius, which is likely to make code that tries to handle circles + // blow up. So return false. + return false; + } + Vector t0 = (ctrl[0]).Minus(ctrl[1]), t2 = (ctrl[2]).Minus(ctrl[1]), r0 = axis.Cross(t0), diff --git a/srf/surface.h b/srf/surface.h index 99f252c..764292c 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -62,6 +62,8 @@ public: class SBezier { public: int tag; + int auxA, auxB; + int deg; Vector ctrl[4]; double weight[4]; diff --git a/style.cpp b/style.cpp index 2f0c178..2f4f3ef 100644 --- a/style.cpp +++ b/style.cpp @@ -111,6 +111,8 @@ void Style::FreezeDefaultStyles(void) { // the style, according to our table of default styles. //----------------------------------------------------------------------------- Style *Style::Get(hStyle h) { + if(h.v == 0) h.v = ACTIVE_GRP; + Style *s = SK.style.FindByIdNoOops(h); if(s) { // It exists, good. @@ -140,6 +142,19 @@ float Style::Width(int s) { //----------------------------------------------------------------------------- DWORD Style::Color(hStyle h, bool forExport) { Style *s = Get(h); + if(forExport) { + Vector rgb = Vector::From(REDf(s->color), + GREENf(s->color), + BLUEf(s->color)); + rgb = rgb.Minus(Vector::From(1, 1, 1)); + if(rgb.Magnitude() < 0.4 && SS.fixExportColors) { + // This is an almost-white color in a default style, which is + // good for the default on-screen view (black bg) but probably + // not desired in the exported files, which typically are shown + // against white backgrounds. + return RGB(0, 0, 0); + } + } return s->color; } @@ -158,6 +173,24 @@ float Style::Width(hStyle h) { return (float)r; } +//----------------------------------------------------------------------------- +// Return the width associated with our style in millimeters.. +//----------------------------------------------------------------------------- +double Style::WidthMm(int hs) { + double widthpx = Width(hs); + return widthpx / SS.GW.scale; +} + +//----------------------------------------------------------------------------- +// Should lines and curves from this style appear in the output file? Only +// if it's both shown and exportable. +//----------------------------------------------------------------------------- +bool Style::Exportable(int si) { + hStyle hs = { si }; + Style *s = Get(hs); + return (s->exportable) && (s->visible); +} + //----------------------------------------------------------------------------- // Return the appropriate style for our entity. If the entity has a style // explicitly assigned, then it's that style. Otherwise it's the appropriate @@ -432,6 +465,39 @@ void TextWindow::ShowStyleInfo(void) { s->h.v, &ScreenChangeStyleYesNo, (!s->exportable ? "" : "no"), (!s->exportable ? "no" : "")); + + Printf(false, ""); + Printf(false, "To assign lines or curves to this style,"); + Printf(false, "select them on the drawing. Then commit"); + Printf(false, "by clicking the link at the bottom of"); + Printf(false, "this window."); } } +void TextWindow::ScreenAssignSelectionToStyle(int link, DWORD v) { + bool showError = false; + SS.GW.GroupSelection(); + + SS.UndoRemember(); + for(int i = 0; i < SS.GW.gs.entities; i++) { + hEntity he = SS.GW.gs.entity[i]; + if(!he.isFromRequest()) { + showError = true; + continue; + } + + hRequest hr = he.request(); + Request *r = SK.GetRequest(hr); + r->style.v = v; + SS.later.generateAll = true; + } + + if(showError) { + Error("Can't assign style to an entity that's derived from another " + "entity; try assigning a style to this entity's parent."); + } + + SS.GW.ClearSelection(); + InvalidateGraphics(); +} + diff --git a/textscreens.cpp b/textscreens.cpp index 53125f6..bd1898d 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -636,6 +636,9 @@ void TextWindow::ScreenChangeExportOffset(int link, DWORD v) { ShowTextEditControl(57, 3, SS.MmToString(SS.exportOffset)); SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; } +void TextWindow::ScreenChangeFixExportColors(int link, DWORD v) { + SS.fixExportColors = !SS.fixExportColors; +} void TextWindow::ScreenChangeBackFaces(int link, DWORD v) { SS.drawBackFaces = !SS.drawBackFaces; InvalidateGraphics(); @@ -786,7 +789,14 @@ void TextWindow::ShowConfiguration(void) { } Printf(false, ""); - Printf(false, "%Ft draw back faces: " + Printf(false, "%Ft fix white exported lines: " + "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &ScreenChangeFixExportColors, + ( SS.fixExportColors ? "" : "yes"), ( SS.fixExportColors ? "yes" : ""), + &ScreenChangeFixExportColors, + (!SS.fixExportColors ? "" : "no"), (!SS.fixExportColors ? "no" : "")); + + Printf(false, "%Ft draw triangle back faces: " "%Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", &ScreenChangeBackFaces, (SS.drawBackFaces ? "" : "yes"), (SS.drawBackFaces ? "yes" : ""), diff --git a/textwin.cpp b/textwin.cpp index 2ae8bd1..0cc265d 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -414,6 +414,12 @@ void TextWindow::DescribeSelection(void) { Entity *w = SK.GetEntity(e->workplane); Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString()); } + if(e->style.v) { + Style *s = Style::Get(e->style); + Printf(false, "%FtIN STYLE%E %s", s->DescriptionString()); + } else { + Printf(false, "%FtIN STYLE%E none"); + } } else if(gs.n == 2 && gs.points == 2) { Printf(false, "%FtTWO POINTS"); Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum(); @@ -481,6 +487,31 @@ void TextWindow::DescribeSelection(void) { Printf(true, "%FtSELECTED:%E %d item%s", gs.n, gs.n == 1 ? "" : "s"); } + if(shown.screen == SCREEN_STYLE_INFO && + shown.style.v >= Style::FIRST_CUSTOM) + { + // If we are showing a screen for a particular style, then offer the + // option to assign our selected entities to that style. + Style *s = Style::Get(shown.style); + Printf(true, "%Fl%D%f%Ll(assign to style %s)%E", + shown.style.v, + &ScreenAssignSelectionToStyle, + s->DescriptionString()); + } + // If any of the selected entities have an assigned style, then offer + // the option to remove that style. + for(i = 0; i < gs.entities; i++) { + Entity *e = SK.GetEntity(gs.entity[i]); + if(e->style.v != 0) { + break; + } + } + if(i < gs.entities) { + Printf(true, "%Fl%D%f%Ll(remove assigned style)%E", + 0, + &ScreenAssignSelectionToStyle); + } + Printf(true, "%Fl%f%Ll(unselect all)%E", &TextWindow::ScreenUnselectAll); } diff --git a/ui.h b/ui.h index 32cc89f..48beb91 100644 --- a/ui.h +++ b/ui.h @@ -159,10 +159,12 @@ public: static void ScreenChangeStyleYesNo(int link, DWORD v); static void ScreenCreateCustomStyle(int link, DWORD v); static void ScreenLoadFactoryDefaultStyles(int link, DWORD v); + static void ScreenAssignSelectionToStyle(int link, DWORD v); static void ScreenShowConfiguration(int link, DWORD v); static void ScreenGoToWebsite(int link, DWORD v); + static void ScreenChangeFixExportColors(int link, DWORD v); static void ScreenChangeBackFaces(int link, DWORD v); static void ScreenChangePwlCurves(int link, DWORD v); static void ScreenChangeCanvasSizeAuto(int link, DWORD v); diff --git a/wishlist.txt b/wishlist.txt index 8d564aa..c362bb6 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,8 +1,7 @@ grid -line styles (color, thickness) -background color setting better text +right-click menu faster triangulation interpolating splines