diff --git a/export.cpp b/export.cpp index 603d08c7..599d42de 100644 --- a/export.cpp +++ b/export.cpp @@ -71,6 +71,7 @@ void SolveSpace::ExportSectionTo(char *filename) { &el, SS.exportPwlCurves ? NULL : &bl); el.CullExtraneousEdges(); + bl.CullIdenticalBeziers(); // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); @@ -95,6 +96,9 @@ void SolveSpace::ExportViewTo(char *filename) { if(SS.GW.showShaded) { sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); } + if(sm->l.n == 0) { + sm = NULL; + } for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); @@ -368,7 +372,6 @@ void VectorFileWriter::BezierAsPwl(SBezier *sb) { lv.Clear(); } - //----------------------------------------------------------------------------- // Routines for DXF export //----------------------------------------------------------------------------- @@ -386,6 +389,18 @@ void DxfFileWriter::StartFile(void) { " 1\r\n" "AC1006\r\n" " 9\r\n" +"$ANGDIR\r\n" +" 70\r\n" +"0\r\n" +" 9\r\n" +"$AUNITS\r\n" +" 70\r\n" +"0\r\n" +" 9\r\n" +"$AUPREC\r\n" +" 70\r\n" +"0\r\n" +" 9\r\n" "$INSBASE\r\n" " 10\r\n" "0.0\r\n" @@ -443,7 +458,40 @@ void DxfFileWriter::Triangle(STriangle *tr) { } void DxfFileWriter::Bezier(SBezier *sb) { - BezierAsPwl(sb); + Vector c, n = Vector::From(0, 0, 1); + double r; + if(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); + if(dtheta < 0) { + SWAP(double, theta0, theta1); + } + + fprintf(f, +" 0\r\n" +"ARC\r\n" +" 8\r\n" // Layer code +"%d\r\n" +" 10\r\n" // x +"%.6f\r\n" +" 20\r\n" // y +"%.6f\r\n" +" 30\r\n" // z +"%.6f\r\n" +" 40\r\n" // radius +"%.6f\r\n" +" 50\r\n" // start angle +"%.6f\r\n" +" 51\r\n" // end angle +"%.6f\r\n", + 0, + c.x, c.y, 0.0, + r, + theta0*180/PI, theta1*180/PI); + } else { + BezierAsPwl(sb); + } } void DxfFileWriter::FinishAndCloseFile(void) { @@ -527,7 +575,31 @@ void EpsFileWriter::Triangle(STriangle *tr) { } void EpsFileWriter::Bezier(SBezier *sb) { - BezierAsPwl(sb); + Vector c, n = Vector::From(0, 0, 1); + double r; + 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), + dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI); + if(dtheta < 0) { + SWAP(double, theta0, theta1); + SWAP(Vector, p0, p1); + } + fprintf(f, +"newpath\r\n" +" %.3f %.3f moveto\r\n" +" %.3f %.3f %.3f %.3f %.3f arc\r\n" +" 1 setlinewidth\r\n" +" 0 setgray\r\n" +"stroke\r\n", + MmToPoints(p0.x - ptMin.x), MmToPoints(p0.y - ptMin.y), + MmToPoints(c.x - ptMin.x), MmToPoints(c.y - ptMin.y), + MmToPoints(r), + theta0*180/PI, theta1*180/PI); + } else { + BezierAsPwl(sb); + } } void EpsFileWriter::FinishAndCloseFile(void) { @@ -541,6 +613,10 @@ void EpsFileWriter::FinishAndCloseFile(void) { //----------------------------------------------------------------------------- // Routines for SVG output //----------------------------------------------------------------------------- + +const char *SvgFileWriter::SVG_STYLE = + "stroke-width='1' stroke='black' style='fill: none;'"; + void SvgFileWriter::StartFile(void) { fprintf(f, "\r\n", +"\r\n", (x0 - ptMin.x), (ptMax.y - y0), - (x1 - ptMin.x), (ptMax.y - y1)); + (x1 - ptMin.x), (ptMax.y - y1), + SVG_STYLE); } void SvgFileWriter::Triangle(STriangle *tr) { @@ -583,7 +659,52 @@ void SvgFileWriter::Triangle(STriangle *tr) { } void SvgFileWriter::Bezier(SBezier *sb) { - BezierAsPwl(sb); + Vector c, n = Vector::From(0, 0, 1); + double r; + 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), + dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI); + // The arc must be less than 180 degrees, or else it couldn't have + // been represented as a single rational Bezier. And arrange it + // to run counter-clockwise, which corresponds to clockwise in + // SVG's mirrored coordinate system. + 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, + SVG_STYLE); + } 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); + } else if(sb->deg == 2) { + fprintf(f, +"\r\n", + sb->ctrl[0].x - ptMin.x, ptMax.y - sb->ctrl[0].y, + sb->ctrl[1].x - ptMin.x, ptMax.y - sb->ctrl[1].y, + sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y, + SVG_STYLE); + } else if(sb->deg == 3) { + fprintf(f, +"\r\n", + sb->ctrl[0].x - ptMin.x, ptMax.y - sb->ctrl[0].y, + 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, + SVG_STYLE); + } + } else { + BezierAsPwl(sb); + } } void SvgFileWriter::FinishAndCloseFile(void) { diff --git a/solvespace.h b/solvespace.h index 843ab326..169ee4e1 100644 --- a/solvespace.h +++ b/solvespace.h @@ -378,6 +378,7 @@ public: }; class SvgFileWriter : public VectorFileWriter { public: + static const char *SVG_STYLE; void LineSegment(double x0, double y0, double x1, double y1); void Triangle(STriangle *tr); void Bezier(SBezier *sb); diff --git a/srf/curve.cpp b/srf/curve.cpp index 21f0542b..833510a9 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -97,6 +97,59 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) { return ret; } +//----------------------------------------------------------------------------- +// Is this Bezier exactly the arc of a circle, projected along the specified +// axis? If yes, return that circle's center and radius. +//----------------------------------------------------------------------------- +bool SBezier::IsCircle(Vector axis, Vector *center, double *r) { + if(deg != 2) return false; + + Vector t0 = (ctrl[0]).Minus(ctrl[1]), + t2 = (ctrl[2]).Minus(ctrl[1]), + r0 = axis.Cross(t0), + r2 = axis.Cross(t2); + + *center = Vector::AtIntersectionOfLines(ctrl[0], (ctrl[0]).Plus(r0), + ctrl[2], (ctrl[2]).Plus(r2), + NULL, NULL, NULL); + + double rd0 = center->Minus(ctrl[0]).Magnitude(), + rd2 = center->Minus(ctrl[2]).Magnitude(); + if(fabs(rd0 - rd2) > LENGTH_EPS) { + return false; + } + *r = rd0; + + Vector u = r0.WithMagnitude(1), + v = (axis.Cross(u)).WithMagnitude(1); + Point2d c2 = center->Project2d(u, v), + pa2 = (ctrl[0]).Project2d(u, v).Minus(c2), + pb2 = (ctrl[2]).Project2d(u, v).Minus(c2); + + double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys + thetab = atan2(pb2.y, pb2.x), + dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI); + if(dtheta > PI) { + // Not possible with a second order Bezier arc; so we must have + // the points backwards. + dtheta = 2*PI - dtheta; + } + + if(fabs(weight[1] - cos(dtheta/2)) > LENGTH_EPS) { + return false; + } + + return true; +} + +bool SBezier::IsRational(void) { + int i; + for(i = 0; i <= deg; i++) { + if(fabs(weight[i] - 1) > LENGTH_EPS) return true; + } + return false; +} + //----------------------------------------------------------------------------- // Apply a perspective transformation to a rational Bezier curve, calculating // the new weights as required. @@ -141,6 +194,32 @@ void SBezierList::Clear(void) { l.Clear(); } +//----------------------------------------------------------------------------- +// If our list contains multiple identical Beziers (in either forward or +// reverse order), then cull them. +//----------------------------------------------------------------------------- +void SBezierList::CullIdenticalBeziers(void) { + int i, j; + + l.ClearTags(); + for(i = 0; i < l.n; i++) { + SBezier *bi = &(l.elem[i]), bir; + bir = *bi; + bir.Reverse(); + + for(j = i + 1; j < l.n; j++) { + SBezier *bj = &(l.elem[j]); + if(bj->Equals(bi) || + bj->Equals(&bir)) + { + bi->tag = 1; + bj->tag = 1; + } + } + } + l.RemoveTagged(); +} + SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, bool *allClosed, SEdge *errorAt) diff --git a/srf/surface.cpp b/srf/surface.cpp index 62d2e4de..ca50834c 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -50,51 +50,15 @@ bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) { return true; } -bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r, - Vector *start, Vector *finish) +bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r, + Vector *start, Vector *finish) { SBezier sb; if(!IsExtrusion(&sb, axis)) return false; - if(sb.deg != 2) return false; + if(!sb.IsCircle(*axis, center, r)) return false; - Vector t0 = (sb.ctrl[0]).Minus(sb.ctrl[1]), - t2 = (sb.ctrl[2]).Minus(sb.ctrl[1]), - r0 = axis->Cross(t0), - r2 = axis->Cross(t2); - - *center = Vector::AtIntersectionOfLines(sb.ctrl[0], (sb.ctrl[0]).Plus(r0), - sb.ctrl[2], (sb.ctrl[2]).Plus(r2), - NULL, NULL, NULL); - - double rd0 = center->Minus(sb.ctrl[0]).Magnitude(), - rd2 = center->Minus(sb.ctrl[2]).Magnitude(); - if(fabs(rd0 - rd2) > LENGTH_EPS) { - return false; - } - *r = rd0; - - Vector u = r0.WithMagnitude(1), - v = (axis->Cross(u)).WithMagnitude(1); - Point2d c2 = center->Project2d(u, v), - pa2 = (sb.ctrl[0]).Project2d(u, v).Minus(c2), - pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2); - - double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys - thetab = atan2(pb2.y, pb2.x), - dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI); - if(dtheta > PI) { - // Not possible with a second order Bezier arc; so we must have - // the points backwards. - dtheta = 2*PI - dtheta; - } - - if(fabs(sb.weight[1] - cos(dtheta/2)) > LENGTH_EPS) { - return false; - } - - *start = sb.ctrl[0]; + *start = sb.ctrl[0]; *finish = sb.ctrl[2]; - return true; } diff --git a/srf/surface.h b/srf/surface.h index 4b91e2f6..3003265f 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -73,6 +73,9 @@ public: void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); void Reverse(void); + bool IsCircle(Vector axis, Vector *center, double *r); + bool IsRational(void); + SBezier TransformedBy(Vector t, Quaternion q); SBezier InPerspective(Vector u, Vector v, Vector n, Vector origin, double cameraTan); @@ -90,6 +93,7 @@ public: List l; void Clear(void); + void CullIdenticalBeziers(void); }; class SBezierLoop { @@ -239,8 +243,8 @@ public: bool CoincidentWithPlane(Vector n, double d); bool CoincidentWith(SSurface *ss, bool sameNormal); bool IsExtrusion(SBezier *of, Vector *along); - bool IsCylinder(Vector *center, Vector *axis, double *r, - Vector *start, Vector *finish); + bool IsCylinder(Vector *axis, Vector *center, double *r, + Vector *start, Vector *finish); void TriangulateInto(SShell *shell, SMesh *sm); void MakeTrimEdgesInto(SEdgeList *sel, bool asUv, SCurve *sc, STrimBy *stb); diff --git a/srf/surfinter.cpp b/srf/surfinter.cpp index 751f7ba2..6f4dd4a5 100644 --- a/srf/surfinter.cpp +++ b/srf/surfinter.cpp @@ -469,7 +469,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, ClosestPointTo(p, &(inter.p.x), &(inter.p.y)); inters.Add(&inter); } - } else if(IsCylinder(¢er, &axis, &radius, &start, &finish)) { + } else if(IsCylinder(&axis, ¢er, &radius, &start, &finish)) { // This one can be solved in closed form too. Vector ab = b.Minus(a); if(axis.Cross(ab).Magnitude() < LENGTH_EPS) { diff --git a/wishlist.txt b/wishlist.txt index f2a374fc..98adf3d5 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,11 +1,10 @@ marching algorithm for surface intersection surfaces of revolution (lathed) -cylinder-line special cases boundary avoidance when casting ray for point-in-shell tangent intersections short pwl edge avoidance -exact curve export (at least for dxf) +direct PDF export assembly -----