Add exact export of arcs for EPS, DXF, SVG, and of nonrational

polynomial curves for SVG.

[git-p4: depot-paths = "//depot/solvespace/": change = 1937]
solver
Jonathan Westhues 2009-04-14 18:55:18 -08:00
parent 775653a75d
commit b5c8aade21
7 changed files with 220 additions and 52 deletions

View File

@ -71,6 +71,7 @@ void SolveSpace::ExportSectionTo(char *filename) {
&el, SS.exportPwlCurves ? NULL : &bl); &el, SS.exportPwlCurves ? NULL : &bl);
el.CullExtraneousEdges(); el.CullExtraneousEdges();
bl.CullIdenticalBeziers();
// And write the edges. // And write the edges.
VectorFileWriter *out = VectorFileWriter::ForFile(filename); VectorFileWriter *out = VectorFileWriter::ForFile(filename);
@ -95,6 +96,9 @@ void SolveSpace::ExportViewTo(char *filename) {
if(SS.GW.showShaded) { if(SS.GW.showShaded) {
sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh);
} }
if(sm->l.n == 0) {
sm = NULL;
}
for(i = 0; i < SS.entity.n; i++) { for(i = 0; i < SS.entity.n; i++) {
Entity *e = &(SS.entity.elem[i]); Entity *e = &(SS.entity.elem[i]);
@ -368,7 +372,6 @@ void VectorFileWriter::BezierAsPwl(SBezier *sb) {
lv.Clear(); lv.Clear();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Routines for DXF export // Routines for DXF export
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -386,6 +389,18 @@ void DxfFileWriter::StartFile(void) {
" 1\r\n" " 1\r\n"
"AC1006\r\n" "AC1006\r\n"
" 9\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" "$INSBASE\r\n"
" 10\r\n" " 10\r\n"
"0.0\r\n" "0.0\r\n"
@ -443,8 +458,41 @@ void DxfFileWriter::Triangle(STriangle *tr) {
} }
void DxfFileWriter::Bezier(SBezier *sb) { void DxfFileWriter::Bezier(SBezier *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); BezierAsPwl(sb);
} }
}
void DxfFileWriter::FinishAndCloseFile(void) { void DxfFileWriter::FinishAndCloseFile(void) {
fprintf(f, fprintf(f,
@ -527,8 +575,32 @@ void EpsFileWriter::Triangle(STriangle *tr) {
} }
void EpsFileWriter::Bezier(SBezier *sb) { void EpsFileWriter::Bezier(SBezier *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); BezierAsPwl(sb);
} }
}
void EpsFileWriter::FinishAndCloseFile(void) { void EpsFileWriter::FinishAndCloseFile(void) {
fprintf(f, fprintf(f,
@ -541,6 +613,10 @@ void EpsFileWriter::FinishAndCloseFile(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Routines for SVG output // Routines for SVG output
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
const char *SvgFileWriter::SVG_STYLE =
"stroke-width='1' stroke='black' style='fill: none;'";
void SvgFileWriter::StartFile(void) { void SvgFileWriter::StartFile(void) {
fprintf(f, fprintf(f,
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" " "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" "
@ -559,10 +635,10 @@ void SvgFileWriter::StartFile(void) {
void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) { void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) {
// SVG uses a coordinate system with the origin at top left, +y down // SVG uses a coordinate system with the origin at top left, +y down
fprintf(f, fprintf(f,
"<polyline points='%.3f,%.3f %.3f,%.3f' " "<polyline points='%.3f,%.3f %.3f,%.3f' %s />\r\n",
"stroke-width='1' stroke='black' style='fill: none;' />\r\n",
(x0 - ptMin.x), (ptMax.y - y0), (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) { void SvgFileWriter::Triangle(STriangle *tr) {
@ -583,8 +659,53 @@ void SvgFileWriter::Triangle(STriangle *tr) {
} }
void SvgFileWriter::Bezier(SBezier *sb) { void SvgFileWriter::Bezier(SBezier *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,
"<path d='M%.3f,%.3f "
"A%.3f,%.3f 0 0,0 %.3f,%.3f' %s />\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,
"<path d='M%.3f,%.3f "
"Q%.3f,%.3f %.3f,%.3f' %s />\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,
"<path d='M%.3f,%.3f "
"C%.3f,%.3f %.3f,%.3f %.3f,%.3f' %s />\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); BezierAsPwl(sb);
} }
}
void SvgFileWriter::FinishAndCloseFile(void) { void SvgFileWriter::FinishAndCloseFile(void) {
fprintf(f, "\r\n</svg>\r\n"); fprintf(f, "\r\n</svg>\r\n");

View File

@ -378,6 +378,7 @@ public:
}; };
class SvgFileWriter : public VectorFileWriter { class SvgFileWriter : public VectorFileWriter {
public: public:
static const char *SVG_STYLE;
void LineSegment(double x0, double y0, double x1, double y1); void LineSegment(double x0, double y0, double x1, double y1);
void Triangle(STriangle *tr); void Triangle(STriangle *tr);
void Bezier(SBezier *sb); void Bezier(SBezier *sb);

View File

@ -97,6 +97,59 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
return ret; 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 // Apply a perspective transformation to a rational Bezier curve, calculating
// the new weights as required. // the new weights as required.
@ -141,6 +194,32 @@ void SBezierList::Clear(void) {
l.Clear(); 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, SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl,
bool *allClosed, SEdge *errorAt) bool *allClosed, SEdge *errorAt)

View File

@ -50,51 +50,15 @@ bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) {
return true; return true;
} }
bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r, bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r,
Vector *start, Vector *finish) Vector *start, Vector *finish)
{ {
SBezier sb; SBezier sb;
if(!IsExtrusion(&sb, axis)) return false; 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]; *finish = sb.ctrl[2];
return true; return true;
} }

View File

@ -73,6 +73,9 @@ public:
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax);
void Reverse(void); void Reverse(void);
bool IsCircle(Vector axis, Vector *center, double *r);
bool IsRational(void);
SBezier TransformedBy(Vector t, Quaternion q); SBezier TransformedBy(Vector t, Quaternion q);
SBezier InPerspective(Vector u, Vector v, Vector n, SBezier InPerspective(Vector u, Vector v, Vector n,
Vector origin, double cameraTan); Vector origin, double cameraTan);
@ -90,6 +93,7 @@ public:
List<SBezier> l; List<SBezier> l;
void Clear(void); void Clear(void);
void CullIdenticalBeziers(void);
}; };
class SBezierLoop { class SBezierLoop {
@ -239,7 +243,7 @@ public:
bool CoincidentWithPlane(Vector n, double d); bool CoincidentWithPlane(Vector n, double d);
bool CoincidentWith(SSurface *ss, bool sameNormal); bool CoincidentWith(SSurface *ss, bool sameNormal);
bool IsExtrusion(SBezier *of, Vector *along); bool IsExtrusion(SBezier *of, Vector *along);
bool IsCylinder(Vector *center, Vector *axis, double *r, bool IsCylinder(Vector *axis, Vector *center, double *r,
Vector *start, Vector *finish); Vector *start, Vector *finish);
void TriangulateInto(SShell *shell, SMesh *sm); void TriangulateInto(SShell *shell, SMesh *sm);

View File

@ -469,7 +469,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b,
ClosestPointTo(p, &(inter.p.x), &(inter.p.y)); ClosestPointTo(p, &(inter.p.x), &(inter.p.y));
inters.Add(&inter); inters.Add(&inter);
} }
} else if(IsCylinder(&center, &axis, &radius, &start, &finish)) { } else if(IsCylinder(&axis, &center, &radius, &start, &finish)) {
// This one can be solved in closed form too. // This one can be solved in closed form too.
Vector ab = b.Minus(a); Vector ab = b.Minus(a);
if(axis.Cross(ab).Magnitude() < LENGTH_EPS) { if(axis.Cross(ab).Magnitude() < LENGTH_EPS) {

View File

@ -1,11 +1,10 @@
marching algorithm for surface intersection marching algorithm for surface intersection
surfaces of revolution (lathed) surfaces of revolution (lathed)
cylinder-line special cases
boundary avoidance when casting ray for point-in-shell boundary avoidance when casting ray for point-in-shell
tangent intersections tangent intersections
short pwl edge avoidance short pwl edge avoidance
exact curve export (at least for dxf) direct PDF export
assembly assembly
----- -----