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
-----