diff options
Diffstat (limited to 'hyp2mat/lib')
38 files changed, 11545 insertions, 0 deletions
diff --git a/hyp2mat/lib/.gitignore b/hyp2mat/lib/.gitignore new file mode 100644 index 0000000..17fd190 --- /dev/null +++ b/hyp2mat/lib/.gitignore @@ -0,0 +1,11 @@ +*.lo +*.o +.deps/ +.libs/ +config.h +config.h.in~ +hyp2mat.pc +libhyp2mat.la +parse.cc +parse.h +scan.cc diff --git a/hyp2mat/lib/Makefile.am b/hyp2mat/lib/Makefile.am new file mode 100644 index 0000000..5e1000d --- /dev/null +++ b/hyp2mat/lib/Makefile.am @@ -0,0 +1,69 @@ +# used by automake + +# +# hyp2mat library +# + +# if you wish to install libraries, headers, and package information +if INSTALL_LIBRARY +include_HEADERS=hyp2mat.h +lib_LTLIBRARIES=libhyp2mat.la +pkgconfig_DATA=hyp2mat.pc +else +# if you do not wish to install libraries, headers, and package information +noinst_HEADERS=hyp2mat.h +noinst_LTLIBRARIES=libhyp2mat.la +noinst_DATA=hyp2mat.pc +endif + +pkgconfigdir=$(datarootdir)/pkgconfig + +libhyp2mat_la_SOURCES=\ + clipper.cpp \ + clipper.hpp \ + copper.cc \ + crop.h \ + crop.cc \ + csxcad.cc \ + csxcad.h \ + draw.cc \ + exec_board.cc \ + exec_devices.cc \ + exec_end.cc \ + exec_net.cc \ + exec_netclass.cc \ + exec_padstack.cc \ + exec_polygon.cc \ + exec_stackup.cc \ + exec_supplies.cc \ + hyp2mat.h \ + hyperlynx.cc \ + hyperlynx.h \ + hypfile.cc \ + hypfile.h \ + misc.cc \ + palette.cc \ + palette.h \ + parse_param.h \ + parser.h \ + parse.yy \ + polygon.h \ + polygon.cc \ + pcb.cc \ + pdf.cc \ + pdf.h \ + scan.ll + +EXTRA_DIST=clipper.txt parse.h + +BUILT_SOURCES=parse.cc parse.h scan.cc + +scan.cc: scan.ll parse.h + ${LEX} -o $*.cc $< + +parse.h parse.cc: parse.yy + ${YACC} --output=$*.cc --defines=$*.h $< + +CLEANFILES=parse.cc parse.h scan.cc + +#not truncated diff --git a/hyp2mat/lib/clipper.cpp b/hyp2mat/lib/clipper.cpp new file mode 100644 index 0000000..02a7b70 --- /dev/null +++ b/hyp2mat/lib/clipper.cpp @@ -0,0 +1,3700 @@ +/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 5.1.6 *
+* Date : 23 May 2013 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2013 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+* Attributions: *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping" *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
+* http://portal.acm.org/citation.cfm?id=129906 *
+* *
+* Computer graphics and geometric modeling: implementation and algorithms *
+* By Max K. Agoston *
+* Springer; 1 edition (January 4, 2005) *
+* http://books.google.com/books?q=vatti+clipping+agoston *
+* *
+* See also: *
+* "Polygon Offsetting by Computing Winding Numbers" *
+* Paper no. DETC2005-85513 pp. 565-575 *
+* ASME 2005 International Design Engineering Technical Conferences *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
+* September 24-28, 2005 , Long Beach, California, USA *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
+* *
+*******************************************************************************/
+
+/*******************************************************************************
+* *
+* This is a translation of the Delphi Clipper library and the naming style *
+* used has retained a Delphi flavour. *
+* *
+*******************************************************************************/
+
+#include "clipper.hpp"
+#include <cmath>
+#include <vector>
+#include <algorithm>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+
+namespace ClipperLib {
+
+static long64 const loRange = 0x3FFFFFFF;
+static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL;
+static double const pi = 3.141592653589793238;
+enum Direction { dRightToLeft, dLeftToRight };
+
+#define HORIZONTAL (-1.0E+40)
+#define TOLERANCE (1.0e-20)
+#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
+#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b))
+
+const char coords_range_error[] = "Coordinate exceeds range bounds.";
+
+inline long64 Abs(long64 val)
+{
+ return val < 0 ? -val : val;
+}
+
+//------------------------------------------------------------------------------
+// PolyTree methods ...
+//------------------------------------------------------------------------------
+
+void PolyTree::Clear()
+{
+ for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
+ delete AllNodes[i];
+ AllNodes.resize(0);
+ Childs.resize(0);
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyTree::GetFirst() const
+{
+ if (!Childs.empty())
+ return Childs[0];
+ else
+ return 0;
+}
+//------------------------------------------------------------------------------
+
+int PolyTree::Total() const
+{
+ return AllNodes.size();
+}
+
+//------------------------------------------------------------------------------
+// PolyNode methods ...
+//------------------------------------------------------------------------------
+
+PolyNode::PolyNode(): Childs(), Parent(0), Index(0)
+{
+}
+//------------------------------------------------------------------------------
+
+int PolyNode::ChildCount() const
+{
+ return Childs.size();
+}
+//------------------------------------------------------------------------------
+
+void PolyNode::AddChild(PolyNode& child)
+{
+ unsigned cnt = Childs.size();
+ Childs.push_back(&child);
+ child.Parent = this;
+ child.Index = cnt;
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyNode::GetNext() const
+{
+ if (!Childs.empty())
+ return Childs[0];
+ else
+ return GetNextSiblingUp();
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyNode::GetNextSiblingUp() const
+{
+ if (!Parent) //protects against PolyTree.GetNextSiblingUp()
+ return 0;
+ else if (Index == Parent->Childs.size() - 1)
+ return Parent->GetNextSiblingUp();
+ else
+ return Parent->Childs[Index + 1];
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsHole() const
+{
+ bool result = true;
+ PolyNode* node = Parent;
+ while (node)
+ {
+ result = !result;
+ node = node->Parent;
+ }
+ return result;
+}
+
+//------------------------------------------------------------------------------
+// Int128 class (enables safe math on signed 64bit integers)
+// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
+// Int128 val2((long64)9223372036854775807);
+// Int128 val3 = val1 * val2;
+// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
+//------------------------------------------------------------------------------
+
+class Int128
+{
+ public:
+
+ ulong64 lo;
+ long64 hi;
+
+ Int128(long64 _lo = 0)
+ {
+ lo = (ulong64)_lo;
+ if (_lo < 0) hi = -1; else hi = 0;
+ }
+
+
+ Int128(const Int128 &val): lo(val.lo), hi(val.hi){}
+
+ Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){}
+
+ long64 operator = (const long64 &val)
+ {
+ lo = (ulong64)val;
+ if (val < 0) hi = -1; else hi = 0;
+ return val;
+ }
+
+ bool operator == (const Int128 &val) const
+ {return (hi == val.hi && lo == val.lo);}
+
+ bool operator != (const Int128 &val) const
+ { return !(*this == val);}
+
+ bool operator > (const Int128 &val) const
+ {
+ if (hi != val.hi)
+ return hi > val.hi;
+ else
+ return lo > val.lo;
+ }
+
+ bool operator < (const Int128 &val) const
+ {
+ if (hi != val.hi)
+ return hi < val.hi;
+ else
+ return lo < val.lo;
+ }
+
+ bool operator >= (const Int128 &val) const
+ { return !(*this < val);}
+
+ bool operator <= (const Int128 &val) const
+ { return !(*this > val);}
+
+ Int128& operator += (const Int128 &rhs)
+ {
+ hi += rhs.hi;
+ lo += rhs.lo;
+ if (lo < rhs.lo) hi++;
+ return *this;
+ }
+
+ Int128 operator + (const Int128 &rhs) const
+ {
+ Int128 result(*this);
+ result+= rhs;
+ return result;
+ }
+
+ Int128& operator -= (const Int128 &rhs)
+ {
+ *this += -rhs;
+ return *this;
+ }
+
+ Int128 operator - (const Int128 &rhs) const
+ {
+ Int128 result(*this);
+ result -= rhs;
+ return result;
+ }
+
+ Int128 operator-() const //unary negation
+ {
+ if (lo == 0)
+ return Int128(-hi,0);
+ else
+ return Int128(~hi,~lo +1);
+ }
+
+ Int128 operator/ (const Int128 &rhs) const
+ {
+ if (rhs.lo == 0 && rhs.hi == 0)
+ throw "Int128 operator/: divide by zero";
+
+ bool negate = (rhs.hi < 0) != (hi < 0);
+ Int128 dividend = *this;
+ Int128 divisor = rhs;
+ if (dividend.hi < 0) dividend = -dividend;
+ if (divisor.hi < 0) divisor = -divisor;
+
+ if (divisor < dividend)
+ {
+ Int128 result = Int128(0);
+ Int128 cntr = Int128(1);
+ while (divisor.hi >= 0 && !(divisor > dividend))
+ {
+ divisor.hi <<= 1;
+ if ((long64)divisor.lo < 0) divisor.hi++;
+ divisor.lo <<= 1;
+
+ cntr.hi <<= 1;
+ if ((long64)cntr.lo < 0) cntr.hi++;
+ cntr.lo <<= 1;
+ }
+ divisor.lo >>= 1;
+ if ((divisor.hi & 1) == 1)
+ divisor.lo |= 0x8000000000000000LL;
+ divisor.hi = (ulong64)divisor.hi >> 1;
+
+ cntr.lo >>= 1;
+ if ((cntr.hi & 1) == 1)
+ cntr.lo |= 0x8000000000000000LL;
+ cntr.hi >>= 1;
+
+ while (cntr.hi != 0 || cntr.lo != 0)
+ {
+ if (!(dividend < divisor))
+ {
+ dividend -= divisor;
+ result.hi |= cntr.hi;
+ result.lo |= cntr.lo;
+ }
+ divisor.lo >>= 1;
+ if ((divisor.hi & 1) == 1)
+ divisor.lo |= 0x8000000000000000LL;
+ divisor.hi >>= 1;
+
+ cntr.lo >>= 1;
+ if ((cntr.hi & 1) == 1)
+ cntr.lo |= 0x8000000000000000LL;
+ cntr.hi >>= 1;
+ }
+ if (negate) result = -result;
+ return result;
+ }
+ else if (rhs.hi == this->hi && rhs.lo == this->lo)
+ return Int128(1);
+ else
+ return Int128(0);
+ }
+
+ double AsDouble() const
+ {
+ const double shift64 = 18446744073709551616.0; //2^64
+ if (hi < 0)
+ {
+ if (lo == 0) return (double)hi * shift64;
+ else return -(double)(~lo + ~hi * shift64);
+ }
+ else
+ return (double)(lo + hi * shift64);
+ }
+};
+
+Int128 Int128Mul (long64 lhs, long64 rhs)
+{
+ bool negate = (lhs < 0) != (rhs < 0);
+
+ if (lhs < 0) lhs = -lhs;
+ ulong64 int1Hi = ulong64(lhs) >> 32;
+ ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
+
+ if (rhs < 0) rhs = -rhs;
+ ulong64 int2Hi = ulong64(rhs) >> 32;
+ ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
+
+ //nb: see comments in clipper.pas
+ ulong64 a = int1Hi * int2Hi;
+ ulong64 b = int1Lo * int2Lo;
+ ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+ Int128 tmp;
+ tmp.hi = long64(a + (c >> 32));
+ tmp.lo = long64(c << 32);
+ tmp.lo += long64(b);
+ if (tmp.lo < b) tmp.hi++;
+ if (negate) tmp = -tmp;
+ return tmp;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+bool FullRangeNeeded(const Polygon &pts)
+{
+ bool result = false;
+ for (Polygon::size_type i = 0; i < pts.size(); ++i)
+ {
+ if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange)
+ throw coords_range_error;
+ else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange)
+ result = true;
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+bool Orientation(const Polygon &poly)
+{
+ return Area(poly) >= 0;
+}
+//------------------------------------------------------------------------------
+
+inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2)
+{
+ return ( pt1.X == pt2.X && pt1.Y == pt2.Y );
+}
+//------------------------------------------------------------------------------
+
+double Area(const Polygon &poly)
+{
+ int highI = (int)poly.size() -1;
+ if (highI < 2) return 0;
+
+ if (FullRangeNeeded(poly)) {
+ Int128 a;
+ a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y);
+ for (int i = 1; i <= highI; ++i)
+ a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y);
+ return a.AsDouble() / 2;
+ }
+ else
+ {
+ double a;
+ a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y);
+ for (int i = 1; i <= highI; ++i)
+ a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y);
+ return a / 2;
+ }
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutRec &outRec, bool UseFullInt64Range)
+{
+ OutPt *op = outRec.pts;
+ if (!op) return 0;
+ if (UseFullInt64Range) {
+ Int128 a(0);
+ do {
+ a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y);
+ op = op->next;
+ } while (op != outRec.pts);
+ return a.AsDouble() / 2;
+ }
+ else
+ {
+ double a = 0;
+ do {
+ a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y);
+ op = op->next;
+ } while (op != outRec.pts);
+ return a / 2;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool PointIsVertex(const IntPoint &pt, OutPt *pp)
+{
+ OutPt *pp2 = pp;
+ do
+ {
+ if (PointsEqual(pp2->pt, pt)) return true;
+ pp2 = pp2->next;
+ }
+ while (pp2 != pp);
+ return false;
+}
+//------------------------------------------------------------------------------
+
+bool PointOnLineSegment(const IntPoint pt,
+ const IntPoint linePt1, const IntPoint linePt2, bool UseFullInt64Range)
+{
+ if (UseFullInt64Range)
+ return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) ||
+ ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) ||
+ (((pt.X > linePt1.X) == (pt.X < linePt2.X)) &&
+ ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) &&
+ ((Int128Mul((pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) ==
+ Int128Mul((linePt2.X - linePt1.X), (pt.Y - linePt1.Y)))));
+ else
+ return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) ||
+ ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) ||
+ (((pt.X > linePt1.X) == (pt.X < linePt2.X)) &&
+ ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) &&
+ ((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) ==
+ (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y)));
+}
+//------------------------------------------------------------------------------
+
+bool PointOnPolygon(const IntPoint pt, OutPt *pp, bool UseFullInt64Range)
+{
+ OutPt *pp2 = pp;
+ while (true)
+ {
+ if (PointOnLineSegment(pt, pp2->pt, pp2->next->pt, UseFullInt64Range))
+ return true;
+ pp2 = pp2->next;
+ if (pp2 == pp) break;
+ }
+ return false;
+}
+//------------------------------------------------------------------------------
+
+bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range)
+{
+ OutPt *pp2 = pp;
+ bool result = false;
+ if (UseFullInt64Range) {
+ do
+ {
+ if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) ||
+ ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) &&
+ Int128(pt.X - pp2->pt.X) <
+ Int128Mul(pp2->prev->pt.X - pp2->pt.X, pt.Y - pp2->pt.Y) /
+ Int128(pp2->prev->pt.Y - pp2->pt.Y))
+ result = !result;
+ pp2 = pp2->next;
+ }
+ while (pp2 != pp);
+ }
+ else
+ {
+ do
+ {
+ if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) ||
+ ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) &&
+ (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) /
+ (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result;
+ pp2 = pp2->next;
+ }
+ while (pp2 != pp);
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range)
+{
+ if (UseFullInt64Range)
+ return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY);
+ else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY;
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+ const IntPoint pt3, bool UseFullInt64Range)
+{
+ if (UseFullInt64Range)
+ return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y);
+ else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+ const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range)
+{
+ if (UseFullInt64Range)
+ return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y);
+ else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y);
+}
+//------------------------------------------------------------------------------
+
+double GetDx(const IntPoint pt1, const IntPoint pt2)
+{
+ return (pt1.Y == pt2.Y) ?
+ HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
+}
+//---------------------------------------------------------------------------
+
+void SetDx(TEdge &e)
+{
+ e.deltaX = (e.xtop - e.xbot);
+ e.deltaY = (e.ytop - e.ybot);
+
+ if (e.deltaY == 0) e.dx = HORIZONTAL;
+ else e.dx = (double)(e.deltaX) / e.deltaY;
+}
+//---------------------------------------------------------------------------
+
+void SwapSides(TEdge &edge1, TEdge &edge2)
+{
+ EdgeSide side = edge1.side;
+ edge1.side = edge2.side;
+ edge2.side = side;
+}
+//------------------------------------------------------------------------------
+
+void SwapPolyIndexes(TEdge &edge1, TEdge &edge2)
+{
+ int outIdx = edge1.outIdx;
+ edge1.outIdx = edge2.outIdx;
+ edge2.outIdx = outIdx;
+}
+//------------------------------------------------------------------------------
+
+inline long64 Round(double val)
+{
+ return (val < 0) ? static_cast<long64>(val - 0.5) : static_cast<long64>(val + 0.5);
+}
+//------------------------------------------------------------------------------
+
+long64 TopX(TEdge &edge, const long64 currentY)
+{
+ return ( currentY == edge.ytop ) ?
+ edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot));
+}
+//------------------------------------------------------------------------------
+
+bool IntersectPoint(TEdge &edge1, TEdge &edge2,
+ IntPoint &ip, bool UseFullInt64Range)
+{
+ double b1, b2;
+ if (SlopesEqual(edge1, edge2, UseFullInt64Range))
+ {
+ if (edge2.ybot > edge1.ybot) ip.Y = edge2.ybot;
+ else ip.Y = edge1.ybot;
+ return false;
+ }
+ else if (NEAR_ZERO(edge1.dx))
+ {
+ ip.X = edge1.xbot;
+ if (NEAR_EQUAL(edge2.dx, HORIZONTAL))
+ ip.Y = edge2.ybot;
+ else
+ {
+ b2 = edge2.ybot - (edge2.xbot / edge2.dx);
+ ip.Y = Round(ip.X / edge2.dx + b2);
+ }
+ }
+ else if (NEAR_ZERO(edge2.dx))
+ {
+ ip.X = edge2.xbot;
+ if (NEAR_EQUAL(edge1.dx, HORIZONTAL))
+ ip.Y = edge1.ybot;
+ else
+ {
+ b1 = edge1.ybot - (edge1.xbot / edge1.dx);
+ ip.Y = Round(ip.X / edge1.dx + b1);
+ }
+ }
+ else
+ {
+ b1 = edge1.xbot - edge1.ybot * edge1.dx;
+ b2 = edge2.xbot - edge2.ybot * edge2.dx;
+ double q = (b2-b1) / (edge1.dx - edge2.dx);
+ ip.Y = Round(q);
+ if (std::fabs(edge1.dx) < std::fabs(edge2.dx))
+ ip.X = Round(edge1.dx * q + b1);
+ else
+ ip.X = Round(edge2.dx * q + b2);
+ }
+
+ if (ip.Y < edge1.ytop || ip.Y < edge2.ytop)
+ {
+ if (edge1.ytop > edge2.ytop)
+ {
+ ip.X = edge1.xtop;
+ ip.Y = edge1.ytop;
+ return TopX(edge2, edge1.ytop) < edge1.xtop;
+ }
+ else
+ {
+ ip.X = edge2.xtop;
+ ip.Y = edge2.ytop;
+ return TopX(edge1, edge2.ytop) > edge2.xtop;
+ }
+ }
+ else
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void ReversePolyPtLinks(OutPt *pp)
+{
+ if (!pp) return;
+ OutPt *pp1, *pp2;
+ pp1 = pp;
+ do {
+ pp2 = pp1->next;
+ pp1->next = pp1->prev;
+ pp1->prev = pp2;
+ pp1 = pp2;
+ } while( pp1 != pp );
+}
+//------------------------------------------------------------------------------
+
+void DisposeOutPts(OutPt*& pp)
+{
+ if (pp == 0) return;
+ pp->prev->next = 0;
+ while( pp )
+ {
+ OutPt *tmpPp = pp;
+ pp = pp->next;
+ delete tmpPp;
+ }
+}
+//------------------------------------------------------------------------------
+
+void InitEdge(TEdge *e, TEdge *eNext,
+ TEdge *ePrev, const IntPoint &pt, PolyType polyType)
+{
+ std::memset(e, 0, sizeof(TEdge));
+ e->next = eNext;
+ e->prev = ePrev;
+ e->xcurr = pt.X;
+ e->ycurr = pt.Y;
+ if (e->ycurr >= e->next->ycurr)
+ {
+ e->xbot = e->xcurr;
+ e->ybot = e->ycurr;
+ e->xtop = e->next->xcurr;
+ e->ytop = e->next->ycurr;
+ e->windDelta = 1;
+ } else
+ {
+ e->xtop = e->xcurr;
+ e->ytop = e->ycurr;
+ e->xbot = e->next->xcurr;
+ e->ybot = e->next->ycurr;
+ e->windDelta = -1;
+ }
+ SetDx(*e);
+ e->polyType = polyType;
+ e->outIdx = -1;
+}
+//------------------------------------------------------------------------------
+
+inline void SwapX(TEdge &e)
+{
+ //swap horizontal edges' top and bottom x's so they follow the natural
+ //progression of the bounds - ie so their xbots will align with the
+ //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
+ e.xcurr = e.xtop;
+ e.xtop = e.xbot;
+ e.xbot = e.xcurr;
+}
+//------------------------------------------------------------------------------
+
+void SwapPoints(IntPoint &pt1, IntPoint &pt2)
+{
+ IntPoint tmp = pt1;
+ pt1 = pt2;
+ pt2 = tmp;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
+ IntPoint pt2b, IntPoint &pt1, IntPoint &pt2)
+{
+ //precondition: segments are colinear.
+ if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y))
+ {
+ if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b);
+ if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b);
+ if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a;
+ if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b;
+ return pt1.X < pt2.X;
+ } else
+ {
+ if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b);
+ if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b);
+ if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a;
+ if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b;
+ return pt1.Y > pt2.Y;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
+{
+ OutPt *p = btmPt1->prev;
+ while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev;
+ double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt));
+ p = btmPt1->next;
+ while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next;
+ double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt));
+
+ p = btmPt2->prev;
+ while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev;
+ double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt));
+ p = btmPt2->next;
+ while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next;
+ double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt));
+ return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
+}
+//------------------------------------------------------------------------------
+
+OutPt* GetBottomPt(OutPt *pp)
+{
+ OutPt* dups = 0;
+ OutPt* p = pp->next;
+ while (p != pp)
+ {
+ if (p->pt.Y > pp->pt.Y)
+ {
+ pp = p;
+ dups = 0;
+ }
+ else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X)
+ {
+ if (p->pt.X < pp->pt.X)
+ {
+ dups = 0;
+ pp = p;
+ } else
+ {
+ if (p->next != pp && p->prev != pp) dups = p;
+ }
+ }
+ p = p->next;
+ }
+ if (dups)
+ {
+ //there appears to be at least 2 vertices at bottomPt so ...
+ while (dups != p)
+ {
+ if (!FirstIsBottomPt(p, dups)) pp = dups;
+ dups = dups->next;
+ while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next;
+ }
+ }
+ return pp;
+}
+//------------------------------------------------------------------------------
+
+bool FindSegment(OutPt* &pp, bool UseFullInt64Range,
+ IntPoint &pt1, IntPoint &pt2)
+{
+ //outPt1 & outPt2 => the overlap segment (if the function returns true)
+ if (!pp) return false;
+ OutPt* pp2 = pp;
+ IntPoint pt1a = pt1, pt2a = pt2;
+ do
+ {
+ if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, UseFullInt64Range) &&
+ SlopesEqual(pt1a, pt2a, pp->pt, UseFullInt64Range) &&
+ GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2))
+ return true;
+ pp = pp->next;
+ }
+ while (pp != pp2);
+ return false;
+}
+//------------------------------------------------------------------------------
+
+bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1,
+ const IntPoint pt2, const IntPoint pt3)
+{
+ if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true;
+ else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X);
+ else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y);
+}
+//------------------------------------------------------------------------------
+
+OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt)
+{
+ if (p1 == p2) throw "JoinError";
+ OutPt* result = new OutPt;
+ result->pt = pt;
+ if (p2 == p1->next)
+ {
+ p1->next = result;
+ p2->prev = result;
+ result->next = p2;
+ result->prev = p1;
+ } else
+ {
+ p2->next = result;
+ p1->prev = result;
+ result->next = p1;
+ result->prev = p2;
+ }
+ return result;
+}
+
+//------------------------------------------------------------------------------
+// ClipperBase class methods ...
+//------------------------------------------------------------------------------
+
+ClipperBase::ClipperBase() //constructor
+{
+ m_MinimaList = 0;
+ m_CurrentLM = 0;
+ m_UseFullRange = true;
+}
+//------------------------------------------------------------------------------
+
+ClipperBase::~ClipperBase() //destructor
+{
+ Clear();
+}
+//------------------------------------------------------------------------------
+
+void RangeTest(const IntPoint& pt, long64& maxrange)
+{
+ if (Abs(pt.X) > maxrange)
+ {
+ if (Abs(pt.X) > hiRange)
+ throw coords_range_error;
+ else maxrange = hiRange;
+ }
+ if (Abs(pt.Y) > maxrange)
+ {
+ if (Abs(pt.Y) > hiRange)
+ throw coords_range_error;
+ else maxrange = hiRange;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPolygon(const Polygon &pg, PolyType polyType)
+{
+ int len = (int)pg.size();
+ if (len < 3) return false;
+
+ long64 maxVal;
+ if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange;
+ RangeTest(pg[0], maxVal);
+
+ Polygon p(len);
+ p[0] = pg[0];
+ int j = 0;
+
+ for (int i = 0; i < len; ++i)
+ {
+ RangeTest(pg[i], maxVal);
+
+ if (i == 0 || PointsEqual(p[j], pg[i])) continue;
+ else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange))
+ {
+ if (PointsEqual(p[j-1], pg[i])) j--;
+ } else j++;
+ p[j] = pg[i];
+ }
+ if (j < 2) return false;
+
+ len = j+1;
+ while (len > 2)
+ {
+ //nb: test for point equality before testing slopes ...
+ if (PointsEqual(p[j], p[0])) j--;
+ else if (PointsEqual(p[0], p[1]) ||
+ SlopesEqual(p[j], p[0], p[1], m_UseFullRange))
+ p[0] = p[j--];
+ else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--;
+ else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange))
+ {
+ for (int i = 2; i <= j; ++i) p[i-1] = p[i];
+ j--;
+ }
+ else break;
+ len--;
+ }
+ if (len < 3) return false;
+
+ //create a new edge array ...
+ TEdge *edges = new TEdge [len];
+ m_edges.push_back(edges);
+
+ //convert vertices to a double-linked-list of edges and initialize ...
+ edges[0].xcurr = p[0].X;
+ edges[0].ycurr = p[0].Y;
+ InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType);
+ for (int i = len-2; i > 0; --i)
+ InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType);
+ InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType);
+
+ //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates
+ //increase downward so the 'highest' edge will have the smallest ytop) ...
+ TEdge *e = &edges[0];
+ TEdge *eHighest = e;
+ do
+ {
+ e->xcurr = e->xbot;
+ e->ycurr = e->ybot;
+ if (e->ytop < eHighest->ytop) eHighest = e;
+ e = e->next;
+ }
+ while ( e != &edges[0]);
+
+ //make sure eHighest is positioned so the following loop works safely ...
+ if (eHighest->windDelta > 0) eHighest = eHighest->next;
+ if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next;
+
+ //finally insert each local minima ...
+ e = eHighest;
+ do {
+ e = AddBoundsToLML(e);
+ }
+ while( e != eHighest );
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::InsertLocalMinima(LocalMinima *newLm)
+{
+ if( ! m_MinimaList )
+ {
+ m_MinimaList = newLm;
+ }
+ else if( newLm->Y >= m_MinimaList->Y )
+ {
+ newLm->next = m_MinimaList;
+ m_MinimaList = newLm;
+ } else
+ {
+ LocalMinima* tmpLm = m_MinimaList;
+ while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) )
+ tmpLm = tmpLm->next;
+ newLm->next = tmpLm->next;
+ tmpLm->next = newLm;
+ }
+}
+//------------------------------------------------------------------------------
+
+TEdge* ClipperBase::AddBoundsToLML(TEdge *e)
+{
+ //Starting at the top of one bound we progress to the bottom where there's
+ //a local minima. We then go to the top of the next bound. These two bounds
+ //form the left and right (or right and left) bounds of the local minima.
+ e->nextInLML = 0;
+ e = e->next;
+ for (;;)
+ {
+ if (NEAR_EQUAL(e->dx, HORIZONTAL))
+ {
+ //nb: proceed through horizontals when approaching from their right,
+ // but break on horizontal minima if approaching from their left.
+ // This ensures 'local minima' are always on the left of horizontals.
+ if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break;
+ if (e->xtop != e->prev->xbot) SwapX(*e);
+ e->nextInLML = e->prev;
+ }
+ else if (e->ycurr == e->prev->ycurr) break;
+ else e->nextInLML = e->prev;
+ e = e->next;
+ }
+
+ //e and e.prev are now at a local minima ...
+ LocalMinima* newLm = new LocalMinima;
+ newLm->next = 0;
+ newLm->Y = e->prev->ybot;
+
+ if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound
+ {
+ if (e->xbot != e->prev->xbot) SwapX(*e);
+ newLm->leftBound = e->prev;
+ newLm->rightBound = e;
+ } else if (e->dx < e->prev->dx)
+ {
+ newLm->leftBound = e->prev;
+ newLm->rightBound = e;
+ } else
+ {
+ newLm->leftBound = e;
+ newLm->rightBound = e->prev;
+ }
+ newLm->leftBound->side = esLeft;
+ newLm->rightBound->side = esRight;
+ InsertLocalMinima( newLm );
+
+ for (;;)
+ {
+ if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break;
+ e->nextInLML = e->next;
+ e = e->next;
+ if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e);
+ }
+ return e->next;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType)
+{
+ bool result = false;
+ for (Polygons::size_type i = 0; i < ppg.size(); ++i)
+ if (AddPolygon(ppg[i], polyType)) result = true;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Clear()
+{
+ DisposeLocalMinimaList();
+ for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i];
+ m_edges.clear();
+ m_UseFullRange = false;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Reset()
+{
+ m_CurrentLM = m_MinimaList;
+ if( !m_CurrentLM ) return; //ie nothing to process
+
+ //reset all edges ...
+ LocalMinima* lm = m_MinimaList;
+ while( lm )
+ {
+ TEdge* e = lm->leftBound;
+ while( e )
+ {
+ e->xcurr = e->xbot;
+ e->ycurr = e->ybot;
+ e->side = esLeft;
+ e->outIdx = -1;
+ e = e->nextInLML;
+ }
+ e = lm->rightBound;
+ while( e )
+ {
+ e->xcurr = e->xbot;
+ e->ycurr = e->ybot;
+ e->side = esRight;
+ e->outIdx = -1;
+ e = e->nextInLML;
+ }
+ lm = lm->next;
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeLocalMinimaList()
+{
+ while( m_MinimaList )
+ {
+ LocalMinima* tmpLm = m_MinimaList->next;
+ delete m_MinimaList;
+ m_MinimaList = tmpLm;
+ }
+ m_CurrentLM = 0;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::PopLocalMinima()
+{
+ if( ! m_CurrentLM ) return;
+ m_CurrentLM = m_CurrentLM->next;
+}
+//------------------------------------------------------------------------------
+
+IntRect ClipperBase::GetBounds()
+{
+ IntRect result;
+ LocalMinima* lm = m_MinimaList;
+ if (!lm)
+ {
+ result.left = result.top = result.right = result.bottom = 0;
+ return result;
+ }
+ result.left = lm->leftBound->xbot;
+ result.top = lm->leftBound->ybot;
+ result.right = lm->leftBound->xbot;
+ result.bottom = lm->leftBound->ybot;
+ while (lm)
+ {
+ if (lm->leftBound->ybot > result.bottom)
+ result.bottom = lm->leftBound->ybot;
+ TEdge* e = lm->leftBound;
+ for (;;) {
+ TEdge* bottomE = e;
+ while (e->nextInLML)
+ {
+ if (e->xbot < result.left) result.left = e->xbot;
+ if (e->xbot > result.right) result.right = e->xbot;
+ e = e->nextInLML;
+ }
+ if (e->xbot < result.left) result.left = e->xbot;
+ if (e->xbot > result.right) result.right = e->xbot;
+ if (e->xtop < result.left) result.left = e->xtop;
+ if (e->xtop > result.right) result.right = e->xtop;
+ if (e->ytop < result.top) result.top = e->ytop;
+
+ if (bottomE == lm->leftBound) e = lm->rightBound;
+ else break;
+ }
+ lm = lm->next;
+ }
+ return result;
+}
+
+
+//------------------------------------------------------------------------------
+// TClipper methods ...
+//------------------------------------------------------------------------------
+
+Clipper::Clipper() : ClipperBase() //constructor
+{
+ m_Scanbeam = 0;
+ m_ActiveEdges = 0;
+ m_SortedEdges = 0;
+ m_IntersectNodes = 0;
+ m_ExecuteLocked = false;
+ m_UseFullRange = false;
+ m_ReverseOutput = false;
+ m_ForceSimple = false;
+}
+//------------------------------------------------------------------------------
+
+Clipper::~Clipper() //destructor
+{
+ Clear();
+ DisposeScanbeamList();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::Clear()
+{
+ if (m_edges.empty()) return; //avoids problems with ClipperBase destructor
+ DisposeAllPolyPts();
+ ClipperBase::Clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeScanbeamList()
+{
+ while ( m_Scanbeam ) {
+ Scanbeam* sb2 = m_Scanbeam->next;
+ delete m_Scanbeam;
+ m_Scanbeam = sb2;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::Reset()
+{
+ ClipperBase::Reset();
+ m_Scanbeam = 0;
+ m_ActiveEdges = 0;
+ m_SortedEdges = 0;
+ DisposeAllPolyPts();
+ LocalMinima* lm = m_MinimaList;
+ while (lm)
+ {
+ InsertScanbeam(lm->Y);
+ lm = lm->next;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, Polygons &solution,
+ PolyFillType subjFillType, PolyFillType clipFillType)
+{
+ if( m_ExecuteLocked ) return false;
+ m_ExecuteLocked = true;
+ solution.resize(0);
+ m_SubjFillType = subjFillType;
+ m_ClipFillType = clipFillType;
+ m_ClipType = clipType;
+ m_UsingPolyTree = false;
+ bool succeeded = ExecuteInternal();
+ if (succeeded) BuildResult(solution);
+ m_ExecuteLocked = false;
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree& polytree,
+ PolyFillType subjFillType, PolyFillType clipFillType)
+{
+ if( m_ExecuteLocked ) return false;
+ m_ExecuteLocked = true;
+ m_SubjFillType = subjFillType;
+ m_ClipFillType = clipFillType;
+ m_ClipType = clipType;
+ m_UsingPolyTree = true;
+ bool succeeded = ExecuteInternal();
+ if (succeeded) BuildResult2(polytree);
+ m_ExecuteLocked = false;
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixHoleLinkage(OutRec &outrec)
+{
+ //skip OutRecs that (a) contain outermost polygons or
+ //(b) already have the correct owner/child linkage ...
+ if (!outrec.FirstLeft ||
+ (outrec.isHole != outrec.FirstLeft->isHole &&
+ outrec.FirstLeft->pts)) return;
+
+ OutRec* orfl = outrec.FirstLeft;
+ while (orfl && ((orfl->isHole == outrec.isHole) || !orfl->pts))
+ orfl = orfl->FirstLeft;
+ outrec.FirstLeft = orfl;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ExecuteInternal()
+{
+ bool succeeded;
+ try {
+ Reset();
+ if (!m_CurrentLM ) return true;
+ long64 botY = PopScanbeam();
+ do {
+ InsertLocalMinimaIntoAEL(botY);
+ ClearHorzJoins();
+ ProcessHorizontals();
+ long64 topY = PopScanbeam();
+ succeeded = ProcessIntersections(botY, topY);
+ if (!succeeded) break;
+ ProcessEdgesAtTopOfScanbeam(topY);
+ botY = topY;
+ } while(m_Scanbeam || m_CurrentLM);
+ }
+ catch(...) {
+ succeeded = false;
+ }
+
+ if (succeeded)
+ {
+ //tidy up output polygons and fix orientations where necessary ...
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec *outRec = m_PolyOuts[i];
+ if (!outRec->pts) continue;
+ FixupOutPolygon(*outRec);
+ if (!outRec->pts) continue;
+
+ if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0))
+ ReversePolyPtLinks(outRec->pts);
+ }
+
+ if (!m_Joins.empty()) JoinCommonEdges();
+ if (m_ForceSimple) DoSimplePolygons();
+ }
+
+ ClearJoins();
+ ClearHorzJoins();
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertScanbeam(const long64 Y)
+{
+ if( !m_Scanbeam )
+ {
+ m_Scanbeam = new Scanbeam;
+ m_Scanbeam->next = 0;
+ m_Scanbeam->Y = Y;
+ }
+ else if( Y > m_Scanbeam->Y )
+ {
+ Scanbeam* newSb = new Scanbeam;
+ newSb->Y = Y;
+ newSb->next = m_Scanbeam;
+ m_Scanbeam = newSb;
+ } else
+ {
+ Scanbeam* sb2 = m_Scanbeam;
+ while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next;
+ if( Y == sb2->Y ) return; //ie ignores duplicates
+ Scanbeam* newSb = new Scanbeam;
+ newSb->Y = Y;
+ newSb->next = sb2->next;
+ sb2->next = newSb;
+ }
+}
+//------------------------------------------------------------------------------
+
+long64 Clipper::PopScanbeam()
+{
+ long64 Y = m_Scanbeam->Y;
+ Scanbeam* sb2 = m_Scanbeam;
+ m_Scanbeam = m_Scanbeam->next;
+ delete sb2;
+ return Y;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeAllPolyPts(){
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ DisposeOutRec(i);
+ m_PolyOuts.clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeOutRec(PolyOutList::size_type index)
+{
+ OutRec *outRec = m_PolyOuts[index];
+ if (outRec->pts) DisposeOutPts(outRec->pts);
+ delete outRec;
+ m_PolyOuts[index] = 0;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetWindingCount(TEdge &edge)
+{
+ TEdge *e = edge.prevInAEL;
+ //find the edge of the same polytype that immediately preceeds 'edge' in AEL
+ while ( e && e->polyType != edge.polyType ) e = e->prevInAEL;
+ if ( !e )
+ {
+ edge.windCnt = edge.windDelta;
+ edge.windCnt2 = 0;
+ e = m_ActiveEdges; //ie get ready to calc windCnt2
+ } else if ( IsEvenOddFillType(edge) )
+ {
+ //EvenOdd filling ...
+ edge.windCnt = 1;
+ edge.windCnt2 = e->windCnt2;
+ e = e->nextInAEL; //ie get ready to calc windCnt2
+ } else
+ {
+ //nonZero, Positive or Negative filling ...
+ if ( e->windCnt * e->windDelta < 0 )
+ {
+ if (Abs(e->windCnt) > 1)
+ {
+ if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt;
+ else edge.windCnt = e->windCnt + edge.windDelta;
+ } else
+ edge.windCnt = e->windCnt + e->windDelta + edge.windDelta;
+ } else
+ {
+ if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0)
+ edge.windCnt = e->windCnt;
+ else if ( e->windCnt + edge.windDelta == 0 )
+ edge.windCnt = e->windCnt;
+ else edge.windCnt = e->windCnt + edge.windDelta;
+ }
+ edge.windCnt2 = e->windCnt2;
+ e = e->nextInAEL; //ie get ready to calc windCnt2
+ }
+
+ //update windCnt2 ...
+ if ( IsEvenOddAltFillType(edge) )
+ {
+ //EvenOdd filling ...
+ while ( e != &edge )
+ {
+ edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0;
+ e = e->nextInAEL;
+ }
+ } else
+ {
+ //nonZero, Positive or Negative filling ...
+ while ( e != &edge )
+ {
+ edge.windCnt2 += e->windDelta;
+ e = e->nextInAEL;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddFillType(const TEdge& edge) const
+{
+ if (edge.polyType == ptSubject)
+ return m_SubjFillType == pftEvenOdd; else
+ return m_ClipFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const
+{
+ if (edge.polyType == ptSubject)
+ return m_ClipFillType == pftEvenOdd; else
+ return m_SubjFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsContributing(const TEdge& edge) const
+{
+ PolyFillType pft, pft2;
+ if (edge.polyType == ptSubject)
+ {
+ pft = m_SubjFillType;
+ pft2 = m_ClipFillType;
+ } else
+ {
+ pft = m_ClipFillType;
+ pft2 = m_SubjFillType;
+ }
+
+ switch(pft)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ if (Abs(edge.windCnt) != 1) return false;
+ break;
+ case pftPositive:
+ if (edge.windCnt != 1) return false;
+ break;
+ default: //pftNegative
+ if (edge.windCnt != -1) return false;
+ }
+
+ switch(m_ClipType)
+ {
+ case ctIntersection:
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.windCnt2 != 0);
+ case pftPositive:
+ return (edge.windCnt2 > 0);
+ default:
+ return (edge.windCnt2 < 0);
+ }
+ break;
+ case ctUnion:
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.windCnt2 == 0);
+ case pftPositive:
+ return (edge.windCnt2 <= 0);
+ default:
+ return (edge.windCnt2 >= 0);
+ }
+ break;
+ case ctDifference:
+ if (edge.polyType == ptSubject)
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.windCnt2 == 0);
+ case pftPositive:
+ return (edge.windCnt2 <= 0);
+ default:
+ return (edge.windCnt2 >= 0);
+ }
+ else
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.windCnt2 != 0);
+ case pftPositive:
+ return (edge.windCnt2 > 0);
+ default:
+ return (edge.windCnt2 < 0);
+ }
+ break;
+ default:
+ return true;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt)
+{
+ TEdge *e, *prevE;
+ if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) )
+ {
+ AddOutPt( e1, pt );
+ e2->outIdx = e1->outIdx;
+ e1->side = esLeft;
+ e2->side = esRight;
+ e = e1;
+ if (e->prevInAEL == e2)
+ prevE = e2->prevInAEL;
+ else
+ prevE = e->prevInAEL;
+ } else
+ {
+ AddOutPt( e2, pt );
+ e1->outIdx = e2->outIdx;
+ e1->side = esRight;
+ e2->side = esLeft;
+ e = e2;
+ if (e->prevInAEL == e1)
+ prevE = e1->prevInAEL;
+ else
+ prevE = e->prevInAEL;
+ }
+ if (prevE && prevE->outIdx >= 0 &&
+ (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) &&
+ SlopesEqual(*e, *prevE, m_UseFullRange))
+ AddJoin(e, prevE, -1, -1);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt)
+{
+ AddOutPt( e1, pt );
+ if( e1->outIdx == e2->outIdx )
+ {
+ e1->outIdx = -1;
+ e2->outIdx = -1;
+ }
+ else if (e1->outIdx < e2->outIdx)
+ AppendPolygon(e1, e2);
+ else
+ AppendPolygon(e2, e1);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddEdgeToSEL(TEdge *edge)
+{
+ //SEL pointers in PEdge are reused to build a list of horizontal edges.
+ //However, we don't need to worry about order with horizontal edge processing.
+ if( !m_SortedEdges )
+ {
+ m_SortedEdges = edge;
+ edge->prevInSEL = 0;
+ edge->nextInSEL = 0;
+ }
+ else
+ {
+ edge->nextInSEL = m_SortedEdges;
+ edge->prevInSEL = 0;
+ m_SortedEdges->prevInSEL = edge;
+ m_SortedEdges = edge;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::CopyAELToSEL()
+{
+ TEdge* e = m_ActiveEdges;
+ m_SortedEdges = e;
+ while ( e )
+ {
+ e->prevInSEL = e->prevInAEL;
+ e->nextInSEL = e->nextInAEL;
+ e = e->nextInAEL;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx)
+{
+ JoinRec* jr = new JoinRec;
+ if (e1OutIdx >= 0)
+ jr->poly1Idx = e1OutIdx; else
+ jr->poly1Idx = e1->outIdx;
+ jr->pt1a = IntPoint(e1->xcurr, e1->ycurr);
+ jr->pt1b = IntPoint(e1->xtop, e1->ytop);
+ if (e2OutIdx >= 0)
+ jr->poly2Idx = e2OutIdx; else
+ jr->poly2Idx = e2->outIdx;
+ jr->pt2a = IntPoint(e2->xcurr, e2->ycurr);
+ jr->pt2b = IntPoint(e2->xtop, e2->ytop);
+ m_Joins.push_back(jr);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearJoins()
+{
+ for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+ delete m_Joins[i];
+ m_Joins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddHorzJoin(TEdge *e, int idx)
+{
+ HorzJoinRec* hj = new HorzJoinRec;
+ hj->edge = e;
+ hj->savedIdx = idx;
+ m_HorizJoins.push_back(hj);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearHorzJoins()
+{
+ for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++)
+ delete m_HorizJoins[i];
+ m_HorizJoins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertLocalMinimaIntoAEL(const long64 botY)
+{
+ while( m_CurrentLM && ( m_CurrentLM->Y == botY ) )
+ {
+ TEdge* lb = m_CurrentLM->leftBound;
+ TEdge* rb = m_CurrentLM->rightBound;
+
+ InsertEdgeIntoAEL( lb );
+ InsertScanbeam( lb->ytop );
+ InsertEdgeIntoAEL( rb );
+
+ if (IsEvenOddFillType(*lb))
+ {
+ lb->windDelta = 1;
+ rb->windDelta = 1;
+ }
+ else
+ {
+ rb->windDelta = -lb->windDelta;
+ }
+ SetWindingCount( *lb );
+ rb->windCnt = lb->windCnt;
+ rb->windCnt2 = lb->windCnt2;
+
+ if( NEAR_EQUAL(rb->dx, HORIZONTAL) )
+ {
+ //nb: only rightbounds can have a horizontal bottom edge
+ AddEdgeToSEL( rb );
+ InsertScanbeam( rb->nextInLML->ytop );
+ }
+ else
+ InsertScanbeam( rb->ytop );
+
+ if( IsContributing(*lb) )
+ AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) );
+
+ //if any output polygons share an edge, they'll need joining later ...
+ if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL))
+ {
+ for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i)
+ {
+ IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here.
+ HorzJoinRec* hj = m_HorizJoins[i];
+ //if horizontals rb and hj.edge overlap, flag for joining later ...
+ if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot),
+ IntPoint(hj->edge->xtop, hj->edge->ytop),
+ IntPoint(rb->xbot, rb->ybot),
+ IntPoint(rb->xtop, rb->ytop), pt, pt2))
+ AddJoin(hj->edge, rb, hj->savedIdx);
+ }
+ }
+
+ if( lb->nextInAEL != rb )
+ {
+ if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 &&
+ SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange))
+ AddJoin(rb, rb->prevInAEL);
+
+ TEdge* e = lb->nextInAEL;
+ IntPoint pt = IntPoint(lb->xcurr, lb->ycurr);
+ while( e != rb )
+ {
+ if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!");
+ //nb: For calculating winding counts etc, IntersectEdges() assumes
+ //that param1 will be to the right of param2 ABOVE the intersection ...
+ IntersectEdges( rb , e , pt , ipNone); //order important here
+ e = e->nextInAEL;
+ }
+ }
+ PopLocalMinima();
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromAEL(TEdge *e)
+{
+ TEdge* AelPrev = e->prevInAEL;
+ TEdge* AelNext = e->nextInAEL;
+ if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted
+ if( AelPrev ) AelPrev->nextInAEL = AelNext;
+ else m_ActiveEdges = AelNext;
+ if( AelNext ) AelNext->prevInAEL = AelPrev;
+ e->nextInAEL = 0;
+ e->prevInAEL = 0;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromSEL(TEdge *e)
+{
+ TEdge* SelPrev = e->prevInSEL;
+ TEdge* SelNext = e->nextInSEL;
+ if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted
+ if( SelPrev ) SelPrev->nextInSEL = SelNext;
+ else m_SortedEdges = SelNext;
+ if( SelNext ) SelNext->prevInSEL = SelPrev;
+ e->nextInSEL = 0;
+ e->prevInSEL = 0;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::IntersectEdges(TEdge *e1, TEdge *e2,
+ const IntPoint &pt, const IntersectProtects protects)
+{
+ //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before
+ //e2 in AEL except when e1 is being inserted at the intersection point ...
+ bool e1stops = !(ipLeft & protects) && !e1->nextInLML &&
+ e1->xtop == pt.X && e1->ytop == pt.Y;
+ bool e2stops = !(ipRight & protects) && !e2->nextInLML &&
+ e2->xtop == pt.X && e2->ytop == pt.Y;
+ bool e1Contributing = ( e1->outIdx >= 0 );
+ bool e2contributing = ( e2->outIdx >= 0 );
+
+ //update winding counts...
+ //assumes that e1 will be to the right of e2 ABOVE the intersection
+ if ( e1->polyType == e2->polyType )
+ {
+ if ( IsEvenOddFillType( *e1) )
+ {
+ int oldE1WindCnt = e1->windCnt;
+ e1->windCnt = e2->windCnt;
+ e2->windCnt = oldE1WindCnt;
+ } else
+ {
+ if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt;
+ else e1->windCnt += e2->windDelta;
+ if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt;
+ else e2->windCnt -= e1->windDelta;
+ }
+ } else
+ {
+ if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta;
+ else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0;
+ if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta;
+ else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0;
+ }
+
+ PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
+ if (e1->polyType == ptSubject)
+ {
+ e1FillType = m_SubjFillType;
+ e1FillType2 = m_ClipFillType;
+ } else
+ {
+ e1FillType = m_ClipFillType;
+ e1FillType2 = m_SubjFillType;
+ }
+ if (e2->polyType == ptSubject)
+ {
+ e2FillType = m_SubjFillType;
+ e2FillType2 = m_ClipFillType;
+ } else
+ {
+ e2FillType = m_ClipFillType;
+ e2FillType2 = m_SubjFillType;
+ }
+
+ long64 e1Wc, e2Wc;
+ switch (e1FillType)
+ {
+ case pftPositive: e1Wc = e1->windCnt; break;
+ case pftNegative: e1Wc = -e1->windCnt; break;
+ default: e1Wc = Abs(e1->windCnt);
+ }
+ switch(e2FillType)
+ {
+ case pftPositive: e2Wc = e2->windCnt; break;
+ case pftNegative: e2Wc = -e2->windCnt; break;
+ default: e2Wc = Abs(e2->windCnt);
+ }
+
+ if ( e1Contributing && e2contributing )
+ {
+ if ( e1stops || e2stops ||
+ (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
+ (e1->polyType != e2->polyType && m_ClipType != ctXor) )
+ AddLocalMaxPoly(e1, e2, pt);
+ else
+ {
+ AddOutPt(e1, pt);
+ AddOutPt(e2, pt);
+ SwapSides( *e1 , *e2 );
+ SwapPolyIndexes( *e1 , *e2 );
+ }
+ }
+ else if ( e1Contributing )
+ {
+ if (e2Wc == 0 || e2Wc == 1)
+ {
+ AddOutPt(e1, pt);
+ SwapSides(*e1, *e2);
+ SwapPolyIndexes(*e1, *e2);
+ }
+ }
+ else if ( e2contributing )
+ {
+ if (e1Wc == 0 || e1Wc == 1)
+ {
+ AddOutPt(e2, pt);
+ SwapSides(*e1, *e2);
+ SwapPolyIndexes(*e1, *e2);
+ }
+ }
+ else if ( (e1Wc == 0 || e1Wc == 1) &&
+ (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops )
+ {
+ //neither edge is currently contributing ...
+
+ long64 e1Wc2, e2Wc2;
+ switch (e1FillType2)
+ {
+ case pftPositive: e1Wc2 = e1->windCnt2; break;
+ case pftNegative : e1Wc2 = -e1->windCnt2; break;
+ default: e1Wc2 = Abs(e1->windCnt2);
+ }
+ switch (e2FillType2)
+ {
+ case pftPositive: e2Wc2 = e2->windCnt2; break;
+ case pftNegative: e2Wc2 = -e2->windCnt2; break;
+ default: e2Wc2 = Abs(e2->windCnt2);
+ }
+
+ if (e1->polyType != e2->polyType)
+ AddLocalMinPoly(e1, e2, pt);
+ else if (e1Wc == 1 && e2Wc == 1)
+ switch( m_ClipType ) {
+ case ctIntersection:
+ if (e1Wc2 > 0 && e2Wc2 > 0)
+ AddLocalMinPoly(e1, e2, pt);
+ break;
+ case ctUnion:
+ if ( e1Wc2 <= 0 && e2Wc2 <= 0 )
+ AddLocalMinPoly(e1, e2, pt);
+ break;
+ case ctDifference:
+ if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+ ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+ AddLocalMinPoly(e1, e2, pt);
+ break;
+ case ctXor:
+ AddLocalMinPoly(e1, e2, pt);
+ }
+ else
+ SwapSides( *e1, *e2 );
+ }
+
+ if( (e1stops != e2stops) &&
+ ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) )
+ {
+ SwapSides( *e1, *e2 );
+ SwapPolyIndexes( *e1, *e2 );
+ }
+
+ //finally, delete any non-contributing maxima edges ...
+ if( e1stops ) DeleteFromAEL( e1 );
+ if( e2stops ) DeleteFromAEL( e2 );
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetHoleState(TEdge *e, OutRec *outrec)
+{
+ bool isHole = false;
+ TEdge *e2 = e->prevInAEL;
+ while (e2)
+ {
+ if (e2->outIdx >= 0)
+ {
+ isHole = !isHole;
+ if (! outrec->FirstLeft)
+ outrec->FirstLeft = m_PolyOuts[e2->outIdx];
+ }
+ e2 = e2->prevInAEL;
+ }
+ if (isHole) outrec->isHole = true;
+}
+//------------------------------------------------------------------------------
+
+OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2)
+{
+ //work out which polygon fragment has the correct hole state ...
+ if (!outRec1->bottomPt)
+ outRec1->bottomPt = GetBottomPt(outRec1->pts);
+ if (!outRec2->bottomPt)
+ outRec2->bottomPt = GetBottomPt(outRec2->pts);
+ OutPt *outPt1 = outRec1->bottomPt;
+ OutPt *outPt2 = outRec2->bottomPt;
+ if (outPt1->pt.Y > outPt2->pt.Y) return outRec1;
+ else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2;
+ else if (outPt1->pt.X < outPt2->pt.X) return outRec1;
+ else if (outPt1->pt.X > outPt2->pt.X) return outRec2;
+ else if (outPt1->next == outPt1) return outRec2;
+ else if (outPt2->next == outPt2) return outRec1;
+ else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1;
+ else return outRec2;
+}
+//------------------------------------------------------------------------------
+
+bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2)
+{
+ do
+ {
+ outRec1 = outRec1->FirstLeft;
+ if (outRec1 == outRec2) return true;
+ } while (outRec1);
+ return false;
+}
+//------------------------------------------------------------------------------
+
+OutRec* Clipper::GetOutRec(int idx)
+{
+ OutRec* outrec = m_PolyOuts[idx];
+ while (outrec != m_PolyOuts[outrec->idx])
+ outrec = m_PolyOuts[outrec->idx];
+ return outrec;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
+{
+ //get the start and ends of both output polygons ...
+ OutRec *outRec1 = m_PolyOuts[e1->outIdx];
+ OutRec *outRec2 = m_PolyOuts[e2->outIdx];
+
+ OutRec *holeStateRec;
+ if (Param1RightOfParam2(outRec1, outRec2))
+ holeStateRec = outRec2;
+ else if (Param1RightOfParam2(outRec2, outRec1))
+ holeStateRec = outRec1;
+ else
+ holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+ OutPt* p1_lft = outRec1->pts;
+ OutPt* p1_rt = p1_lft->prev;
+ OutPt* p2_lft = outRec2->pts;
+ OutPt* p2_rt = p2_lft->prev;
+
+ EdgeSide side;
+ //join e2 poly onto e1 poly and delete pointers to e2 ...
+ if( e1->side == esLeft )
+ {
+ if( e2->side == esLeft )
+ {
+ //z y x a b c
+ ReversePolyPtLinks(p2_lft);
+ p2_lft->next = p1_lft;
+ p1_lft->prev = p2_lft;
+ p1_rt->next = p2_rt;
+ p2_rt->prev = p1_rt;
+ outRec1->pts = p2_rt;
+ } else
+ {
+ //x y z a b c
+ p2_rt->next = p1_lft;
+ p1_lft->prev = p2_rt;
+ p2_lft->prev = p1_rt;
+ p1_rt->next = p2_lft;
+ outRec1->pts = p2_lft;
+ }
+ side = esLeft;
+ } else
+ {
+ if( e2->side == esRight )
+ {
+ //a b c z y x
+ ReversePolyPtLinks(p2_lft);
+ p1_rt->next = p2_rt;
+ p2_rt->prev = p1_rt;
+ p2_lft->next = p1_lft;
+ p1_lft->prev = p2_lft;
+ } else
+ {
+ //a b c x y z
+ p1_rt->next = p2_lft;
+ p2_lft->prev = p1_rt;
+ p1_lft->prev = p2_rt;
+ p2_rt->next = p1_lft;
+ }
+ side = esRight;
+ }
+
+ outRec1->bottomPt = 0;
+ if (holeStateRec == outRec2)
+ {
+ if (outRec2->FirstLeft != outRec1)
+ outRec1->FirstLeft = outRec2->FirstLeft;
+ outRec1->isHole = outRec2->isHole;
+ }
+ outRec2->pts = 0;
+ outRec2->bottomPt = 0;
+
+ outRec2->FirstLeft = outRec1;
+
+ int OKIdx = e1->outIdx;
+ int ObsoleteIdx = e2->outIdx;
+
+ e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly
+ e2->outIdx = -1;
+
+ TEdge* e = m_ActiveEdges;
+ while( e )
+ {
+ if( e->outIdx == ObsoleteIdx )
+ {
+ e->outIdx = OKIdx;
+ e->side = side;
+ break;
+ }
+ e = e->nextInAEL;
+ }
+
+ outRec2->idx = outRec1->idx;
+}
+//------------------------------------------------------------------------------
+
+OutRec* Clipper::CreateOutRec()
+{
+ OutRec* result = new OutRec;
+ result->isHole = false;
+ result->FirstLeft = 0;
+ result->pts = 0;
+ result->bottomPt = 0;
+ result->polyNode = 0;
+ m_PolyOuts.push_back(result);
+ result->idx = (int)m_PolyOuts.size()-1;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
+{
+ bool ToFront = (e->side == esLeft);
+ if( e->outIdx < 0 )
+ {
+ OutRec *outRec = CreateOutRec();
+ e->outIdx = outRec->idx;
+ OutPt* newOp = new OutPt;
+ outRec->pts = newOp;
+ newOp->pt = pt;
+ newOp->idx = outRec->idx;
+ newOp->next = newOp;
+ newOp->prev = newOp;
+ SetHoleState(e, outRec);
+ } else
+ {
+ OutRec *outRec = m_PolyOuts[e->outIdx];
+ OutPt* op = outRec->pts;
+ if ((ToFront && PointsEqual(pt, op->pt)) ||
+ (!ToFront && PointsEqual(pt, op->prev->pt))) return;
+
+ OutPt* newOp = new OutPt;
+ newOp->pt = pt;
+ newOp->idx = outRec->idx;
+ newOp->next = op;
+ newOp->prev = op->prev;
+ newOp->prev->next = newOp;
+ op->prev = newOp;
+ if (ToFront) outRec->pts = newOp;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessHorizontals()
+{
+ TEdge* horzEdge = m_SortedEdges;
+ while( horzEdge )
+ {
+ DeleteFromSEL( horzEdge );
+ ProcessHorizontal( horzEdge );
+ horzEdge = m_SortedEdges;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsTopHorz(const long64 XPos)
+{
+ TEdge* e = m_SortedEdges;
+ while( e )
+ {
+ if( ( XPos >= std::min(e->xcurr, e->xtop) ) &&
+ ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false;
+ e = e->nextInSEL;
+ }
+ return true;
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMinima(TEdge *e)
+{
+ return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMaxima(TEdge *e, const long64 Y)
+{
+ return e && e->ytop == Y && !e->nextInLML;
+}
+//------------------------------------------------------------------------------
+
+inline bool IsIntermediate(TEdge *e, const long64 Y)
+{
+ return e->ytop == Y && e->nextInLML;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPair(TEdge *e)
+{
+ if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop )
+ return e->prev; else
+ return e->next;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2)
+{
+ if( edge1->nextInAEL == edge2 )
+ {
+ TEdge* next = edge2->nextInAEL;
+ if( next ) next->prevInAEL = edge1;
+ TEdge* prev = edge1->prevInAEL;
+ if( prev ) prev->nextInAEL = edge2;
+ edge2->prevInAEL = prev;
+ edge2->nextInAEL = edge1;
+ edge1->prevInAEL = edge2;
+ edge1->nextInAEL = next;
+ }
+ else if( edge2->nextInAEL == edge1 )
+ {
+ TEdge* next = edge1->nextInAEL;
+ if( next ) next->prevInAEL = edge2;
+ TEdge* prev = edge2->prevInAEL;
+ if( prev ) prev->nextInAEL = edge1;
+ edge1->prevInAEL = prev;
+ edge1->nextInAEL = edge2;
+ edge2->prevInAEL = edge1;
+ edge2->nextInAEL = next;
+ }
+ else
+ {
+ TEdge* next = edge1->nextInAEL;
+ TEdge* prev = edge1->prevInAEL;
+ edge1->nextInAEL = edge2->nextInAEL;
+ if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1;
+ edge1->prevInAEL = edge2->prevInAEL;
+ if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1;
+ edge2->nextInAEL = next;
+ if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2;
+ edge2->prevInAEL = prev;
+ if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2;
+ }
+
+ if( !edge1->prevInAEL ) m_ActiveEdges = edge1;
+ else if( !edge2->prevInAEL ) m_ActiveEdges = edge2;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2)
+{
+ if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return;
+ if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return;
+
+ if( edge1->nextInSEL == edge2 )
+ {
+ TEdge* next = edge2->nextInSEL;
+ if( next ) next->prevInSEL = edge1;
+ TEdge* prev = edge1->prevInSEL;
+ if( prev ) prev->nextInSEL = edge2;
+ edge2->prevInSEL = prev;
+ edge2->nextInSEL = edge1;
+ edge1->prevInSEL = edge2;
+ edge1->nextInSEL = next;
+ }
+ else if( edge2->nextInSEL == edge1 )
+ {
+ TEdge* next = edge1->nextInSEL;
+ if( next ) next->prevInSEL = edge2;
+ TEdge* prev = edge2->prevInSEL;
+ if( prev ) prev->nextInSEL = edge1;
+ edge1->prevInSEL = prev;
+ edge1->nextInSEL = edge2;
+ edge2->prevInSEL = edge1;
+ edge2->nextInSEL = next;
+ }
+ else
+ {
+ TEdge* next = edge1->nextInSEL;
+ TEdge* prev = edge1->prevInSEL;
+ edge1->nextInSEL = edge2->nextInSEL;
+ if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1;
+ edge1->prevInSEL = edge2->prevInSEL;
+ if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1;
+ edge2->nextInSEL = next;
+ if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2;
+ edge2->prevInSEL = prev;
+ if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2;
+ }
+
+ if( !edge1->prevInSEL ) m_SortedEdges = edge1;
+ else if( !edge2->prevInSEL ) m_SortedEdges = edge2;
+}
+//------------------------------------------------------------------------------
+
+TEdge* GetNextInAEL(TEdge *e, Direction dir)
+{
+ return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessHorizontal(TEdge *horzEdge)
+{
+ Direction dir;
+ long64 horzLeft, horzRight;
+
+ if( horzEdge->xcurr < horzEdge->xtop )
+ {
+ horzLeft = horzEdge->xcurr;
+ horzRight = horzEdge->xtop;
+ dir = dLeftToRight;
+ } else
+ {
+ horzLeft = horzEdge->xtop;
+ horzRight = horzEdge->xcurr;
+ dir = dRightToLeft;
+ }
+
+ TEdge* eMaxPair;
+ if( horzEdge->nextInLML ) eMaxPair = 0;
+ else eMaxPair = GetMaximaPair(horzEdge);
+
+ TEdge* e = GetNextInAEL( horzEdge , dir );
+ while( e )
+ {
+ if ( e->xcurr == horzEdge->xtop && !eMaxPair )
+ {
+ if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange))
+ {
+ //if output polygons share an edge, they'll need joining later ...
+ if (horzEdge->outIdx >= 0 && e->outIdx >= 0)
+ AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx);
+ break; //we've reached the end of the horizontal line
+ }
+ else if (e->dx < horzEdge->nextInLML->dx)
+ //we really have got to the end of the intermediate horz edge so quit.
+ //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal.
+ break;
+ }
+
+ TEdge* eNext = GetNextInAEL( e, dir );
+
+ if (eMaxPair ||
+ ((dir == dLeftToRight) && (e->xcurr < horzRight)) ||
+ ((dir == dRightToLeft) && (e->xcurr > horzLeft)))
+ {
+ //so far we're still in range of the horizontal edge
+ if( e == eMaxPair )
+ {
+ //horzEdge is evidently a maxima horizontal and we've arrived at its end.
+ if (dir == dLeftToRight)
+ IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone);
+ else
+ IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone);
+ if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error");
+ return;
+ }
+ else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) )
+ {
+ //An overlapping horizontal edge. Overlapping horizontal edges are
+ //processed as if layered with the current horizontal edge (horizEdge)
+ //being infinitesimally lower that the next (e). Therfore, we
+ //intersect with e only if e.xcurr is within the bounds of horzEdge ...
+ if( dir == dLeftToRight )
+ IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr),
+ (IsTopHorz( e->xcurr ))? ipLeft : ipBoth );
+ else
+ IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr),
+ (IsTopHorz( e->xcurr ))? ipRight : ipBoth );
+ }
+ else if( dir == dLeftToRight )
+ {
+ IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr),
+ (IsTopHorz( e->xcurr ))? ipLeft : ipBoth );
+ }
+ else
+ {
+ IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr),
+ (IsTopHorz( e->xcurr ))? ipRight : ipBoth );
+ }
+ SwapPositionsInAEL( horzEdge, e );
+ }
+ else if( (dir == dLeftToRight && e->xcurr >= horzRight) ||
+ (dir == dRightToLeft && e->xcurr <= horzLeft) ) break;
+ e = eNext;
+ } //end while
+
+ if( horzEdge->nextInLML )
+ {
+ if( horzEdge->outIdx >= 0 )
+ AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop));
+ UpdateEdgeIntoAEL( horzEdge );
+ }
+ else
+ {
+ if ( horzEdge->outIdx >= 0 )
+ IntersectEdges( horzEdge, eMaxPair,
+ IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth);
+ if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error");
+ DeleteFromAEL(eMaxPair);
+ DeleteFromAEL(horzEdge);
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::UpdateEdgeIntoAEL(TEdge *&e)
+{
+ if( !e->nextInLML ) throw
+ clipperException("UpdateEdgeIntoAEL: invalid call");
+ TEdge* AelPrev = e->prevInAEL;
+ TEdge* AelNext = e->nextInAEL;
+ e->nextInLML->outIdx = e->outIdx;
+ if( AelPrev ) AelPrev->nextInAEL = e->nextInLML;
+ else m_ActiveEdges = e->nextInLML;
+ if( AelNext ) AelNext->prevInAEL = e->nextInLML;
+ e->nextInLML->side = e->side;
+ e->nextInLML->windDelta = e->windDelta;
+ e->nextInLML->windCnt = e->windCnt;
+ e->nextInLML->windCnt2 = e->windCnt2;
+ e = e->nextInLML;
+ e->prevInAEL = AelPrev;
+ e->nextInAEL = AelNext;
+ if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop );
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ProcessIntersections(const long64 botY, const long64 topY)
+{
+ if( !m_ActiveEdges ) return true;
+ try {
+ BuildIntersectList(botY, topY);
+ if (!m_IntersectNodes) return true;
+ if (!m_IntersectNodes->next || FixupIntersectionOrder()) ProcessIntersectList();
+ else return false;
+ }
+ catch(...) {
+ m_SortedEdges = 0;
+ DisposeIntersectNodes();
+ throw clipperException("ProcessIntersections error");
+ }
+ m_SortedEdges = 0;
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeIntersectNodes()
+{
+ while ( m_IntersectNodes )
+ {
+ IntersectNode* iNode = m_IntersectNodes->next;
+ delete m_IntersectNodes;
+ m_IntersectNodes = iNode;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildIntersectList(const long64 botY, const long64 topY)
+{
+ if ( !m_ActiveEdges ) return;
+
+ //prepare for sorting ...
+ TEdge* e = m_ActiveEdges;
+ m_SortedEdges = e;
+ while( e )
+ {
+ e->prevInSEL = e->prevInAEL;
+ e->nextInSEL = e->nextInAEL;
+ e->xcurr = TopX( *e, topY );
+ e = e->nextInAEL;
+ }
+
+ //bubblesort ...
+ bool isModified;
+ do
+ {
+ isModified = false;
+ e = m_SortedEdges;
+ while( e->nextInSEL )
+ {
+ TEdge *eNext = e->nextInSEL;
+ IntPoint pt;
+ if(e->xcurr > eNext->xcurr)
+ {
+ if (!IntersectPoint(*e, *eNext, pt, m_UseFullRange) && e->xcurr > eNext->xcurr +1)
+ throw clipperException("Intersection error");
+ if (pt.Y > botY)
+ {
+ pt.Y = botY;
+ pt.X = TopX(*e, pt.Y);
+ }
+ InsertIntersectNode( e, eNext, pt );
+ SwapPositionsInSEL(e, eNext);
+ isModified = true;
+ }
+ else
+ e = eNext;
+ }
+ if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0;
+ else break;
+ }
+ while ( isModified );
+ m_SortedEdges = 0; //important
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt)
+{
+ IntersectNode* newNode = new IntersectNode;
+ newNode->edge1 = e1;
+ newNode->edge2 = e2;
+ newNode->pt = pt;
+ newNode->next = 0;
+ if( !m_IntersectNodes ) m_IntersectNodes = newNode;
+ else if(newNode->pt.Y > m_IntersectNodes->pt.Y )
+ {
+ newNode->next = m_IntersectNodes;
+ m_IntersectNodes = newNode;
+ }
+ else
+ {
+ IntersectNode* iNode = m_IntersectNodes;
+ while(iNode->next && newNode->pt.Y <= iNode->next->pt.Y)
+ iNode = iNode->next;
+ newNode->next = iNode->next;
+ iNode->next = newNode;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessIntersectList()
+{
+ while( m_IntersectNodes )
+ {
+ IntersectNode* iNode = m_IntersectNodes->next;
+ {
+ IntersectEdges( m_IntersectNodes->edge1 ,
+ m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth );
+ SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 );
+ }
+ delete m_IntersectNodes;
+ m_IntersectNodes = iNode;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DoMaxima(TEdge *e, long64 topY)
+{
+ TEdge* eMaxPair = GetMaximaPair(e);
+ long64 X = e->xtop;
+ TEdge* eNext = e->nextInAEL;
+ while( eNext != eMaxPair )
+ {
+ if (!eNext) throw clipperException("DoMaxima error");
+ IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth );
+ SwapPositionsInAEL(e, eNext);
+ eNext = e->nextInAEL;
+ }
+ if( e->outIdx < 0 && eMaxPair->outIdx < 0 )
+ {
+ DeleteFromAEL( e );
+ DeleteFromAEL( eMaxPair );
+ }
+ else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 )
+ {
+ IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone );
+ }
+ else throw clipperException("DoMaxima error");
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY)
+{
+ TEdge* e = m_ActiveEdges;
+ while( e )
+ {
+ //1. process maxima, treating them as if they're 'bent' horizontal edges,
+ // but exclude maxima with horizontal edges. nb: e can't be a horizontal.
+ if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) )
+ {
+ //'e' might be removed from AEL, as may any following edges so ...
+ TEdge* ePrev = e->prevInAEL;
+ DoMaxima(e, topY);
+ if( !ePrev ) e = m_ActiveEdges;
+ else e = ePrev->nextInAEL;
+ }
+ else
+ {
+ bool intermediateVert = IsIntermediate(e, topY);
+ //2. promote horizontal edges, otherwise update xcurr and ycurr ...
+ if (intermediateVert && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) )
+ {
+ if (e->outIdx >= 0)
+ {
+ AddOutPt(e, IntPoint(e->xtop, e->ytop));
+
+ for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i)
+ {
+ IntPoint pt, pt2;
+ HorzJoinRec* hj = m_HorizJoins[i];
+ if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot),
+ IntPoint(hj->edge->xtop, hj->edge->ytop),
+ IntPoint(e->nextInLML->xbot, e->nextInLML->ybot),
+ IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2))
+ AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx);
+ }
+
+ AddHorzJoin(e->nextInLML, e->outIdx);
+ }
+ UpdateEdgeIntoAEL(e);
+ AddEdgeToSEL(e);
+ } else
+ {
+ e->xcurr = TopX( *e, topY );
+ e->ycurr = topY;
+
+ if (m_ForceSimple && e->prevInAEL &&
+ e->prevInAEL->xcurr == e->xcurr &&
+ e->outIdx >= 0 && e->prevInAEL->outIdx >= 0)
+ {
+ if (intermediateVert)
+ AddOutPt(e->prevInAEL, IntPoint(e->xcurr, topY));
+ else
+ AddOutPt(e, IntPoint(e->xcurr, topY));
+ }
+ }
+ e = e->nextInAEL;
+ }
+ }
+
+ //3. Process horizontals at the top of the scanbeam ...
+ ProcessHorizontals();
+
+ //4. Promote intermediate vertices ...
+ e = m_ActiveEdges;
+ while( e )
+ {
+ if( IsIntermediate( e, topY ) )
+ {
+ if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop));
+ UpdateEdgeIntoAEL(e);
+
+ //if output polygons share an edge, they'll need joining later ...
+ TEdge* ePrev = e->prevInAEL;
+ TEdge* eNext = e->nextInAEL;
+ if (ePrev && ePrev->xcurr == e->xbot &&
+ ePrev->ycurr == e->ybot && e->outIdx >= 0 &&
+ ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop &&
+ SlopesEqual(*e, *ePrev, m_UseFullRange))
+ {
+ AddOutPt(ePrev, IntPoint(e->xbot, e->ybot));
+ AddJoin(e, ePrev);
+ }
+ else if (eNext && eNext->xcurr == e->xbot &&
+ eNext->ycurr == e->ybot && e->outIdx >= 0 &&
+ eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop &&
+ SlopesEqual(*e, *eNext, m_UseFullRange))
+ {
+ AddOutPt(eNext, IntPoint(e->xbot, e->ybot));
+ AddJoin(e, eNext);
+ }
+ }
+ e = e->nextInAEL;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolygon(OutRec &outrec)
+{
+ //FixupOutPolygon() - removes duplicate points and simplifies consecutive
+ //parallel edges by removing the middle vertex.
+ OutPt *lastOK = 0;
+ outrec.bottomPt = 0;
+ OutPt *pp = outrec.pts;
+
+ for (;;)
+ {
+ if (pp->prev == pp || pp->prev == pp->next )
+ {
+ DisposeOutPts(pp);
+ outrec.pts = 0;
+ return;
+ }
+ //test for duplicate points and for same slope (cross-product) ...
+ if ( PointsEqual(pp->pt, pp->next->pt) ||
+ SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) )
+ {
+ lastOK = 0;
+ OutPt *tmp = pp;
+ pp->prev->next = pp->next;
+ pp->next->prev = pp->prev;
+ pp = pp->prev;
+ delete tmp;
+ }
+ else if (pp == lastOK) break;
+ else
+ {
+ if (!lastOK) lastOK = pp;
+ pp = pp->next;
+ }
+ }
+ outrec.pts = pp;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult(Polygons &polys)
+{
+ polys.reserve(m_PolyOuts.size());
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ if (m_PolyOuts[i]->pts)
+ {
+ Polygon pg;
+ OutPt* p = m_PolyOuts[i]->pts;
+ do
+ {
+ pg.push_back(p->pt);
+ p = p->prev;
+ } while (p != m_PolyOuts[i]->pts);
+ if (pg.size() > 2)
+ polys.push_back(pg);
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+int PointCount(OutPt *pts)
+{
+ if (!pts) return 0;
+ int result = 0;
+ OutPt* p = pts;
+ do
+ {
+ result++;
+ p = p->next;
+ }
+ while (p != pts);
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult2(PolyTree& polytree)
+{
+ polytree.Clear();
+ polytree.AllNodes.reserve(m_PolyOuts.size());
+ //add each output polygon/contour to polytree ...
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ int cnt = PointCount(outRec->pts);
+ if (cnt < 3) continue;
+ FixHoleLinkage(*outRec);
+ PolyNode* pn = new PolyNode();
+ //nb: polytree takes ownership of all the PolyNodes
+ polytree.AllNodes.push_back(pn);
+ outRec->polyNode = pn;
+ pn->Parent = 0;
+ pn->Index = 0;
+ pn->Contour.reserve(cnt);
+ OutPt *op = outRec->pts;
+ for (int j = 0; j < cnt; j++)
+ {
+ pn->Contour.push_back(op->pt);
+ op = op->prev;
+ }
+ }
+
+ //fixup PolyNode links etc ...
+ polytree.Childs.reserve(m_PolyOuts.size());
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (!outRec->polyNode) continue;
+ if (outRec->FirstLeft)
+ outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode);
+ else
+ polytree.AddChild(*outRec->polyNode);
+ }
+}
+//------------------------------------------------------------------------------
+
+void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2)
+{
+ //just swap the contents (because fIntersectNodes is a single-linked-list)
+ IntersectNode inode = int1; //gets a copy of Int1
+ int1.edge1 = int2.edge1;
+ int1.edge2 = int2.edge2;
+ int1.pt = int2.pt;
+ int2.edge1 = inode.edge1;
+ int2.edge2 = inode.edge2;
+ int2.pt = inode.pt;
+}
+//------------------------------------------------------------------------------
+
+inline bool EdgesAdjacent(const IntersectNode &inode)
+{
+ return (inode.edge1->nextInSEL == inode.edge2) ||
+ (inode.edge1->prevInSEL == inode.edge2);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::FixupIntersectionOrder()
+{
+ //pre-condition: intersections are sorted bottom-most (then left-most) first.
+ //Now it's crucial that intersections are made only between adjacent edges,
+ //so to ensure this the order of intersections may need adjusting ...
+ IntersectNode *inode = m_IntersectNodes;
+ CopyAELToSEL();
+ while (inode)
+ {
+ if (!EdgesAdjacent(*inode))
+ {
+ IntersectNode *nextNode = inode->next;
+ while (nextNode && !EdgesAdjacent(*nextNode))
+ nextNode = nextNode->next;
+ if (!nextNode)
+ return false;
+ SwapIntersectNodes(*inode, *nextNode);
+ }
+ SwapPositionsInSEL(inode->edge1, inode->edge2);
+ inode = inode->next;
+ }
+ return true;
+}
+//------------------------------------------------------------------------------
+
+inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2)
+{
+ if (e2.xcurr == e1.xcurr)
+ {
+ if (e2.ytop > e1.ytop)
+ return e2.xtop < TopX(e1, e2.ytop);
+ else return e1.xtop > TopX(e2, e1.ytop);
+ }
+ else return e2.xcurr < e1.xcurr;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertEdgeIntoAEL(TEdge *edge)
+{
+ edge->prevInAEL = 0;
+ edge->nextInAEL = 0;
+ if( !m_ActiveEdges )
+ {
+ m_ActiveEdges = edge;
+ }
+ else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) )
+ {
+ edge->nextInAEL = m_ActiveEdges;
+ m_ActiveEdges->prevInAEL = edge;
+ m_ActiveEdges = edge;
+ } else
+ {
+ TEdge* e = m_ActiveEdges;
+ while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) )
+ e = e->nextInAEL;
+ edge->nextInAEL = e->nextInAEL;
+ if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge;
+ edge->prevInAEL = e;
+ e->nextInAEL = edge;
+ }
+}
+//----------------------------------------------------------------------
+
+bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2)
+{
+ OutRec *outRec1 = m_PolyOuts[j->poly1Idx];
+ OutRec *outRec2 = m_PolyOuts[j->poly2Idx];
+ if (!outRec1 || !outRec2) return false;
+ OutPt *pp1a = outRec1->pts;
+ OutPt *pp2a = outRec2->pts;
+ IntPoint pt1 = j->pt2a, pt2 = j->pt2b;
+ IntPoint pt3 = j->pt1a, pt4 = j->pt1b;
+ if (!FindSegment(pp1a, m_UseFullRange, pt1, pt2)) return false;
+ if (outRec1 == outRec2)
+ {
+ //we're searching the same polygon for overlapping segments so
+ //segment 2 mustn't be the same as segment 1 ...
+ pp2a = pp1a->next;
+ if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4) || (pp2a == pp1a))
+ return false;
+ }
+ else if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4)) return false;
+
+ if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false;
+
+ OutPt *p3, *p4, *prev = pp1a->prev;
+ //get p1 & p2 polypts - the overlap start & endpoints on poly1
+ if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a;
+ else if (PointsEqual(prev->pt, pt1)) p1 = prev;
+ else p1 = InsertPolyPtBetween(pp1a, prev, pt1);
+
+ if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a;
+ else if (PointsEqual(prev->pt, pt2)) p2 = prev;
+ else if ((p1 == pp1a) || (p1 == prev))
+ p2 = InsertPolyPtBetween(pp1a, prev, pt2);
+ else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2))
+ p2 = InsertPolyPtBetween(pp1a, p1, pt2); else
+ p2 = InsertPolyPtBetween(p1, prev, pt2);
+
+ //get p3 & p4 polypts - the overlap start & endpoints on poly2
+ prev = pp2a->prev;
+ if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a;
+ else if (PointsEqual(prev->pt, pt1)) p3 = prev;
+ else p3 = InsertPolyPtBetween(pp2a, prev, pt1);
+
+ if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a;
+ else if (PointsEqual(prev->pt, pt2)) p4 = prev;
+ else if ((p3 == pp2a) || (p3 == prev))
+ p4 = InsertPolyPtBetween(pp2a, prev, pt2);
+ else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2))
+ p4 = InsertPolyPtBetween(pp2a, p3, pt2); else
+ p4 = InsertPolyPtBetween(p3, prev, pt2);
+
+ //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ...
+ if (p1->next == p2 && p3->prev == p4)
+ {
+ p1->next = p3;
+ p3->prev = p1;
+ p2->prev = p4;
+ p4->next = p2;
+ return true;
+ }
+ else if (p1->prev == p2 && p3->next == p4)
+ {
+ p1->prev = p3;
+ p3->next = p1;
+ p2->next = p4;
+ p4->prev = p2;
+ return true;
+ }
+ else
+ return false; //an orientation is probably wrong
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx)
+{
+ for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++)
+ {
+ JoinRec* j2 = m_Joins[k];
+ if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt))
+ j2->poly1Idx = j->poly2Idx;
+ if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt))
+ j2->poly2Idx = j->poly2Idx;
+ }
+}
+//----------------------------------------------------------------------
+
+bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range)
+{
+ OutPt* pt = outPt1;
+ //Because the polygons may be touching, we need to find a vertex that
+ //isn't touching the other polygon ...
+ if (PointOnPolygon(pt->pt, outPt2, UseFullInt64Range))
+ {
+ pt = pt->next;
+ while (pt != outPt1 && PointOnPolygon(pt->pt, outPt2, UseFullInt64Range))
+ pt = pt->next;
+ if (pt == outPt1) return true;
+ }
+ return PointInPolygon(pt->pt, outPt2, UseFullInt64Range);
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec)
+{
+
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (outRec->pts && outRec->FirstLeft == OldOutRec)
+ {
+ if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange))
+ outRec->FirstLeft = NewOutRec;
+ }
+ }
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec)
+{
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec;
+ }
+}
+//----------------------------------------------------------------------
+
+void Clipper::JoinCommonEdges()
+{
+ for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+ {
+ JoinRec* j = m_Joins[i];
+
+ OutRec *outRec1 = GetOutRec(j->poly1Idx);
+ OutRec *outRec2 = GetOutRec(j->poly2Idx);
+
+ if (!outRec1->pts || !outRec2->pts) continue;
+
+ //get the polygon fragment with the correct hole state (FirstLeft)
+ //before calling JoinPoints() ...
+ OutRec *holeStateRec;
+ if (outRec1 == outRec2) holeStateRec = outRec1;
+ else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2;
+ else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1;
+ else holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+ OutPt *p1, *p2;
+ if (!JoinPoints(j, p1, p2)) continue;
+
+ if (outRec1 == outRec2)
+ {
+ //instead of joining two polygons, we've just created a new one by
+ //splitting one polygon into two.
+ outRec1->pts = p1;
+ outRec1->bottomPt = 0;
+ outRec2 = CreateOutRec();
+ outRec2->pts = p2;
+
+ if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange))
+ {
+ //outRec2 is contained by outRec1 ...
+ outRec2->isHole = !outRec1->isHole;
+ outRec2->FirstLeft = outRec1;
+
+ FixupJoinRecs(j, p2, i+1);
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1);
+
+ FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation
+ FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs()
+
+
+ if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0))
+ ReversePolyPtLinks(outRec2->pts);
+
+ } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange))
+ {
+ //outRec1 is contained by outRec2 ...
+ outRec2->isHole = outRec1->isHole;
+ outRec1->isHole = !outRec2->isHole;
+ outRec2->FirstLeft = outRec1->FirstLeft;
+ outRec1->FirstLeft = outRec2;
+
+ FixupJoinRecs(j, p2, i+1);
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2);
+
+ FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation
+ FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs()
+
+ if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0))
+ ReversePolyPtLinks(outRec1->pts);
+ }
+ else
+ {
+ //the 2 polygons are completely separate ...
+ outRec2->isHole = outRec1->isHole;
+ outRec2->FirstLeft = outRec1->FirstLeft;
+
+ FixupJoinRecs(j, p2, i+1);
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec2
+ if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2);
+
+ FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation
+ FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs()
+ }
+
+ } else
+ {
+ //joined 2 polygons together ...
+
+ //cleanup redundant edges ...
+ FixupOutPolygon(*outRec1);
+
+ outRec2->pts = 0;
+ outRec2->bottomPt = 0;
+ outRec2->idx = outRec1->idx;
+
+ outRec1->isHole = holeStateRec->isHole;
+ if (holeStateRec == outRec2)
+ outRec1->FirstLeft = outRec2->FirstLeft;
+ outRec2->FirstLeft = outRec1;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1);
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+inline void UpdateOutPtIdxs(OutRec& outrec)
+{
+ OutPt* op = outrec.pts;
+ do
+ {
+ op->idx = outrec.idx;
+ op = op->prev;
+ }
+ while(op != outrec.pts);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DoSimplePolygons()
+{
+ PolyOutList::size_type i = 0;
+ while (i < m_PolyOuts.size())
+ {
+ OutRec* outrec = m_PolyOuts[i++];
+ OutPt* op = outrec->pts;
+ if (!op) continue;
+ do //for each Pt in Polygon until duplicate found do ...
+ {
+ OutPt* op2 = op->next;
+ while (op2 != outrec->pts)
+ {
+ if (PointsEqual(op->pt, op2->pt) && op2->next != op && op2->prev != op)
+ {
+ //split the polygon into two ...
+ OutPt* op3 = op->prev;
+ OutPt* op4 = op2->prev;
+ op->prev = op4;
+ op4->next = op;
+ op2->prev = op3;
+ op3->next = op2;
+
+ outrec->pts = op;
+ OutRec* outrec2 = CreateOutRec();
+ outrec2->pts = op2;
+ UpdateOutPtIdxs(*outrec2);
+ if (Poly2ContainsPoly1(outrec2->pts, outrec->pts, m_UseFullRange))
+ {
+ //OutRec2 is contained by OutRec1 ...
+ outrec2->isHole = !outrec->isHole;
+ outrec2->FirstLeft = outrec;
+ }
+ else
+ if (Poly2ContainsPoly1(outrec->pts, outrec2->pts, m_UseFullRange))
+ {
+ //OutRec1 is contained by OutRec2 ...
+ outrec2->isHole = outrec->isHole;
+ outrec->isHole = !outrec2->isHole;
+ outrec2->FirstLeft = outrec->FirstLeft;
+ outrec->FirstLeft = outrec2;
+ } else
+ {
+ //the 2 polygons are separate ...
+ outrec2->isHole = outrec->isHole;
+ outrec2->FirstLeft = outrec->FirstLeft;
+ }
+ op2 = op; //ie get ready for the next iteration
+ }
+ op2 = op2->next;
+ }
+ op = op->next;
+ }
+ while (op != outrec->pts);
+ }
+}
+//------------------------------------------------------------------------------
+
+void ReversePolygon(Polygon& p)
+{
+ std::reverse(p.begin(), p.end());
+}
+//------------------------------------------------------------------------------
+
+void ReversePolygons(Polygons& p)
+{
+ for (Polygons::size_type i = 0; i < p.size(); ++i)
+ ReversePolygon(p[i]);
+}
+
+//------------------------------------------------------------------------------
+// OffsetPolygon functions ...
+//------------------------------------------------------------------------------
+
+struct DoublePoint
+{
+ double X;
+ double Y;
+ DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
+};
+//------------------------------------------------------------------------------
+
+Polygon BuildArc(const IntPoint &pt,
+ const double a1, const double a2, const double r, double limit)
+{
+ //see notes in clipper.pas regarding steps
+ double arcFrac = std::fabs(a2 - a1) / (2 * pi);
+ int steps = (int)(arcFrac * pi / std::acos(1 - limit / std::fabs(r)));
+ if (steps < 2) steps = 2;
+ else if (steps > (int)(222.0 * arcFrac)) steps = (int)(222.0 * arcFrac);
+
+ double x = std::cos(a1);
+ double y = std::sin(a1);
+ double c = std::cos((a2 - a1) / steps);
+ double s = std::sin((a2 - a1) / steps);
+ Polygon result(steps +1);
+ for (int i = 0; i <= steps; ++i)
+ {
+ result[i].X = pt.X + Round(x * r);
+ result[i].Y = pt.Y + Round(y * r);
+ double x2 = x;
+ x = x * c - s * y; //cross product
+ y = x2 * s + y * c; //dot product
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2)
+{
+ if(pt2.X == pt1.X && pt2.Y == pt1.Y)
+ return DoublePoint(0, 0);
+
+ double dx = (double)(pt2.X - pt1.X);
+ double dy = (double)(pt2.Y - pt1.Y);
+ double f = 1 *1.0/ std::sqrt( dx*dx + dy*dy );
+ dx *= f;
+ dy *= f;
+ return DoublePoint(dy, -dx);
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+class OffsetBuilder
+{
+private:
+ const Polygons& m_p;
+ Polygon* m_curr_poly;
+ std::vector<DoublePoint> normals;
+ double m_delta, m_rmin, m_r;
+ size_t m_i, m_j, m_k;
+ static const int buffLength = 128;
+
+public:
+
+OffsetBuilder(const Polygons& in_polys, Polygons& out_polys,
+ bool isPolygon, double delta, JoinType jointype, EndType endtype, double limit): m_p(in_polys)
+{
+ //precondition: &out_polys != &in_polys
+
+ if (NEAR_ZERO(delta)) {out_polys = in_polys; return;}
+ m_rmin = 0.5;
+ m_delta = delta;
+ if (jointype == jtMiter)
+ {
+ if (limit > 2) m_rmin = 2.0 / (limit * limit);
+ limit = 0.25; //just in case endtype == etRound
+ }
+ else
+ {
+ if (limit <= 0) limit = 0.25;
+ else if (limit > std::fabs(delta)) limit = std::fabs(delta);
+ }
+
+ double deltaSq = delta*delta;
+ out_polys.clear();
+ out_polys.resize(m_p.size());
+ for (m_i = 0; m_i < m_p.size(); m_i++)
+ {
+ size_t len = m_p[m_i].size();
+
+ if (len == 0 || (len < 3 && delta <= 0))
+ continue;
+ else if (len == 1)
+ {
+ out_polys[m_i] = BuildArc(m_p[m_i][0], 0, 2*pi, delta, limit);
+ continue;
+ }
+
+ bool forceClose = PointsEqual(m_p[m_i][0], m_p[m_i][len -1]);
+ if (forceClose) len--;
+
+ //build normals ...
+ normals.clear();
+ normals.resize(len);
+ for (m_j = 0; m_j < len -1; ++m_j)
+ normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j +1]);
+ if (isPolygon || forceClose)
+ normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]);
+ else //is open polyline
+ normals[len-1] = normals[len-2];
+
+ m_curr_poly = &out_polys[m_i];
+ m_curr_poly->reserve(len);
+
+ if (isPolygon || forceClose)
+ {
+ m_k = len -1;
+ for (m_j = 0; m_j < len; ++m_j)
+ OffsetPoint(jointype, limit);
+
+ if (!isPolygon)
+ {
+ size_t j = out_polys.size();
+ out_polys.resize(j+1);
+ m_curr_poly = &out_polys[j];
+ m_curr_poly->reserve(len);
+ m_delta = -m_delta;
+
+ m_k = len -1;
+ for (m_j = 0; m_j < len; ++m_j)
+ OffsetPoint(jointype, limit);
+ m_delta = -m_delta;
+ ReversePolygon(*m_curr_poly);
+ }
+ }
+ else //is open polyline
+ {
+ //offset the polyline going forward ...
+ m_k = 0;
+ for (m_j = 1; m_j < len -1; ++m_j)
+ OffsetPoint(jointype, limit);
+
+ //handle the end (butt, round or square) ...
+ IntPoint pt1;
+ if (endtype == etButt)
+ {
+ m_j = len - 1;
+ pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta));
+ AddPoint(pt1);
+ pt1 = IntPoint(Round(m_p[m_i][m_j].X - normals[m_j].X * m_delta),
+ Round(m_p[m_i][m_j].Y - normals[m_j].Y * m_delta));
+ AddPoint(pt1);
+ }
+ else
+ {
+ m_j = len - 1;
+ m_k = len - 2;
+ normals[m_j].X = -normals[m_j].X;
+ normals[m_j].Y = -normals[m_j].Y;
+ if (endtype == etSquare) DoSquare();
+ else DoRound(limit);
+ }
+
+ //re-build Normals ...
+ for (int j = len - 1; j > 0; --j)
+ {
+ normals[j].X = -normals[j - 1].X;
+ normals[j].Y = -normals[j - 1].Y;
+ }
+ normals[0].X = -normals[1].X;
+ normals[0].Y = -normals[1].Y;
+
+ //offset the polyline going backward ...
+ m_k = len -1;
+ for (m_j = m_k - 1; m_j > 0; --m_j)
+ OffsetPoint(jointype, limit);
+
+ //finally handle the start (butt, round or square) ...
+ if (endtype == etButt)
+ {
+ pt1 = IntPoint(Round(m_p[m_i][0].X - normals[0].X * m_delta),
+ Round(m_p[m_i][0].Y - normals[0].Y * m_delta));
+ AddPoint(pt1);
+ pt1 = IntPoint(Round(m_p[m_i][0].X + normals[0].X * m_delta),
+ Round(m_p[m_i][0].Y + normals[0].Y * m_delta));
+ AddPoint(pt1);
+ } else
+ {
+ m_k = 1;
+ if (endtype == etSquare) DoSquare();
+ else DoRound(limit);
+ }
+ }
+ }
+
+ //and clean up untidy corners using Clipper ...
+ Clipper clpr;
+ clpr.AddPolygons(out_polys, ptSubject);
+ if (delta > 0)
+ {
+ if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive))
+ out_polys.clear();
+ }
+ else
+ {
+ IntRect r = clpr.GetBounds();
+ Polygon outer(4);
+ outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+ outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+ outer[2] = IntPoint(r.right + 10, r.top - 10);
+ outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+ clpr.AddPolygon(outer, ptSubject);
+ clpr.ReverseSolution(true);
+ if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative))
+ out_polys.erase(out_polys.begin());
+ else
+ out_polys.clear();
+ }
+}
+//------------------------------------------------------------------------------
+
+private:
+
+void OffsetPoint(JoinType jointype, double limit)
+{
+ switch (jointype)
+ {
+ case jtMiter:
+ {
+ m_r = 1 + (normals[m_j].X*normals[m_k].X +
+ normals[m_j].Y*normals[m_k].Y);
+ if (m_r >= m_rmin) DoMiter(); else DoSquare();
+ break;
+ }
+ case jtSquare: DoSquare(); break;
+ case jtRound: DoRound(limit); break;
+ }
+ m_k = m_j;
+}
+//------------------------------------------------------------------------------
+
+void AddPoint(const IntPoint& pt)
+{
+ if (m_curr_poly->size() == m_curr_poly->capacity())
+ m_curr_poly->reserve(m_curr_poly->capacity() + buffLength);
+ m_curr_poly->push_back(pt);
+}
+//------------------------------------------------------------------------------
+
+void DoSquare()
+{
+ IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta));
+ IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta));
+ if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0)
+ {
+ double a1 = std::atan2(normals[m_k].Y, normals[m_k].X);
+ double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X);
+ a1 = std::fabs(a2 - a1);
+ if (a1 > pi) a1 = pi * 2 - a1;
+ double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta);
+ pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), (long64)(pt1.Y + normals[m_k].X * dx));
+ AddPoint(pt1);
+ pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), (long64)(pt2.Y -normals[m_j].X * dx));
+ AddPoint(pt2);
+ }
+ else
+ {
+ AddPoint(pt1);
+ AddPoint(m_p[m_i][m_j]);
+ AddPoint(pt2);
+ }
+}
+//------------------------------------------------------------------------------
+
+void DoMiter()
+{
+ if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0)
+ {
+ double q = m_delta / m_r;
+ AddPoint(IntPoint(Round(m_p[m_i][m_j].X + (normals[m_k].X + normals[m_j].X) * q),
+ Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q)));
+ }
+ else
+ {
+ IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta));
+ IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta));
+ AddPoint(pt1);
+ AddPoint(m_p[m_i][m_j]);
+ AddPoint(pt2);
+ }
+}
+//------------------------------------------------------------------------------
+
+void DoRound(double limit)
+{
+ IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta));
+ IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta),
+ Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta));
+ AddPoint(pt1);
+ //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg).
+ if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0)
+ {
+ if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985)
+ {
+ double a1 = std::atan2(normals[m_k].Y, normals[m_k].X);
+ double a2 = std::atan2(normals[m_j].Y, normals[m_j].X);
+ if (m_delta > 0 && a2 < a1) a2 += pi *2;
+ else if (m_delta < 0 && a2 > a1) a2 -= pi *2;
+ Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta, limit);
+ for (Polygon::size_type m = 0; m < arc.size(); m++)
+ AddPoint(arc[m]);
+ }
+ }
+ else
+ AddPoint(m_p[m_i][m_j]);
+ AddPoint(pt2);
+}
+//--------------------------------------------------------------------------
+
+}; //end PolyOffsetBuilder
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt)
+{
+ if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X))
+ {
+ botPt = pt;
+ return true;
+ }
+ else return false;
+}
+//--------------------------------------------------------------------------
+
+void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys,
+ double delta, JoinType jointype, double limit, bool autoFix)
+{
+ if (!autoFix && &in_polys != &out_polys)
+ {
+ OffsetBuilder(in_polys, out_polys, true, delta, jointype, etClosed, limit);
+ return;
+ }
+
+ Polygons inPolys = Polygons(in_polys);
+ out_polys.clear();
+
+ //ChecksInput - fixes polygon orientation if necessary and removes
+ //duplicate vertices. Can be set false when you're sure that polygon
+ //orientation is correct and that there are no duplicate vertices.
+ if (autoFix)
+ {
+ size_t polyCount = inPolys.size(), botPoly = 0;
+ while (botPoly < polyCount && inPolys[botPoly].empty()) botPoly++;
+ if (botPoly == polyCount) return;
+
+ //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point
+ //This point (on m_p[botPoly]) must be on an outer polygon ring and if
+ //its orientation is false (counterclockwise) then assume all polygons
+ //need reversing ...
+ IntPoint botPt = inPolys[botPoly][0];
+ for (size_t i = botPoly; i < polyCount; ++i)
+ {
+ if (inPolys[i].size() < 3) { inPolys[i].clear(); continue; }
+ if (UpdateBotPt(inPolys[i][0], botPt)) botPoly = i;
+ Polygon::iterator it = inPolys[i].begin() +1;
+ while (it != inPolys[i].end())
+ {
+ if (PointsEqual(*it, *(it -1)))
+ it = inPolys[i].erase(it);
+ else
+ {
+ if (UpdateBotPt(*it, botPt)) botPoly = i;
+ ++it;
+ }
+ }
+ }
+ if (!Orientation(inPolys[botPoly]))
+ ReversePolygons(inPolys);
+ }
+ OffsetBuilder(inPolys, out_polys, true, delta, jointype, etClosed, limit);
+}
+//------------------------------------------------------------------------------
+
+void OffsetPolyLines(const Polygons &in_lines, Polygons &out_lines,
+ double delta, JoinType jointype, EndType endtype,
+ double limit, bool autoFix)
+{
+ if (!autoFix && endtype != etClosed && &in_lines != &out_lines)
+ {
+ OffsetBuilder(in_lines, out_lines, false, delta, jointype, endtype, limit);
+ return;
+ }
+
+ Polygons inLines = Polygons(in_lines);
+ if (autoFix)
+ for (size_t i = 0; i < inLines.size(); ++i)
+ {
+ if (inLines[i].size() < 2) { inLines[i].clear(); continue; }
+ Polygon::iterator it = inLines[i].begin() +1;
+ while (it != inLines[i].end())
+ {
+ if (PointsEqual(*it, *(it -1)))
+ it = inLines[i].erase(it);
+ else
+ ++it;
+ }
+ }
+
+ if (endtype == etClosed)
+ {
+ size_t sz = inLines.size();
+ inLines.resize(sz * 2);
+ for (size_t i = 0; i < sz; ++i)
+ {
+ inLines[sz+i] = inLines[i];
+ ReversePolygon(inLines[sz+i]);
+ }
+ OffsetBuilder(inLines, out_lines, true, delta, jointype, endtype, limit);
+ }
+ else
+ OffsetBuilder(inLines, out_lines, false, delta, jointype, endtype, limit);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType)
+{
+ Clipper c;
+ c.ForceSimple(true);
+ c.AddPolygon(in_poly, ptSubject);
+ c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType)
+{
+ Clipper c;
+ c.ForceSimple(true);
+ c.AddPolygons(in_polys, ptSubject);
+ c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(Polygons &polys, PolyFillType fillType)
+{
+ SimplifyPolygons(polys, polys, fillType);
+}
+//------------------------------------------------------------------------------
+
+inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2)
+{
+ double dx = ((double)pt1.X - pt2.X);
+ double dy = ((double)pt1.Y - pt2.Y);
+ return (dx*dx + dy*dy);
+}
+//------------------------------------------------------------------------------
+
+DoublePoint ClosestPointOnLine(const IntPoint& pt, const IntPoint& linePt1, const IntPoint& linePt2)
+{
+ double dx = ((double)linePt2.X - linePt1.X);
+ double dy = ((double)linePt2.Y - linePt1.Y);
+ if (dx == 0 && dy == 0)
+ return DoublePoint((double)linePt1.X, (double)linePt1.Y);
+ double q = ((pt.X-linePt1.X)*dx + (pt.Y-linePt1.Y)*dy) / (dx*dx + dy*dy);
+ return DoublePoint(
+ (1-q)*linePt1.X + q*linePt2.X,
+ (1-q)*linePt1.Y + q*linePt2.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesNearColinear(const IntPoint& pt1,
+ const IntPoint& pt2, const IntPoint& pt3, double distSqrd)
+{
+ if (DistanceSqrd(pt1, pt2) > DistanceSqrd(pt1, pt3)) return false;
+ DoublePoint cpol = ClosestPointOnLine(pt2, pt1, pt3);
+ double dx = pt2.X - cpol.X;
+ double dy = pt2.Y - cpol.Y;
+ return (dx*dx + dy*dy) < distSqrd;
+}
+//------------------------------------------------------------------------------
+
+bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd)
+{
+ double dx = (double)pt1.X - pt2.X;
+ double dy = (double)pt1.Y - pt2.Y;
+ return ((dx * dx) + (dy * dy) <= distSqrd);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(const Polygon& in_poly, Polygon& out_poly, double distance)
+{
+ //distance = proximity in units/pixels below which vertices
+ //will be stripped. Default ~= sqrt(2).
+ int highI = in_poly.size() -1;
+ double distSqrd = distance * distance;
+ while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], distSqrd)) highI--;
+ if (highI < 2) { out_poly.clear(); return; }
+
+ if (&in_poly != &out_poly)
+ out_poly.resize(highI + 1);
+
+ IntPoint pt = in_poly[highI];
+ int i = 0, k = 0;
+ for (;;)
+ {
+ while (i < highI && PointsAreClose(pt, in_poly[i+1], distSqrd)) i+=2;
+ int i2 = i;
+ while (i < highI && (PointsAreClose(in_poly[i], in_poly[i+1], distSqrd) ||
+ SlopesNearColinear(pt, in_poly[i], in_poly[i+1], distSqrd))) i++;
+ if (i >= highI) break;
+ else if (i != i2) continue;
+ pt = in_poly[i++];
+ out_poly[k++] = pt;
+ }
+ if (i <= highI) out_poly[k++] = in_poly[i];
+ if (k > 2 && SlopesNearColinear(out_poly[k -2], out_poly[k -1], out_poly[0], distSqrd)) k--;
+ if (k < 3) out_poly.clear();
+ else if (k <= highI) out_poly.resize(k);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(const Polygons& in_polys, Polygons& out_polys, double distance)
+{
+ for (Polygons::size_type i = 0; i < in_polys.size(); ++i)
+ CleanPolygon(in_polys[i], out_polys[i], distance);
+}
+//------------------------------------------------------------------------------
+
+void AddPolyNodeToPolygons(const PolyNode& polynode, Polygons& polygons)
+{
+ if (!polynode.Contour.empty())
+ polygons.push_back(polynode.Contour);
+ for (int i = 0; i < polynode.ChildCount(); ++i)
+ AddPolyNodeToPolygons(*polynode.Childs[i], polygons);
+}
+//------------------------------------------------------------------------------
+
+void PolyTreeToPolygons(const PolyTree& polytree, Polygons& polygons)
+{
+ polygons.resize(0);
+ polygons.reserve(polytree.Total());
+ AddPolyNodeToPolygons(polytree, polygons);
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, IntPoint& p)
+{
+ s << p.X << ' ' << p.Y << "\n";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, Polygon &p)
+{
+ for (Polygon::size_type i = 0; i < p.size(); i++)
+ s << p[i];
+ s << "\n";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, Polygons &p)
+{
+ for (Polygons::size_type i = 0; i < p.size(); i++)
+ s << p[i];
+ s << "\n";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
diff --git a/hyp2mat/lib/clipper.hpp b/hyp2mat/lib/clipper.hpp new file mode 100644 index 0000000..4dcfe12 --- /dev/null +++ b/hyp2mat/lib/clipper.hpp @@ -0,0 +1,351 @@ +/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 5.1.6 *
+* Date : 23 May 2013 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2013 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+* Attributions: *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping" *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
+* http://portal.acm.org/citation.cfm?id=129906 *
+* *
+* Computer graphics and geometric modeling: implementation and algorithms *
+* By Max K. Agoston *
+* Springer; 1 edition (January 4, 2005) *
+* http://books.google.com/books?q=vatti+clipping+agoston *
+* *
+* See also: *
+* "Polygon Offsetting by Computing Winding Numbers" *
+* Paper no. DETC2005-85513 pp. 565-575 *
+* ASME 2005 International Design Engineering Technical Conferences *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
+* September 24-28, 2005 , Long Beach, California, USA *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
+* *
+*******************************************************************************/
+
+#ifndef clipper_hpp
+#define clipper_hpp
+
+#define CLIPPER_VERSION "5.1.6"
+
+#include <vector>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+
+namespace ClipperLib {
+
+enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
+enum PolyType { ptSubject, ptClip };
+//By far the most widely used winding rules for polygon filling are
+//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
+//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
+//see http://glprogramming.com/red/chapter11.html
+enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
+
+typedef signed long long long64;
+typedef unsigned long long ulong64;
+
+struct IntPoint {
+public:
+ long64 X;
+ long64 Y;
+ IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {};
+ friend std::ostream& operator <<(std::ostream &s, IntPoint &p);
+};
+
+typedef std::vector< IntPoint > Polygon;
+typedef std::vector< Polygon > Polygons;
+
+
+std::ostream& operator <<(std::ostream &s, Polygon &p);
+std::ostream& operator <<(std::ostream &s, Polygons &p);
+
+class PolyNode;
+typedef std::vector< PolyNode* > PolyNodes;
+
+class PolyNode
+{
+public:
+ PolyNode();
+ Polygon Contour;
+ PolyNodes Childs;
+ PolyNode* Parent;
+ PolyNode* GetNext() const;
+ bool IsHole() const;
+ int ChildCount() const;
+private:
+ PolyNode* GetNextSiblingUp() const;
+ unsigned Index; //node index in Parent.Childs
+ void AddChild(PolyNode& child);
+ friend class Clipper; //to access Index
+};
+
+class PolyTree: public PolyNode
+{
+public:
+ ~PolyTree(){Clear();};
+ PolyNode* GetFirst() const;
+ void Clear();
+ int Total() const;
+private:
+ PolyNodes AllNodes;
+ friend class Clipper; //to access AllNodes
+};
+
+enum JoinType { jtSquare, jtRound, jtMiter };
+enum EndType { etClosed, etButt, etSquare, etRound};
+
+bool Orientation(const Polygon &poly);
+double Area(const Polygon &poly);
+
+void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys,
+ double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true);
+
+void OffsetPolyLines(const Polygons &in_lines, Polygons &out_lines,
+ double delta, JoinType jointype = jtSquare, EndType endtype = etSquare, double limit = 0, bool autoFix = true);
+
+void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd);
+
+void CleanPolygon(const Polygon& in_poly, Polygon& out_poly, double distance = 1.415);
+void CleanPolygons(const Polygons& in_polys, Polygons& out_polys, double distance = 1.415);
+
+void PolyTreeToPolygons(const PolyTree& polytree, Polygons& polygons);
+
+void ReversePolygon(Polygon& p);
+void ReversePolygons(Polygons& p);
+
+//used internally ...
+enum EdgeSide { esLeft = 1, esRight = 2};
+enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 };
+//inline IntersectProtects operator|(IntersectProtects a, IntersectProtects b)
+//{return static_cast<IntersectProtects>(static_cast<int>(a) | static_cast<int>(b));}
+
+struct TEdge {
+ long64 xbot;
+ long64 ybot;
+ long64 xcurr;
+ long64 ycurr;
+ long64 xtop;
+ long64 ytop;
+ double dx;
+ long64 deltaX;
+ long64 deltaY;
+ PolyType polyType;
+ EdgeSide side;
+ int windDelta; //1 or -1 depending on winding direction
+ int windCnt;
+ int windCnt2; //winding count of the opposite polytype
+ int outIdx;
+ TEdge *next;
+ TEdge *prev;
+ TEdge *nextInLML;
+ TEdge *nextInAEL;
+ TEdge *prevInAEL;
+ TEdge *nextInSEL;
+ TEdge *prevInSEL;
+};
+
+struct IntersectNode {
+ TEdge *edge1;
+ TEdge *edge2;
+ IntPoint pt;
+ IntersectNode *next;
+};
+
+struct LocalMinima {
+ long64 Y;
+ TEdge *leftBound;
+ TEdge *rightBound;
+ LocalMinima *next;
+};
+
+struct Scanbeam {
+ long64 Y;
+ Scanbeam *next;
+};
+
+struct OutPt; //forward declaration
+
+struct OutRec {
+ int idx;
+ bool isHole;
+ OutRec *FirstLeft; //see comments in clipper.pas
+ PolyNode *polyNode;
+ OutPt *pts;
+ OutPt *bottomPt;
+};
+
+struct OutPt {
+ int idx;
+ IntPoint pt;
+ OutPt *next;
+ OutPt *prev;
+};
+
+struct JoinRec {
+ IntPoint pt1a;
+ IntPoint pt1b;
+ int poly1Idx;
+ IntPoint pt2a;
+ IntPoint pt2b;
+ int poly2Idx;
+};
+
+struct HorzJoinRec {
+ TEdge *edge;
+ int savedIdx;
+};
+
+struct IntRect { long64 left; long64 top; long64 right; long64 bottom; };
+
+typedef std::vector < OutRec* > PolyOutList;
+typedef std::vector < TEdge* > EdgeList;
+typedef std::vector < JoinRec* > JoinList;
+typedef std::vector < HorzJoinRec* > HorzJoinList;
+
+//ClipperBase is the ancestor to the Clipper class. It should not be
+//instantiated directly. This class simply abstracts the conversion of sets of
+//polygon coordinates into edge objects that are stored in a LocalMinima list.
+class ClipperBase
+{
+public:
+ ClipperBase();
+ virtual ~ClipperBase();
+ bool AddPolygon(const Polygon &pg, PolyType polyType);
+ bool AddPolygons( const Polygons &ppg, PolyType polyType);
+ virtual void Clear();
+ IntRect GetBounds();
+protected:
+ void DisposeLocalMinimaList();
+ TEdge* AddBoundsToLML(TEdge *e);
+ void PopLocalMinima();
+ virtual void Reset();
+ void InsertLocalMinima(LocalMinima *newLm);
+ LocalMinima *m_CurrentLM;
+ LocalMinima *m_MinimaList;
+ bool m_UseFullRange;
+ EdgeList m_edges;
+};
+
+class Clipper : public virtual ClipperBase
+{
+public:
+ Clipper();
+ ~Clipper();
+ bool Execute(ClipType clipType,
+ Polygons &solution,
+ PolyFillType subjFillType = pftEvenOdd,
+ PolyFillType clipFillType = pftEvenOdd);
+ bool Execute(ClipType clipType,
+ PolyTree &polytree,
+ PolyFillType subjFillType = pftEvenOdd,
+ PolyFillType clipFillType = pftEvenOdd);
+ void Clear();
+ bool ReverseSolution() {return m_ReverseOutput;};
+ void ReverseSolution(bool value) {m_ReverseOutput = value;};
+ bool ForceSimple() {return m_ForceSimple;};
+ void ForceSimple(bool value) {m_ForceSimple = value;};
+protected:
+ void Reset();
+ virtual bool ExecuteInternal();
+private:
+ PolyOutList m_PolyOuts;
+ JoinList m_Joins;
+ HorzJoinList m_HorizJoins;
+ ClipType m_ClipType;
+ Scanbeam *m_Scanbeam;
+ TEdge *m_ActiveEdges;
+ TEdge *m_SortedEdges;
+ IntersectNode *m_IntersectNodes;
+ bool m_ExecuteLocked;
+ PolyFillType m_ClipFillType;
+ PolyFillType m_SubjFillType;
+ bool m_ReverseOutput;
+ bool m_UsingPolyTree;
+ bool m_ForceSimple;
+ void DisposeScanbeamList();
+ void SetWindingCount(TEdge& edge);
+ bool IsEvenOddFillType(const TEdge& edge) const;
+ bool IsEvenOddAltFillType(const TEdge& edge) const;
+ void InsertScanbeam(const long64 Y);
+ long64 PopScanbeam();
+ void InsertLocalMinimaIntoAEL(const long64 botY);
+ void InsertEdgeIntoAEL(TEdge *edge);
+ void AddEdgeToSEL(TEdge *edge);
+ void CopyAELToSEL();
+ void DeleteFromSEL(TEdge *e);
+ void DeleteFromAEL(TEdge *e);
+ void UpdateEdgeIntoAEL(TEdge *&e);
+ void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
+ bool IsContributing(const TEdge& edge) const;
+ bool IsTopHorz(const long64 XPos);
+ void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
+ void DoMaxima(TEdge *e, long64 topY);
+ void ProcessHorizontals();
+ void ProcessHorizontal(TEdge *horzEdge);
+ void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+ void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+ OutRec* GetOutRec(int idx);
+ void AppendPolygon(TEdge *e1, TEdge *e2);
+ void IntersectEdges(TEdge *e1, TEdge *e2,
+ const IntPoint &pt, const IntersectProtects protects);
+ OutRec* CreateOutRec();
+ void AddOutPt(TEdge *e, const IntPoint &pt);
+ void DisposeAllPolyPts();
+ void DisposeOutRec(PolyOutList::size_type index);
+ bool ProcessIntersections(const long64 botY, const long64 topY);
+ void InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt);
+ void BuildIntersectList(const long64 botY, const long64 topY);
+ void ProcessIntersectList();
+ void ProcessEdgesAtTopOfScanbeam(const long64 topY);
+ void BuildResult(Polygons& polys);
+ void BuildResult2(PolyTree& polytree);
+ void SetHoleState(TEdge *e, OutRec *outrec);
+ void DisposeIntersectNodes();
+ bool FixupIntersectionOrder();
+ void FixupOutPolygon(OutRec &outrec);
+ bool IsHole(TEdge *e);
+ void FixHoleLinkage(OutRec &outrec);
+ void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1);
+ void ClearJoins();
+ void AddHorzJoin(TEdge *e, int idx);
+ void ClearHorzJoins();
+ bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2);
+ void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx);
+ void JoinCommonEdges();
+ void DoSimplePolygons();
+ void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
+ void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
+};
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+class clipperException : public std::exception
+{
+ public:
+ clipperException(const char* description): m_descr(description) {}
+ virtual ~clipperException() throw() {}
+ virtual const char* what() const throw() {return m_descr.c_str();}
+ private:
+ std::string m_descr;
+};
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
+
+#endif //clipper_hpp
+
+
diff --git a/hyp2mat/lib/clipper.txt b/hyp2mat/lib/clipper.txt new file mode 100644 index 0000000..51acabf --- /dev/null +++ b/hyp2mat/lib/clipper.txt @@ -0,0 +1,29 @@ +The Clipper code library, the "Software" (that includes Delphi, C++ & C#
+source code, accompanying samples and documentation), has been released
+under the following license, terms and conditions:
+
+Boost Software License - Version 1.0 - August 17th, 2003
+http://www.boost.org/LICENSE_1_0.txt
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
diff --git a/hyp2mat/lib/copper.cc b/hyp2mat/lib/copper.cc new file mode 100644 index 0000000..3b5addd --- /dev/null +++ b/hyp2mat/lib/copper.cc @@ -0,0 +1,382 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "hyperlynx.h" +#include "crop.h" +#include <iostream> +#include <algorithm> + +using namespace Hyp2Mat; + +/* + * These routines calculate the copper layers. + * The routines operate in one of two modes: raw or normal. + * + * In raw mode, polygons are simply copied from input to output. No processing is done. Raw mode is mainly useful for debugging purposes. + * + * In normal mode, all copper on a single layer is calculated as a set of non-overlapping polygons. + * Two kinds of copper polygons exist: normal and "PLANE". + * + * Normal copper polygons are simply added to the copper of the plane. + * Copper polygons of type "PLANE" have a clearance from other nets. This clearance is the "plane separation". + * Plane separation can be specified for the board, for a layer, for a net or for a polygon. + * + * Copper polygons of type "PLANE" are calculated as follows: + * - all polygons of all other nets on the same layer are computed, and explanded by the plane separation. This produces the mask. + * - The mask is subtracted from the "PLANE" type polygon. + * + * If mask polygon and plane polygon have a different value for plane separation the largest value is used. + * + * Note there is no plane separation between polygons of the same net. + */ + +/* + * copy layer stackup + */ + +void HyperLynx::CopyStackUp(Hyp2Mat::PCB& pcb) +{ + /* iterate over all layers */ + for (HypFile::LayerList::iterator l = hyp_file.stackup.begin(); l != hyp_file.stackup.end(); ++l) { + + /* check if we're interested in this layer. if no layers are specified, copy all layers */ + bool layer_wanted = layers.empty() || (std::find(layers.begin(), layers.end(), l->layer_name) != layers.end()); + + /* copy this layer if needed */ + if (layer_wanted) CopyLayer(pcb, *l); + } + + return; +} + +/* + * copy layer + */ + +void HyperLynx::CopyLayer(Hyp2Mat::PCB& pcb, HypFile::Layer& hyp_layer) +{ + + Hyp2Mat::Layer layer; + layer.layer_name = hyp_layer.layer_name; + layer.material_name = hyp_layer.material_name; + switch (hyp_layer.layer_type) { + case HypFile::LAYER_SIGNAL : layer.layer_type = Hyp2Mat::LAYER_SIGNAL; + break; + case HypFile::LAYER_DIELECTRIC : layer.layer_type = Hyp2Mat::LAYER_DIELECTRIC; + break; + case HypFile::LAYER_PLANE : layer.layer_type = Hyp2Mat::LAYER_PLANE; + break; + default: layer.layer_type = Hyp2Mat::LAYER_SIGNAL; + break; + } + layer.z0 = hyp_layer.z0; + layer.z1 = hyp_layer.z1; + layer.thickness = hyp_layer.thickness; + layer.epsilon_r = hyp_layer.epsilon_r; + layer.loss_tangent = hyp_layer.loss_tangent; + layer.bulk_resistivity = hyp_layer.bulk_resistivity; + layer.resistivity_temp_coeff = hyp_layer.temperature_coefficient; + + /* if the layer is metallic, copy the metal */ + if (layer.layer_type != Hyp2Mat::LAYER_DIELECTRIC) { + + /* Use default plane separation if layer plane separation is not set. layer plane separation is -1.0 if not set */ + layer_plane_separation = hyp_file.board.plane_separation; + + /* board-level override from SetClearance method / --clearance command-line parameter */ + if (HyperLynx::clearance >= 0) layer_plane_separation = HyperLynx::clearance; + + if (hyp_layer.plane_separation >= 0) layer_plane_separation = hyp_layer.plane_separation; + + /* copy layer copper */ + Hyp2Mat::FloatPolygons raw_polygons; + Hyp2Mat::Polygon layer_copper = CopyCopper(layer, raw_polygons); + + if (raw) + layer.metal = raw_polygons; + else + layer.metal = layer_copper.Result(); /* calculate layer copper */ + } + + /* add to stackup */ + pcb.stackup.push_back(layer); + + return; +} + +/* + * copy copper + */ + +Hyp2Mat::Polygon HyperLynx::CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolygons& raw_polygons) +{ + + /* vectors of different polygon types. element [i] belongs to net hyp_file.net[i].net_name */ + std::vector<Hyp2Mat::Polygon> net_pour; /* POLYGON T=POUR and ordinary line and arc segments */ + std::vector<Hyp2Mat::Polygon> net_plane; /* POLYGON T=PLANE */ + std::vector<Hyp2Mat::Polygon> net_copper; /* POLYGON T=COPPER */ + std::vector<Hyp2Mat::Polygon> net_pads; /* pads */ + std::vector<Hyp2Mat::Polygon> net_antipads; /* anti-pads */ + std::vector<bool> net_wanted; /* true if net needs to be included in the output */ + + HypFile::NetList::size_type net_size = hyp_file.net.size(); + net_pour.resize(net_size); + net_plane.resize(net_size); + net_copper.resize(net_size); + net_pads.resize(net_size); + net_antipads.resize(net_size); + net_wanted.resize(net_size); + + /* iterate over all nets twice. + The first time we calculate all the copper except "plane" polygons. + The second time we calculate plane polygon pours. Plane polygons have a clearance from copper of other nets */ + + /* Determine which nets we need to include */ + for (int i = 0; i < hyp_file.net.size(); ++i) { + /* check if we're interested in this net. if no nets are specified, copy all nets */ + net_wanted[i] = nets.empty() || (std::find(nets.begin(), nets.end(), hyp_file.net[i].net_name) != nets.end()); + } + + /* calculate all copper. */ + for (int i = 0; i < hyp_file.net.size(); ++i) { + + /* skip unwanted nets */ + if (!net_wanted[i]) continue; + + /* copy anti-metal of this net */ + net_pour[i] = CopyNet(layer, hyp_file.net[i], POLYGON_TYPE_POUR, raw_polygons); + net_copper[i] = CopyNet(layer, hyp_file.net[i], POLYGON_TYPE_COPPER, raw_polygons); + net_plane[i] = CopyNet(layer, hyp_file.net[i], POLYGON_TYPE_PLANE, raw_polygons); + net_pads[i] = CopyNet(layer, hyp_file.net[i], POLYGON_TYPE_PAD, raw_polygons); + net_antipads[i] = CopyNet(layer, hyp_file.net[i], POLYGON_TYPE_ANTIPAD, raw_polygons); + } + + /* + * sum all copper on this layer + */ + + Hyp2Mat::Polygon layer_copper; + + /* add plane and pour polygons */ + for (int i = 0; i < hyp_file.net.size(); ++i) { + + /* skip unwanted nets */ + if (!net_wanted[i]) continue; + + layer_copper.Union(net_pour[i]); + layer_copper.Union(net_plane[i]); + } + + /* subtract antipads from pour and plane polygons. */ + for (int i = 0; i < hyp_file.net.size(); ++i) { + + /* skip unwanted nets */ + if (!net_wanted[i]) continue; + + layer_copper.Difference(net_antipads[i]); + } + + /* add copper and pad polygons */ + for (int i = 0; i < hyp_file.net.size(); ++i) { + + /* skip unwanted nets */ + if (!net_wanted[i]) continue; + + layer_copper.Union(net_copper[i]); + layer_copper.Union(net_pads[i]); + } + + /* + * Crop to board size + */ + + if (!board.IsEmpty()) { + layer_copper.Intersection(board); + layer_copper = CropPolygon(layer_copper, bounds); + } + + return layer_copper; +} + +/* + * copy net + * copy all polygons of a net. + * if anti is true, copy anti-pads + * if plane is true, copy plane polygons (and subtract other_nets) + */ + +Hyp2Mat::Polygon HyperLynx::CopyNet(Hyp2Mat::Layer layer, HypFile::Net& hyp_net, polygon_type_enum poly_type, Hyp2Mat::FloatPolygons& raw_polygons) +{ + Hyp2Mat::Polygon net_copper; + + /* calculate net-specific plane separation */ + net_plane_separation = layer_plane_separation; + if (hyp_net.plane_separation >= 0) net_plane_separation = hyp_net.plane_separation; + + /* iterate over all Hyperlynx polygon id's */ + for (HypFile::PolygonMap::iterator j = hyp_net.metal.begin(); j != hyp_net.metal.end(); ++j) { + + if (j->second.empty()) continue; + + /* polygon layer */ + std::string poly_layer_name = j->second.begin()->layer_name; + + /* this polygon is on our layer if the polygon's layer name is the current layer's name.*/ + if (poly_layer_name != layer.layer_name) continue; + + /* Check polygon is the correct type (pour, plane, copper, pad or antipad) */ + if (j->second.begin()->polygon_type != poly_type) continue; + + /* Join all Hyperlynx polygons/polyvoids with the same id in a single Hyp2Mat polygon */ + Hyp2Mat::Polygon poly_copper = CopyPolygon(j->second, raw_polygons); + net_copper.Union(poly_copper); + } + + return net_copper; +} + +/* + * copy polygon + * polygon is copied twice; once to the list of "raw" polygons for --raw output; + * once added to this layers' copper for normal output. + */ + +Hyp2Mat::Polygon HyperLynx::CopyPolygon(HypFile::PolygonList metal, Hyp2Mat::FloatPolygons& raw_polygons) +{ + Hyp2Mat::Polygon poly; + + if (metal.empty()) return poly; /* ought never to happen */ + + /* Join all Hyperlynx polygons/polyvoids with the same id in a single Hyp2Mat polygon */ + for (HypFile::PolygonList::iterator k = metal.begin(); k != metal.end(); ++k) { + + /* copy vertices */ + Hyp2Mat::FloatPolygon edge; + for (HypFile::PointList::iterator v = k->vertex.begin(); v != k->vertex.end(); ++v) + edge.push_back(Hyp2Mat::FloatPoint(v->x, v->y)); + + /* fix orientation */ + if (IsClockwise(edge) != k->positive) Reverse(edge); + + /* add to raw polygons */ + Hyp2Mat::FloatPoly raw_poly; + raw_poly.poly = edge; + raw_poly.is_hole = !k->positive; + raw_poly.nesting_level = 0; + raw_polygons.push_back(raw_poly); + + /* add to cooked polygons */ + if (k->positive) poly.AddEdge(edge); + else poly.AddHole(edge); + } + + /* take line width of polygon into account */ + double width = metal.begin()->width; + poly.Simplify(); + poly.Offset(width/2); + + if (metal.front().polygon_type == POLYGON_TYPE_PLANE) { + /* calculate polygon-specific plane separation */ + polygon_plane_separation = net_plane_separation; + if (metal.front().plane_separation >= 0) polygon_plane_separation = metal.front().plane_separation; + + /* calculate mask needed to get clearance with other nets */ + Hyp2Mat::Polygon mask; + mask = PlaneSeparation(metal.front().layer_name, metal.front().net_name); + + /* subtract mask from polygon */ + poly.Difference(mask); + } + + return poly; +} + +/* + * Creates the mask by which to trim a plane polygon, taking into account plane separation. + * + * Algorithm: sum all polygons on layer 'layer_name' belonging to nets other than net 'net_name', + * expanded by the biggest polygon_plane separation. + */ + +Hyp2Mat::Polygon HyperLynx::PlaneSeparation(std::string layer_name, std::string net_name) +{ + Hyp2Mat::Polygon mask; + + if (polygon_plane_separation < 0) return mask; // XXX correct? + + /* iterate over all nets */ + for (HypFile::NetList::iterator i = hyp_file.net.begin(); i != hyp_file.net.end(); ++i) { + + /* all nets except net 'net_name' */ + if (i->net_name == net_name) continue; + + /* wanted nets only */ + bool net_wanted = nets.empty() || (std::find(nets.begin(), nets.end(), net_name) != nets.end()); + if (!net_wanted) continue; + + /* iterate over all Hyperlynx polygon id's */ + for (HypFile::PolygonMap::iterator j = i->metal.begin(); j != i->metal.end(); ++j) { + Hyp2Mat::Polygon poly; + + if (j->second.empty()) continue; /* ought never to happen */ + + /* polygons on layer 'layer_name' only */ + if (j->second.begin()->layer_name != layer_name) continue; + + /* iterate over all edges */ + for (HypFile::PolygonList::iterator k = j->second.begin(); k != j->second.end(); ++k) { + + /* iterate over all vertices */ + Hyp2Mat::FloatPolygon edge; + for (HypFile::PointList::iterator v = k->vertex.begin(); v != k->vertex.end(); ++v) + edge.push_back(Hyp2Mat::FloatPoint(v->x, v->y)); + + /* add edge to polygon */ + if (k->positive) poly.AddEdge(edge); + else poly.AddHole(edge); + } + + double width = j->second.begin()->width; + + /* calculate plane separation of the masking polygon */ + double mask_plane_separation; + mask_plane_separation = layer_plane_separation; /* layer */ + if (i->plane_separation >= 0) mask_plane_separation = i->plane_separation; /* net */ + if (j->second.begin()->plane_separation >= 0) mask_plane_separation = j->second.begin()->plane_separation; /* polygon */ + + /* compare plane separation of polygon and masking polygon; choose biggest plane separation. */ + double biggest_plane_separation; + if (polygon_plane_separation > mask_plane_separation) biggest_plane_separation = polygon_plane_separation; + else biggest_plane_separation = mask_plane_separation; + + /* grow masking polygon by plane separation */ + poly.Offset(width/2 + biggest_plane_separation); + + /* add to mask */ + mask.Union(poly); + + } + + } + + return mask; +} + +/* not truncated */ diff --git a/hyp2mat/lib/crop.cc b/hyp2mat/lib/crop.cc new file mode 100644 index 0000000..ebf05c9 --- /dev/null +++ b/hyp2mat/lib/crop.cc @@ -0,0 +1,108 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "crop.h" + +using namespace Hyp2Mat; + +/* + * Adjust bounds to the size of the board + */ + +Hyp2Mat::Bounds Hyp2Mat::AdjustBounds(Hyp2Mat::PCB& pcb, Hyp2Mat::Bounds bounds) +{ + Bounds new_bounds = bounds; + Bounds size = pcb.GetBounds(); + + if (new_bounds.x_min < size.x_min) new_bounds.x_min = size.x_min; + if (new_bounds.x_max > size.x_max) new_bounds.x_max = size.x_max; + if (new_bounds.y_min < size.y_min) new_bounds.y_min = size.y_min; + if (new_bounds.y_max > size.y_max) new_bounds.y_max = size.y_max; + if (new_bounds.z_min < size.z_min) new_bounds.z_min = size.z_min; + if (new_bounds.z_max > size.z_max) new_bounds.z_max = size.z_max; + + return new_bounds; +} + +/* + * Crop polygon + */ + +Hyp2Mat::Polygon Hyp2Mat::CropPolygon (Hyp2Mat::Polygon poly, Hyp2Mat::Bounds bounds) +{ + Hyp2Mat::Polygon result; + FloatPolygon bounding_poly; + + bounding_poly.push_back(FloatPoint(bounds.x_min, bounds.y_min)); + bounding_poly.push_back(FloatPoint(bounds.x_max, bounds.y_min)); + bounding_poly.push_back(FloatPoint(bounds.x_max, bounds.y_max)); + bounding_poly.push_back(FloatPoint(bounds.x_min, bounds.y_max)); + result.AddEdge(bounding_poly); + + result.Intersection(poly); + + return result; +} + +/* + * Remove all vias which are out of bounds. + * XXX no longer needed? + */ + +void Hyp2Mat::CropVias(Hyp2Mat::PCB& pcb, Hyp2Mat::Bounds bounds) +{ + Hyp2Mat::Via new_via; + Hyp2Mat::ViaList new_vialist; + + for (Hyp2Mat::ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) { + if ((i->x > bounds.x_max) || (i->x < bounds.x_min) || (i->y > bounds.y_max) || (i->y < bounds.y_min)) continue; + if ((i->z0 > bounds.z_max) && (i->z1 > bounds.z_max)) continue; + if ((i->z0 < bounds.z_min) && (i->z1 < bounds.z_min)) continue; + new_via = *i; + if (new_via.z1 > bounds.z_max) new_via.z1 = bounds.z_max; + if (new_via.z0 < bounds.z_min) new_via.z0 = bounds.z_min; + new_vialist.push_back(new_via); + } + pcb.via = new_vialist; + return; +} + +/* + * Remove all layers which are out of bounds. + */ + +void Hyp2Mat::CropLayers(Hyp2Mat::PCB& pcb, Hyp2Mat::Bounds& bounds) +{ + Hyp2Mat::Layer new_layer; + Hyp2Mat::LayerList new_stackup; + + for (Hyp2Mat::LayerList::iterator l = pcb.stackup.begin(); l != pcb.stackup.end(); ++l) { + if ((l->z0 > bounds.z_max) && (l->z1 > bounds.z_max)) continue; + if ((l->z0 < bounds.z_min) && (l->z1 < bounds.z_min)) continue; + new_layer = *l; + if (new_layer.z1 > bounds.z_max) new_layer.z1 = bounds.z_max; + if (new_layer.z0 < bounds.z_min) new_layer.z0 = bounds.z_min; + new_stackup.push_back(new_layer); + } + pcb.stackup = new_stackup; + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/crop.h b/hyp2mat/lib/crop.h new file mode 100644 index 0000000..dd497bc --- /dev/null +++ b/hyp2mat/lib/crop.h @@ -0,0 +1,36 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CROP_H +#define CROP_H + +#include "hyp2mat.h" +#include "polygon.h" + +namespace Hyp2Mat { + Hyp2Mat::Bounds AdjustBounds(Hyp2Mat::PCB& pcb, Bounds bounds); + Hyp2Mat::Polygon CropPolygon (Hyp2Mat::Polygon poly, Hyp2Mat::Bounds bounds); + void CropVias(Hyp2Mat::PCB& pcb, Hyp2Mat::Bounds bounds); + void CropLayers(Hyp2Mat::PCB& pcb, Hyp2Mat::Bounds& bounds); + } + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/csxcad.cc b/hyp2mat/lib/csxcad.cc new file mode 100644 index 0000000..a1ec204 --- /dev/null +++ b/hyp2mat/lib/csxcad.cc @@ -0,0 +1,376 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <algorithm> +#include <iostream> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <cmath> + +#include "csxcad.h" + +using namespace Hyp2Mat; + +CSXCAD::CSXCAD() +{ + /* CSXcad priorities */ + + prio_dielectric = 100; // FR4 dielectric + prio_material = 200; // copper + prio_via = 300; // via metal + prio_drill = 400; // hole + + return; +} + +void CSXCAD::export_edge(FloatPolygon& edge) +{ + std::cout << "pgon = [];" << std::endl; + for (FloatPolygon::iterator i = edge.begin(); i != edge.end(); ++i) { + std::cout << "pgon(:, end+1) = [" << i->x << ";" << i->y << "];" << std::endl; + } + return; +} + +/* + * quote a string using matlab conventions. + */ + +std::string CSXCAD::string2matlab(std::string str) +{ + std::ostringstream ostring; + + // escape non-alpha characters, or WriteOpenEMS (in matlab) may crash on characters such as '%' in strings + + ostring << "'"; + for (unsigned int i = 0; i < str.size(); i++) { + if (str[i] == '\'') ostring << '\''; + if (str[i] == '%') ostring << '%'; + ostring << str[i]; + } + ostring << "'"; + + return ostring.str(); +} + +/* true if polygon list contains at least one (positive) polygon */ + +bool CSXCAD::contains_polygon(Hyp2Mat::FloatPolygons& polygons) +{ + bool result = false; + + for (FloatPolygons::iterator i = polygons.begin(); i != polygons.end(); ++i) { + result = !i->is_hole; + if (result) break; + }; + + return result; +} + +/* true if polygon list contains at least one hole */ + +bool CSXCAD::contains_hole(Hyp2Mat::FloatPolygons& polygons) +{ + bool result = false; + + for (FloatPolygons::iterator i = polygons.begin(); i != polygons.end(); ++i) { + result = i->is_hole; + if (result) break; + }; + + return result; +} + + /* + * Export dielectric + * If pcb_outline is true, export exact board shape, including holes. + * If pcb_outline is false, export bounding box. + */ + +void CSXCAD::export_board(Hyp2Mat::PCB& pcb, bool pcb_outline) +{ + /* CSXCAD coordinate grid definition */ + Bounds bounds = pcb.GetBounds(); + + std::cout << "function CSX = pcb(CSX)" << std::endl; + std::cout << "% matlab script created by hyp2mat" << std::endl; + std::cout << "% create minimal mesh" << std::endl; + std::cout << "mesh = {};" << std::endl; + std::cout << "mesh.x = [" << bounds.x_min << " " << bounds.x_max << "];" << std::endl; + std::cout << "mesh.y = [" << bounds.y_min << " " << bounds.y_max << "];" << std::endl; + std::cout << "mesh.z = [" << bounds.z_min << " " << bounds.z_max << "];" << std::endl; + std::cout << "% add mesh" << std::endl; + std::cout << "CSX = DefineRectGrid(CSX, 1, mesh);" << std::endl; + + /* + * Export the board. The board outline is positive; + */ + + /* + * create board material if at least one positive polygon present + * We output one polygon for each dielectric layer, as each layer may have + * a different epsilon_r. + */ + + if (contains_polygon(pcb.board)) { + + for (FloatPolygons::iterator i = pcb.board.begin(); i != pcb.board.end(); ++i) { + /* only output board material, not holes */ + if (i->is_hole) continue; + for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) { + /* only output dielectrics */ + if (l->layer_type != LAYER_DIELECTRIC) continue; + + /* Output command to create material */ + std::cout << "CSX = AddHyperLynxDielectric(CSX, 'Dielectric_" << l->layer_name << "', " << l->epsilon_r << ", " << l->loss_tangent << ");" << std::endl; + + /* output CSXCAD polygon */ + std::cout << "% board outline, layer " << l->layer_name << std::endl; + int priority = prio_dielectric + i->nesting_level; + if (pcb_outline) { + /* Use AddLinPoly */ + export_edge(i->poly); + std::cout << "CSX = AddLinPoly(CSX, 'Dielectric_" << l->layer_name << "', " << priority << ", 2, " << l->z0 << ", pgon, " << l->z1 - l->z0 << ");" << std::endl; + } + else { + /* Use AddBox */ + std::cout << "CSX = AddBox(CSX, 'Dielectric_" << l->layer_name << "', " << priority << ", [ "; + std::cout << bounds.x_min << ", " << bounds.y_min << ", " << l->z0 << "], [ "; + std::cout << bounds.x_max << ", " << bounds.y_max << ", " << l->z1 << "] );" << std::endl; + } + } + } + }; + + + /* + * Export board cutouts + */ + + /* + * create board cutout material if at least one negative polygon present + * For each cutout we create a single polygon which goes through all layers. + */ + + if (pcb_outline && contains_hole(pcb.board)) { + std::cout << "% create board cutout material" << std::endl; + std::cout << "CSX = AddMaterial( CSX, 'Drill');" << std::endl; + std::cout << "CSX = SetMaterialProperty( CSX, 'Drill', 'Epsilon', 1, 'Mue', 1);" << std::endl; + + for (FloatPolygons::iterator i = pcb.board.begin(); i != pcb.board.end(); ++i) { + /* output CSXCAD polygon */ + if (!i->is_hole) continue; + std::cout << "% board cutout" << std::endl; + export_edge(i->poly); + int priority = prio_dielectric + i->nesting_level; + std::cout << "CSX = AddLinPoly(CSX, 'Drill', " << priority << ", 2, " << bounds.z_min << ", pgon, " << bounds.z_max - bounds.z_min << ");" << std::endl; + } + }; + + return; +} + + /* + * Export copper. + * If lossy_copper is true, take copper conductivity into account. + * If lossy_copper is false, assume copper is perfect conductor. + */ + +void CSXCAD::export_layer(Hyp2Mat::PCB& pcb, Hyp2Mat::Layer& layer, bool lossy_copper, bool metal_3d, bool odd) +{ + std::string layer_material = layer.layer_name + "_copper"; + std::string layer_cutout = layer.layer_name + "_cutout"; + + // create layer material if at least one positive polygon present + if (contains_polygon(layer.metal)) { + std::cout << "% create layer " << layer.layer_name << " material" << std::endl; + + double copper_resistivity = 0.0; + if (lossy_copper && (layer.bulk_resistivity > 0)) copper_resistivity = layer.bulk_resistivity; + + if (metal_3d) + std::cout << "CSX = AddHyperLynxMetal3D(CSX, '" << layer_material << "', " << copper_resistivity << ", " << layer.thickness << ");" << std::endl; + else + std::cout << "CSX = AddHyperLynxMetal2D(CSX, '" << layer_material << "', " << copper_resistivity << ", " << layer.thickness << ");" << std::endl; + }; + + // create layer cutout material if at least one hole present + if (contains_hole(layer.metal)) { + std::cout << "% create layer " << layer.layer_name << " cutout" << std::endl; + std::cout << "CSX = AddMaterial( CSX, '" << layer_cutout << "');" << std::endl; + // outer and inner copper layers have different epsilonr + std::cout << "CSX = SetMaterialProperty( CSX, '" << layer_cutout << "', 'Epsilon', " << layer.epsilon_r << ", 'Mue', 1);" << std::endl; + }; + + /* + * Export the layer. + */ + + std::string material; + + for (FloatPolygons::iterator i = layer.metal.begin(); i != layer.metal.end(); ++i) { + /* output CSXCAD polygon */ + if (!i->is_hole) { + std::cout << "% copper" << std::endl; + material = layer_material; + } + else { + std::cout << "% cutout" << std::endl; + material = layer_cutout; + }; + + export_edge(i->poly); + int priority = prio_material + i->nesting_level; + /* Default is exporting copper as a 2D conducting sheet */ + if (metal_3d) { + // We assume alternating layers of substrate and prepreg,and that metal layers are pushed into the prepreg. + // This means that the top metal layer is pointing up, the second metal layer is pointing down, and so on. + double thickness = layer.thickness; + if (odd) thickness = -thickness; + std::cout << "CSX = AddLinPoly(CSX, '" << material << "', " << priority << ", 2, " << layer.z0 << ", pgon, " << thickness << ");" << std::endl; + } + else + std::cout << "CSX = AddPolygon(CSX, '" << material << "', " << priority << ", 2, " << layer.z0 << ", pgon);" << std::endl; + } + + return; +} + +/* + * Export vias + */ + +void CSXCAD::export_vias(Hyp2Mat::PCB& pcb) +{ + if (!pcb.via.empty()) { + /* create via material */ + std::cout << "% via copper" << std::endl; + std::cout << "CSX = AddMetal( CSX, 'via' );" << std::endl; + for (ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) { + double z0 = i->z0; + double z1 = i->z1; + std::cout << "CSX = AddCylinder(CSX, 'via', " << prio_via ; + std::cout << ", [ " << i->x << " , " << i->y << " , " << z0; + std::cout << " ], [ " << i->x << " , " << i->y << " , " << z1; + std::cout << " ], " << i->radius << ");" << std::endl; + } + } + return; +} + +/* + * Export devices + */ + +void CSXCAD::export_devices(Hyp2Mat::PCB& pcb) +{ + std::cout << "% devices" << std::endl; + std::cout << "CSX.HyperLynxDevice = {};" << std::endl; + for (DeviceList::iterator i = pcb.device.begin(); i != pcb.device.end(); ++i) { + std::cout << "CSX.HyperLynxDevice{end+1} = struct('name', " << string2matlab(i->name) << ", 'ref', " << string2matlab(i->ref); + + /* output device value if available */ + if (i->value_type == DEVICE_VALUE_FLOAT) + std::cout << ", 'value', " << i->value_float; + else if (i->value_type == DEVICE_VALUE_STRING) + std::cout << ", 'value', " << string2matlab(i->value_string); + + std::cout << ", 'layer_name', " << string2matlab(i->layer_name); + std::cout << ");" << std::endl; + } +} + +/* + * Export port + */ + +void CSXCAD::export_ports(Hyp2Mat::PCB& pcb) +{ + std::cout << "% ports" << std::endl; + std::cout << "CSX.HyperLynxPort = {};" << std::endl; + for (PinList::iterator i = pcb.pin.begin(); i != pcb.pin.end(); ++i) { + /* csxcad requires rectangular ports, axis aligned. Use the bounding box. XXX fixme ? Use largest inscribed rectangle instead */ + double x_max = 0, y_max = 0, x_min = 0, y_min = 0; + bool first = true; + for (FloatPolygon::iterator j = i->metal.begin(); j != i->metal.end(); ++j) { + if ((j->x > x_max) || first) x_max = j->x; + if ((j->y > y_max) || first) y_max = j->y; + if ((j->x < x_min) || first) x_min = j->x; + if ((j->y < y_min) || first) y_min = j->y; + first = false; + } + + /* determine whether port is on top or bottom layer of pcb */ + double dbottom = std::abs(i->z0 - pcb.stackup.back().z0); + double dtop = std::abs(i->z1 - pcb.stackup.front().z1); + bool on_top = dtop <= dbottom; + + std::cout << "CSX.HyperLynxPort{end+1} = struct('ref', " << string2matlab(i->ref); + std::cout << ", 'xc', " << i->x << ", 'yc', " << i->y << ", 'z', " << i->z0; + std::cout << ", 'x1', " << x_min << ", 'y1', " << y_min ; + std::cout << ", 'x2', " << x_max << ", 'y2', " << y_max ; + std::cout << ", 'position', " << (on_top ? "'top'" : "'bottom'"); + std::cout << ", 'layer_name', " << string2matlab(i->layer_name) << ");" << std::endl; + } + return; +} + +/* + * Write pcb to file in CSXCAD format + */ + +void CSXCAD::Write(const std::string& filename, Hyp2Mat::PCB pcb, bool pcb_outline, bool lossy_copper, bool metal_3d) +{ + + /* open file for output */ + + if ((filename != "-") && (freopen(filename.c_str(), "w", stdout) == NULL)) { + std::cerr << "could not open '" << filename << "' for writing"; + return; + } + + /* Export dielectric. If pcb_outline is true, export exact board shape, including holes. + If pcb_outline is false, export bounding box. */ + export_board(pcb, pcb_outline); + + /* Export copper */ + std::cout << "% copper" << std::endl; + bool odd = false; + for (LayerList::iterator l = pcb.stackup.begin(); l != pcb.stackup.end(); ++l) { + if (l->layer_type == LAYER_DIELECTRIC) odd = !odd; + export_layer(pcb, *l, lossy_copper, metal_3d, odd); + } + + /* Export vias */ + export_vias(pcb); + + export_devices(pcb); + + export_ports(pcb); + + std::cout << "%not truncated" << std::endl; + + fclose(stdout); + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/csxcad.h b/hyp2mat/lib/csxcad.h new file mode 100644 index 0000000..af7ec5b --- /dev/null +++ b/hyp2mat/lib/csxcad.h @@ -0,0 +1,53 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef CSXCAD_H +#define CSXCAD_H + +#include "hyp2mat.h" + +class CSXCAD { +public: + CSXCAD(); + void Write(const std::string& filename, Hyp2Mat::PCB pcb, bool pcb_outline, bool lossy_copper, bool metal_3d); /* save in CSXcad format */ + +private: + int prio_dielectric; // FR4 dielectric + int prio_material; // copper + int prio_via; // via metal + int prio_drill; // hole + + double adjust_z(Hyp2Mat::PCB& pcb, double z); + + void export_edge(Hyp2Mat::FloatPolygon& edge); /* output a polygon edge */ + void export_layer(Hyp2Mat::PCB& pcb, Hyp2Mat::Layer& layer, bool lossy_copper, bool metal_3d, bool odd); /* output a copper layer */ + void export_board(Hyp2Mat::PCB& pcb, bool pcb_outline); /* output the dielectric */ + void export_vias(Hyp2Mat::PCB& pcb); + void export_devices(Hyp2Mat::PCB& pcb); + void export_ports(Hyp2Mat::PCB& pcb); + bool contains_polygon(Hyp2Mat::FloatPolygons& polygons); /* true if polygon list contains at least one (positive) polygon */ + bool contains_hole(Hyp2Mat::FloatPolygons& polygons); /* true if polygon list contains at least one hole */ + std::string string2matlab(std::string str); /* quote a string using matlab conventions */ + + }; + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/draw.cc b/hyp2mat/lib/draw.cc new file mode 100644 index 0000000..0de9634 --- /dev/null +++ b/hyp2mat/lib/draw.cc @@ -0,0 +1,296 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Uses the Clipper library from Angus Johnson to do polygon arithmetic + */ + +#include <iostream> +#include <cstdio> +#include <cmath> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Polygon constructor + */ + +Polygon::Polygon() +{ + vertex.clear(); + polygon_type = POLYGON_TYPE_COPPER; + id = -1; + positive = true; /* is a polygon, not a hole */ + width = 0.0; + plane_separation = -1.0; /* negative if not set */ + left_plane_separation = -1.0; /* negative if not set */ + net_name.clear(); + layer_name.clear(); +} + +/* + * Draw an arc from (x1, y1) to (x2, y2) with center (xc, yc) and radius r using a polygonal approximation. + * Arc may be clockwise or counterclockwise. + */ + +Polygon HypFile::Hyp::arc2poly(double x1, double y1, double x2, double y2, double xc, double yc, double r, bool clockwise) +{ + + Polygon arc_segment; + arc_segment.width = 0; + arc_segment.positive = true; + arc_segment.layer_name = ""; + + int poly_points = 0; + + double alpha = atan2(y1-yc, x1-xc); + double beta = atan2(y2-yc, x2-xc); + + + if (clockwise) { + // draw arc clockwise from (x1,y1) to (x2,y2) + if (alpha < beta) + alpha = alpha + 2 * pi; + } + else { + // draw arc counterclockwise from (x1,y1) to (x2,y2) + if (beta < alpha) + beta = beta + 2 * pi; + // draw a circle if starting and end points are the same + if ((x1 == x2) && (y1 == y2)) + beta = alpha + 2 * pi; + } + + // Calculate number of segments needed for a full circle. + int segments = min_circle_segments; + + if (arc_precision > 0) { + // Increase number of segments until difference between circular arc + // and polygonal approximation is less than max_arc_error + + double arc_error; + do { + arc_error = r * (1 - cos(pi/segments)); + if (arc_error > arc_precision) segments += 4; + } + while (arc_error > arc_precision); + + } + else if (arc_precision < 0) + error("error: negative arc precision"); + + // A full circle is drawn using 'segments' segments; a 90 degree arc using segments/4. + poly_points = round(segments*abs(beta-alpha)/(2*pi)); + + // Sanity checks + if (poly_points < 1) poly_points = 1; + + // first point + arc_segment.vertex.push_back(Point(x1, y1)); + + // intermediate points + for (int i = 1; i < poly_points ; i++) { + double angle = alpha + (beta - alpha) * i / poly_points; + double x = xc + r * cos(angle); + double y = yc + r * sin(angle); + arc_segment.vertex.push_back(Point(x, y)); + } + + // last point + arc_segment.vertex.push_back(Point(x2, y2)); + + return arc_segment; +} + +/* + * Draws straight metal trace segment as a polygon. + */ + +Polygon HypFile::Hyp::segment2poly(double x1, double y1, double x2, double y2, double width) +{ +/* + * Note a hyperlynx line segment is drawn, ending in two half-circles: + * ------------------------------- ^ + * / \ | + * | | width + * \ / | + * ------------------------------- V + * + * <----------- length ----------> + */ + + Polygon line_segment; + line_segment.width = 0; + line_segment.positive = true; + line_segment.layer_name = ""; + + /* Sanity check */ + double dx; + double dy; + if (( x1 == x2) && (y2 == y1)) { + dx = width/2; + dy = 0; + } + else { + double length = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); + dx = width / 2 * (y2 - y1) / length; + dy = width / 2 * (x2 - x1) / length; + } + + Polygon arc2 = arc2poly(x2 + dx, y2 - dy, x2 - dx, y2 + dy, x2, y2, width/2, false); + Polygon arc1 = arc2poly(x1 - dx, y1 + dy, x1 + dx, y1 - dy, x1, y1, width/2, false); + + line_segment.vertex = arc2.vertex; + line_segment.vertex.insert(line_segment.vertex.end(), arc1.vertex.begin(), arc1.vertex.end()); + + return line_segment; +} + +/* + * Convert a pad to a polygon + */ + +Polygon HypFile::Hyp::pad2poly(double pad_x, double pad_y, Pad pad) +{ + Polygon pad_poly; + pad_poly.width = 0; + pad_poly.layer_name = pad.layer_name; + pad_poly.positive = true; + if (pad.pad_type == PAD_TYPE_ANTIPAD) pad_poly.polygon_type = POLYGON_TYPE_ANTIPAD; + else pad_poly.polygon_type = POLYGON_TYPE_PAD; + + double sx = pad.pad_sx; + double sy = pad.pad_sy; + double angle = pad.pad_angle; + + if (fmod(angle, 180) == 0) angle = 0; + else if (fmod(angle, 90) == 0) { + double t; + t = sx; + sx = sy; + sy = t; + angle = 0; + } + + /* + * Circular and oval pads + */ + + if (pad.pad_shape == PAD_SHAPE_OVAL) { + /* calculate number of segments needed */ + int segments = min_circle_segments; + if (arc_precision > 0) { + /* Increase number of segments until difference between oval + and polygonal approximation is less than max_arc_error */ + double arc_error_x; + double arc_error_y; + do { + arc_error_x = sx / 2 * (1 - cos(pi/segments)); + arc_error_y = sy / 2 * (1 - cos(pi/segments)); + if ((arc_error_x > arc_precision) || (arc_error_y > arc_precision)) segments += 4; + } + while ((arc_error_x > arc_precision) || (arc_error_y > arc_precision)); + } + /* calculate points */ + for (int i = 0; i < segments; i++) { + double alpha = 2 * pi * i / segments; + double x = sx / 2 / cos(pi / segments) * cos(alpha); + double y = sy / 2 / cos(pi / segments) * sin(alpha); + pad_poly.vertex.push_back(Point(x, y)); + } + } + + /* + * Square and rectangular pads + */ + + else if (pad.pad_shape == PAD_SHAPE_RECTANGULAR) { + pad_poly.vertex.push_back(Point( sx/2, sy/2)); + pad_poly.vertex.push_back(Point( sx/2, -sy/2)); + pad_poly.vertex.push_back(Point(-sx/2, -sy/2)); + pad_poly.vertex.push_back(Point(-sx/2, sy/2)); + } + + /* + * Oblong pads + */ + + else if (pad.pad_shape == PAD_SHAPE_OBLONG) { + if (sx > sy) { + /* horizontal oblong pad */ + double x1 = (sx - sy)/2; + double y1 = -sy/2; + double x2 = x1; + double y2 = -y1; + double xc = (sx - sy)/2; + double yc = 0; + double r = sy/2; + Polygon arc_right = arc2poly(x1, y1, x2, y2, xc, yc, r, false); + Polygon arc_left = arc2poly(-x1, -y1, -x2, -y2, -xc, -yc, r, false); + pad_poly.vertex = arc_right.vertex; + pad_poly.vertex.insert(pad_poly.vertex.end(), arc_left.vertex.begin(), arc_left.vertex.end()); + } + else { + /* vertical oblong pad */ + double x1 = sx/2; + double y1 = (sy - sx)/2; + double x2 = -x1; + double y2 = y1; + double xc = 0; + double yc = (sy-sx)/2; + double r = sx/2; + Polygon arc_top = arc2poly(x1, y1, x2, y2, xc, yc, r, false); + Polygon arc_bottom = arc2poly(-x1, -y1, -x2, -y2, -xc, -yc, r, false); + pad_poly.vertex = arc_top.vertex; + pad_poly.vertex.insert(pad_poly.vertex.end(), arc_bottom.vertex.begin(), arc_bottom.vertex.end()); + } + } + + /* + * Unknown pad shape + */ + + else error("unknown pad shape"); + + /* Rotate polygon over pad rotation angle */ + if (angle != 0) { + double cosa = cos(angle * pi / 180); + double sina = sin(angle * pi / 180); + for (PointList::iterator i = pad_poly.vertex.begin(); i != pad_poly.vertex.end(); ++i) { + double x_rot = cosa * i->x - sina * i->y ; + double y_rot = sina * i->x + cosa * i->y ; + i->x = x_rot; + i->y = y_rot; + } + } + + /* Translate polygon to pad center */ + for (PointList::iterator i = pad_poly.vertex.begin(); i != pad_poly.vertex.end(); ++i) { + i->x += pad_x; + i->y += pad_y; + } + + /* return pad converted to polygon */ + return pad_poly; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_board.cc b/hyp2mat/lib/exec_board.cc new file mode 100644 index 0000000..103d586 --- /dev/null +++ b/hyp2mat/lib/exec_board.cc @@ -0,0 +1,251 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <cstdio> +#include <cmath> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'BOARD_FILE' section. + * Hyperlynx file header. + */ + +bool HypFile::Hyp::exec_board_file(parse_param& h) +{ + if (trace_hyp) cerr << "board_file" << endl; + + return false; +} + +/* + * Hyperlynx 'VERSION' record. + * Specifies version number. + * Required record; must be first record of the file. + */ + +bool HypFile::Hyp::exec_version(parse_param& h) +{ + if (trace_hyp) cerr << "version: vers = " << h.vers << endl; + + if (h.vers < 2.0) error("warning: version 1.x deprecated"); + + return false; +} + +/* + * Hyperlynx 'DATA_MODE' record. + * Specifies whether data is sufficient for power simulation. + */ + +bool HypFile::Hyp::exec_data_mode(parse_param& h) +{ + if (trace_hyp) cerr << "data_mode: detailed = " << h.detailed << endl; + + return false; +} + +/* + * Hyperlynx 'UNITS' record. + * Specifies measurement system (english/metric) for the rest of the file. + */ + +bool HypFile::Hyp::exec_units(parse_param& h) +{ + if (trace_hyp) cerr << "units: unit_system_english = " << h.unit_system_english << " metal_thickness_weight = " << h.metal_thickness_weight << endl; + // convert everything to meter + + if (h.unit_system_english) { + unit = inches; // lengths in inches. 1 in = 2.54 cm = 0.0254 m + if (h.metal_thickness_weight) + metal_thickness_unit = copper_imperial_weight * unit; // metal thickness in ounces/ft2. 1 oz/ft2 copper = 1.341 mil + else + metal_thickness_unit = unit; // metal thickness in inches + } + else { + unit = 0.01; // lengths in centimeters. 1 cm = 0.01 m + if (h.metal_thickness_weight) + metal_thickness_unit = copper_metric_weight * unit; // metal thickness in grams/cm2. 1 gr/cm2 copper = 0.1116 cm + else + metal_thickness_unit = unit; // metal thickness in centimeters + } + + return false; +} + +/* + * Hyperlynx 'PLANE_SEP' record. + * Defines default trace to plane separation + */ + +bool HypFile::Hyp::exec_plane_sep(parse_param& h) +{ + if (trace_hyp) cerr << "plane_sep: default_plane_separation = " << h.default_plane_separation << endl; + + board.plane_separation = h.default_plane_separation * unit; + + return false; +} + +/* + * Hyperlynx 'PERIMETER_SEGMENT' subrecord of 'BOARD' record. + * Draws linear board outline segment. + */ + +bool HypFile::Hyp::exec_perimeter_segment(parse_param& h) +{ + if (trace_hyp) cerr << "perimeter_segment: x1 = " << h.x1 << " y1 = " << h.y1 << " x2 = " << h.x2 << " y2 = " << h.y2 << endl; + + Polygon poly; + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + + poly.width = 0; + poly.positive = true; + poly.vertex.push_back(Point(h.x1, h.y1)); + poly.vertex.push_back(Point(h.x2, h.y2)); + + // Add segment to board outline + add_perimeter_polygon(poly); + + return false; +} + +/* + * Hyperlynx 'PERIMETER_ARC' subrecord of 'BOARD' record. + * Draws arc segment of board outline. + */ + +bool HypFile::Hyp::exec_perimeter_arc(parse_param& h) +{ + if (trace_hyp) cerr << "perimeter_arc: x1 = " << h.x1 << " y1 = " << h.y1 << " x2 = " << h.x2 << " y2 = " << h.y2 << " xc = " << h.xc << " yc = " << h.yc << " r = " << h.r << endl; + + Polygon poly; + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + h.xc *= unit; + h.yc *= unit; + h.r *= unit; + + poly = arc2poly(h.x1, h.y1, h.x2, h.y2, h.xc, h.yc, h.r, false); // 'PERIMETER_ARC' draws arc counterclockwise + + // Add arc to board outline + add_perimeter_polygon(poly); + + return false; +} + +/* + * Hyperlynx 'A' attribute subrecord of 'BOARD' record. + * Defines board attributes as name/value pairs. + */ + +bool HypFile::Hyp::exec_board_attribute(parse_param& h) +{ + if (trace_hyp) cerr << "board_attribute: name = " << h.name << " value = " << h.value << endl; + + Attribute a; + a.name = h.name; + a.value = h.value; + + board.attribute.push_back(a); + + return false; +} + +/* + * Add a polygon to the board outline + */ + +void HypFile::Hyp::add_perimeter_polygon(Polygon new_segment) +{ + // Note first polygon of board perimeter is the board outline, and positive. + // Subsequent polygons are board cutouts, and negative. + + // safety check + if (new_segment.vertex.empty()) + return; + + if (new_segment.vertex.size() == 1) { + error("runt perimeter segment"); + return; + } + + /* add new segment to list of PERIMETER_SEGMENT and PERIMETER_ARC segments */ + board.segments.push_back(new_segment); + + /* re-calculate board edge + * loop over the list of segments, trying to re-construct the + * board edge piece by piece. */ + + board.edge.clear(); + PolygonList segs = board.segments; + Polygon current_edge; + + bool found = false; + do { + found = false; + /* find segment to add to existing edge */ + for (PolygonList::iterator i = segs.begin(); i != segs.end(); ++i) { + + if (current_edge.vertex.empty() || (i->vertex.front() == current_edge.vertex.back())) { + /* first point of segment is last point of current edge: add segment to edge */ + found = true; + current_edge.vertex.insert(current_edge.vertex.end(), i->vertex.begin(), i->vertex.end()); + } + else if (i->vertex.back() == current_edge.vertex.back()) { + /* last point of segment is last point of current edge: add segment to edge back to front */ + found = true; + current_edge.vertex.insert(current_edge.vertex.end(), i->vertex.rbegin(), i->vertex.rend()); + }; + + if (found) { + /* erase added segment from list of unused segments */ + segs.erase(i); + /* check if current edge is closed */ + if (current_edge.vertex.front() == current_edge.vertex.back()) { + /* store old edge, begin new one */ + current_edge.positive = false; + board.edge.push_back(current_edge); + current_edge.vertex.clear(); + } + break; + } + + } + } while (found); + + /* only first polygon of board perimeter is positive, rest are holes. */ + if (!board.edge.empty()) board.edge.front().positive = true; + + return; + +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_devices.cc b/hyp2mat/lib/exec_devices.cc new file mode 100644 index 0000000..59b7f00 --- /dev/null +++ b/hyp2mat/lib/exec_devices.cc @@ -0,0 +1,89 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'DEVICE' record. + * Specifies pcb components: resistors, capacitors, ICs, ... + * Required record. + */ + +bool HypFile::Hyp::exec_devices(parse_param& h) +{ + if (trace_hyp) { + cerr << "device: "; + cerr << "device_type = " << h.device_type; + cerr << " ref = " << h.ref ; + if (h.name_set) cerr << " name = " << h.name; + if (h.value_float_set) cerr << " value_float = " << h.value_float; + if (h.value_string_set) cerr << " value_string = " << h.value_string; + if (h.layer_name_set) cerr << " layer_name = " << h.layer_name; + if (h.package_set) cerr << " package = " << h.package; + cerr << endl; + } + + Device d; + + d.device_type = h.device_type; + + d.ref = h.ref; + + if (h.name_set) + d.name = h.name; + else + d.name = ""; + + if (h.value_float_set) + d.value_float = h.value_float; + else + d.value_float = 0; + + d.value_float_set = h.value_float_set; + + if (h.value_string_set) + d.value_string = h.value_string; + else + d.value_string = ""; + + d.value_string_set = h.value_string_set; + + if (h.layer_name_set) + d.layer_name = h.layer_name; + else { + error ("device layer not set"); + return true; + } + + if (h.package_set) + d.package = h.package; + else + d.package = ""; + + device.push_back(d); + + return false; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_end.cc b/hyp2mat/lib/exec_end.cc new file mode 100644 index 0000000..a87ca25 --- /dev/null +++ b/hyp2mat/lib/exec_end.cc @@ -0,0 +1,53 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'END' record. + * Marks end of board description. + */ + +bool HypFile::Hyp::exec_end(parse_param& h) +{ + if (trace_hyp) cerr << "end" << endl; + + /* Optionally flood layers with copper */ + flood_layers_with_copper(); + + return false; +} + +/* + * Hyperlynx 'KEY' record. + */ + +bool HypFile::Hyp::exec_key(parse_param& h) +{ + if (trace_hyp) cerr << "key: key = " << h.key << endl; + + return false; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_net.cc b/hyp2mat/lib/exec_net.cc new file mode 100644 index 0000000..50000b3 --- /dev/null +++ b/hyp2mat/lib/exec_net.cc @@ -0,0 +1,502 @@ + +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <cmath> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'NET' record. + * Specifies a net. + */ + +bool HypFile::Hyp::exec_net(parse_param& h) +{ + if (trace_hyp) cerr << "net: net_name = " << h.net_name << endl; + + Net n; + n.net_name = h.net_name; + n.plane_separation = -1.0; + net.push_back(n); + + return false; +} + +/* + * Hyperlynx 'PS' subrecord of 'NET' record. + * Specifies plane separation of a net. + */ + +bool HypFile::Hyp::exec_net_plane_separation(parse_param& h) +{ + if (trace_hyp) cerr << "net_plane_separation: plane_separation = " << h.plane_separation << endl; + h.plane_separation *= unit; + + net.back().plane_separation = h.plane_separation; + + return false; +} + +/* + * Hyperlynx 'SEG' subrecord of 'NET' record. + * Draws straight metal trace segment. + */ + +bool HypFile::Hyp::exec_seg(parse_param& h) +{ + if (trace_hyp) { + cerr << "seg: x1 = " << h.x1 << " y1 = " << h.y1 << " x2 = " << h.x2 << " y2 = " << h.y2; + cerr << " width = " << h.width << " layer_name = " << h.layer_name; + if (h.plane_separation_set) cerr << " plane_separation = " << h.plane_separation; + if (h.left_plane_separation_set) cerr << " left_plane_separation = " << h.left_plane_separation; + cerr << endl; + } + + Polygon p; + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + h.width *= unit; + h.plane_separation *= unit; + if (!h.plane_separation_set) h.plane_separation = -1.0; + h.left_plane_separation *= unit; + if (!h.left_plane_separation_set) h.left_plane_separation = -1.0; + + p = segment2poly(h.x1 , h.y1, h.x2, h.y2, h.width); + p.layer_name = h.layer_name; + p.width = 0; + p.positive = true; + p.plane_separation = h.plane_separation; /* distance to other copper; -1 if not set */ + p.left_plane_separation = h.left_plane_separation; + + add_polygon(p); + + return false; +} + +/* + * Hyperlynx 'ARC' subrecord of 'NET' record. + * Draws arc metal trace segment. + */ + +bool HypFile::Hyp::exec_arc(parse_param& h) +{ + if (trace_hyp) { + cerr << "arc: x1 = " << h.x1 << " y1 = " << h.y1 << " x2 = " << h.x2 << " y2 = " << h.y2; + cerr << " xc = " << h.xc << " yc = " << h.yc << " r = " << h.r; + cerr << " width = " << h.width << " layer_name = " << h.layer_name; + if (h.plane_separation_set) cerr << " plane_separation = " << h.plane_separation; + if (h.left_plane_separation_set) cerr << " left_plane_separation = " << h.left_plane_separation; + cerr << endl; + } + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + h.xc *= unit; + h.yc *= unit; + h.r *= unit; + h.width *= unit; + h.plane_separation *= unit; + if (!h.plane_separation_set) h.plane_separation = -1.0; + h.left_plane_separation *= unit; + if (!h.left_plane_separation_set) h.left_plane_separation = -1.0; + + /* 'ARC' draws arc clockwise */ + Polygon arc = arc2poly(h.x1, h.y1, h.x2, h.y2, h.xc, h.yc, h.r, true); + + /* draw arc segments */ + for (PointList::iterator i = arc.vertex.begin(); (i != arc.vertex.end()) && (i != --arc.vertex.end()); ++i) { + Polygon line_segment; + PointList::iterator j = i + 1; + line_segment = segment2poly(i->x, i->y, j->x, j->y, h.width); + line_segment.positive = true; + line_segment.layer_name = h.layer_name; + line_segment.width = 0; + line_segment.plane_separation = h.plane_separation; + line_segment.left_plane_separation = h.left_plane_separation; + add_polygon(line_segment); + } + + return false; +} + +/* + * Hyperlynx 'VIA' subrecord of 'NET' record. + * Draws via as defined in padstack. + */ + +bool HypFile::Hyp::exec_via(parse_param& h) +{ + if (trace_hyp) { + cerr << "via: x = " << h.x << " y = " << h.y; + if (h.layer1_name_set) cerr << " layer1_name = " << h.layer1_name; + if (h.layer2_name_set) cerr << " layer2_name = " << h.layer2_name; + cerr << " padstack_name = " << h.padstack_name; + cerr << endl; + } + + h.x *= unit; + h.y *= unit; + + /* lookup padstack */ + for (PadstackList::iterator i = padstack.begin(); i != padstack.end(); ++i) + if (i->padstack_name == h.padstack_name) { + + /* set via begin layer */ + if (!h.layer1_name_set) + h.layer1_name = i->pads.front().layer_name; + + /* set via end layer */ + if (!h.layer2_name_set) + h.layer2_name = i->pads.back().layer_name; + + /* add to list of via holes */ + add_via(h.x, h.y, h.layer2_name, h.layer1_name, i->drill_size/2); + + /* loop over padstack and generate pads */ + for (PadList::iterator j = i->pads.begin(); j != i->pads.end(); ++j) + add_pad(h.x, h.y, *j); + + return false; + } + + /* padstack not found */ + + return false; +} + +/* + * Hyperlynx 'VIA' subrecord of 'NET' record. + * Draws deprecated v1.x via. + */ + +bool HypFile::Hyp::exec_via_v1(parse_param& h) +{ + if (trace_hyp) { + cerr << "via: x = " << h.x << " y = " << h.y; + cerr << " drill_size = " << h.drill_size; + if (h.layer1_name_set) cerr << " layer1_name = " << h.layer1_name; + if (h.layer2_name_set) cerr << " layer2_name = " << h.layer2_name; + cerr << " pad1_shape = " << h.pad1_shape << " pad1_sx = " << h.pad1_sx << " pad1_sy = " << h.pad1_sy << " pad1_angle = " << h.pad1_angle; + cerr << " pad2_shape = " << h.pad2_shape << " pad2_sx = " << h.pad2_sx << " pad2_sy = " << h.pad2_sy << " pad2_angle = " << h.pad2_angle; + cerr << endl; + } + + h.drill_size *= unit; + h.x *= unit; + h.y *= unit; + h.pad1_sx *= unit; + h.pad1_sy *= unit; + h.pad2_sx *= unit; + h.pad2_sy *= unit; + + /* add to list of via holes */ + add_via(h.x, h.y, stackup.back().layer_name /* bottom */, stackup.front().layer_name /* top */ , h.drill_size/2); + + /* add top pad */ + Pad pad; + pad.layer_name = stackup.front().layer_name; /* Top layer */ + if (h.pad1_shape == "OVAL") pad.pad_shape = PAD_SHAPE_OVAL; + else if (h.pad1_shape == "RECT") pad.pad_shape = PAD_SHAPE_RECTANGULAR; + else if (h.pad1_shape == "OBLONG") pad.pad_shape = PAD_SHAPE_OBLONG; + else pad.pad_shape = PAD_SHAPE_OVAL; + pad.pad_type = PAD_TYPE_METAL; + pad.pad_sx = h.pad1_sx; + pad.pad_sy = h.pad1_sy; + pad.pad_angle = h.pad1_angle; + pad.thermal_clear_shape = PAD_SHAPE_OVAL; + pad.thermal_clear_sx = 0; + pad.thermal_clear_sy= 0; + pad.thermal_clear_angle= 0; + add_pad(h.x, h.y, pad); + + /* add bottom pad */ + pad.layer_name = stackup.back().layer_name; /* Top layer */ + if (h.pad2_shape == "OVAL") pad.pad_shape = PAD_SHAPE_OVAL; + else if (h.pad2_shape == "RECT") pad.pad_shape = PAD_SHAPE_RECTANGULAR; + else if (h.pad2_shape == "OBLONG") pad.pad_shape = PAD_SHAPE_OBLONG; + else pad.pad_shape = PAD_SHAPE_OVAL; + pad.pad_type = PAD_TYPE_METAL; + pad.pad_sx = h.pad2_sx; + pad.pad_sy = h.pad2_sy; + pad.pad_angle = h.pad2_angle; + add_pad(h.x, h.y, pad); + + return false; +} + +/* + * Hyperlynx 'PIN' subrecord of 'NET' record. + * Draws PIN as defined in padstack. + */ + +bool HypFile::Hyp::exec_pin(parse_param& h) +{ + if (trace_hyp) { + cerr << "pin: x = " << h.x << " y = " << h.y; + cerr << " pin_reference = " << h.pin_reference; + cerr << " padstack_name = " << h.padstack_name; + if (h.pin_function_set) cerr << " pin_function = " << h.pin_function; + cerr << endl; + } + + h.x *= unit; + h.y *= unit; + + /* add to list of pins */ + Pin p; + p.x = h.x; + p.y = h.y; + p.pin_reference = h.pin_reference; + p.padstack_name = h.padstack_name; + p.pin_function = PIN_SIM_BOTH; + if (h.pin_function_set) p.pin_function = h.pin_function; + net.back().pin.push_back(p); + + /* lookup padstack */ + if (h.padstack_name_set) + for (PadstackList::iterator i = padstack.begin(); i != padstack.end(); ++i) + if (i->padstack_name == h.padstack_name) { + /* add to list of via holes */ + add_via(h.x, h.y, stackup.back().layer_name /* bottom */, stackup.front().layer_name /* top */, i->drill_size/2); + + /* loop over padstack and generate pads */ + for (PadList::iterator j = i->pads.begin(); j != i->pads.end(); ++j) + add_pad(h.x, h.y, *j); + return false; + } + + /* padstack not found or not set */ + + return false; +} + +/* + * Hyperlynx 'PAD' subrecord of 'NET' record. + * Draws deprecated v1.x pad. + */ + +bool HypFile::Hyp::exec_pad(parse_param& h) +{ + if (trace_hyp) { + cerr << "pad: x = " << h.x << " y = " << h.y; + if (h.layer_name_set) cerr << " layer_name = " << h.layer_name; + cerr << " pad1_shape = " << h.pad1_shape << " pad1_sx = " << h.pad1_sx << " pad1_sy = " << h.pad1_sy << " pad1_angle = " << h.pad1_angle; + cerr << endl; + } + + h.x *= unit; + h.y *= unit; + h.pad1_sx *= unit; + h.pad1_sy *= unit; + + /* add pad */ + Pad pad; + pad.layer_name = h.layer_name; + if (h.pad1_shape == "OVAL") pad.pad_shape = PAD_SHAPE_OVAL; + else if (h.pad1_shape == "RECT") pad.pad_shape = PAD_SHAPE_RECTANGULAR; + else if (h.pad1_shape == "OBLONG") pad.pad_shape = PAD_SHAPE_OBLONG; + else pad.pad_shape = PAD_SHAPE_OVAL; + pad.pad_type = PAD_TYPE_METAL; + pad.pad_sx = h.pad1_sx; + pad.pad_sy = h.pad1_sy; + pad.pad_angle = h.pad1_angle; + pad.thermal_clear_shape = PAD_SHAPE_OVAL; + pad.thermal_clear_sx = 0; + pad.thermal_clear_sy= 0; + pad.thermal_clear_angle= 0; + add_pad(h.x, h.y, pad); + + return false; +} + +/* + * Hyperlynx 'USEG' subrecord of 'NET' record. + * Designates unrouted metal trace segment + */ + +bool HypFile::Hyp::exec_useg(parse_param& h) +{ + if (trace_hyp) { + cerr << "useg: x1 = " << h.x1 << " y1 = " << h.y1 << " layer1_name = " << h.layer1_name; + cerr << " x2 = " << h.x2 << " y2 = " << h.y2 << " layer2_name = " << h.layer2_name; + if (h.zlayer_name_set) cerr << " zlayer_name = " << h.zlayer_name << " width = " << h.width << " length = " << h.length; + if (h.impedance_set) cerr << " impedance = " << h.impedance << " delay = " << h.delay; + if (h.resistance_set) cerr << " resistance = " << h.resistance; + cerr << endl; + } + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + h.width *= unit; + h.length *= unit; + if (!h.resistance_set) h.resistance = 0; + + UnroutedSegment u; + u.x1 = h.x1; + u.y1 = h.y1; + u.layer1_name = h.layer1_name; + u.x2 = h.x2; + u.y2 = h.y2; + u.layer2_name = h.layer2_name; + u.zlayer_name = h.zlayer_name; + u.zlayer_name_set = h.zlayer_name_set; + u.width = h.width; + u.impedance = h.impedance; + u.impedance_set = h.impedance_set; + u.delay = h.delay; + u.resistance = h.resistance; + net.back().unrouted_segment.push_back(u); + + return false; +} + +/* + * Hyperlynx 'A' attribute subrecord of 'NET' record. + * Defines net attributes as name/value pairs. + */ + +bool HypFile::Hyp::exec_net_attribute(parse_param& h) +{ + if (trace_hyp) cerr << "net_attribute: name = " << h.name << " value = " << h.value << endl; + + Attribute a; + a.name = h.name; + a.value = h.value; + + net.back().attribute.push_back(a); + + return false; +} + +/* + * Add a polygon to the current net + */ + +void HypFile::Hyp::add_polygon(Polygon poly) +{ + if (poly.vertex.empty()) return; + add_polygon_to_net(net.back(), poly); +} + +/* + * Add polygon poly to net pnet + */ + +void HypFile::Hyp::add_polygon_to_net(Net& pnet, Polygon poly) { + + /* set net name of polygon to net */ + poly.net_name = pnet.net_name; + + /* The polygon id is a positive number if the polygon has been assigned a polygon id. + * If the polygon has not been assigned a polygon id, the id is -1. + * An polygon which has not been assigned a polygon id has a single, outer edge, no holes. + * (the polygon does not have an id a POLYVOID could refer to). + */ + + if (poly.id < 0) { + /* + * If a polygon has id -1, assign the polygon an arbitrary negative id, different from -1. + * Make sure all polygons of the same net do not clash/have different ids. + */ + int new_id = 0; + if (!pnet.metal.empty()) + new_id = pnet.metal.begin()->first - 1; /* new id is one less than the smallest existing id */ + if (new_id >= -1) new_id = -2; /* does not clash with an assigned id (positive) or the default (-1) */ + poly.id = new_id; + } + + /* check if polygons with this id already exist */ + PolygonMap::iterator i = pnet.metal.find(poly.id); + if (i == pnet.metal.end()) { + PolygonList empty_list; + pnet.metal[poly.id] = empty_list; + } + + /* add polygon to net */ + pnet.metal[poly.id].push_back(poly); + + return; +} + +/* + * Add a pad at coordinates (x, y) + */ + +void HypFile::Hyp::add_pad(double pad_x, double pad_y, Pad pad) +{ + /* convert to polygon */ + Polygon pad_poly = pad2poly(pad_x, pad_y, pad); + + /* add pad to copper */ + add_polygon(pad_poly); + + return; +} + +/* + * Add via to list of via holes + */ + +void HypFile::Hyp::add_via(double x, double y, std::string layer0_name, std::string layer1_name, double radius) +{ + if (radius != 0.0) { + + /* lookup drill vertical begin and end */ + double z0 = stackup.back().z0; /* defaults to bottom */ + double z1 = stackup.front().z1; /* defaults to top */ + + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) + if (layer0_name == l->layer_name) { + z0 = l->z0; + z1 = l->z1; + } + + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) + if (layer1_name == l->layer_name) { + if (l->z1 > z1) z1 = l->z1; + if (l->z0 < z0) z0 = l->z0; + } + + Via v; + v.x = x; + v.y = y; + v.layer0_name = layer0_name; + v.layer1_name = layer1_name; + v.z0 = z0; + v.z1 = z1; + v.radius = radius; + net.back().via.push_back(v); + } + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_netclass.cc b/hyp2mat/lib/exec_netclass.cc new file mode 100644 index 0000000..9e592c0 --- /dev/null +++ b/hyp2mat/lib/exec_netclass.cc @@ -0,0 +1,76 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'NET_CLASS' record. + * Defines a class of nets. + */ + +bool HypFile::Hyp::exec_net_class(parse_param& h) +{ + if (trace_hyp) cerr << "net_class: net_class_name = " << h.net_class_name << endl; + + NetClass nc; + nc.net_class_name = h.net_class_name; + net_class.push_back(nc); + + return false; +} + +/* + * Hyperlynx 'A' attribute subrecord of 'NET_CLASS' record. + * Defines net class attributes as name/value pairs. + */ + +bool HypFile::Hyp::exec_net_class_attribute(parse_param& h) +{ + if (trace_hyp) cerr << "netclass_attribute: name = " << h.name << " value = " << h.value << endl; + + Attribute a; + a.name = h.name; + a.value = h.value; + + net_class.back().attribute.push_back(a); + + return false; +} + +/* + * Hyperlynx 'N' subrecord of 'NET_CLASS' record. + * Defines membership of a net class. + */ + +bool HypFile::Hyp::exec_net_class_element(parse_param& h) +{ + if (trace_hyp) cerr << "net_class_element: net_name = " << h.net_name << endl; + + if (!net_class.empty()) + net_class.back().net_class_element.push_back(h.net_name); + + return false; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_padstack.cc b/hyp2mat/lib/exec_padstack.cc new file mode 100644 index 0000000..0c2435e --- /dev/null +++ b/hyp2mat/lib/exec_padstack.cc @@ -0,0 +1,269 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'PADSTACK' record. + * Defines a padstack. + */ + +/* + * Hyperlynx padstack-element subrecord of 'PADSTACK' record. + * Defines an individual pad in a padstack. + */ + +bool HypFile::Hyp::exec_padstack_element(parse_param& h) +{ + if (trace_hyp) { + cerr << "padstack_element:"; + if (h.padstack_name_set) cerr << " padstack_name = " << h.padstack_name; + if (h.drill_size_set) cerr << " drill_size = " << h.drill_size; + cerr << " layer_name = " << h.layer_name; + cerr << " pad_shape = " << h.pad_shape; + cerr << " pad_sx = " << h.pad_sx; + cerr << " pad_sy = " << h.pad_sy; + cerr << " pad_angle = " << h.pad_angle; + if (h.pad_type_set & (h.pad_type == PAD_TYPE_THERMAL_RELIEF)) { + cerr << " thermal_clear_shape = " << h.thermal_clear_shape; + cerr << " thermal_clear_sx = " << h.thermal_clear_sx; + cerr << " thermal_clear_sy = " << h.thermal_clear_sy; + cerr << " thermal_clear_angle = " << h.thermal_clear_angle; + } + if (h.pad_type_set) cerr << " pad_type = " << h.pad_type; + cerr << endl; + } + + h.drill_size *= unit; + h.pad_sx *= unit; + h.pad_sy *= unit; + h.thermal_clear_sx *= unit; + h.thermal_clear_sy *= unit; + + /* new padstack */ + if (h.padstack_name_set) { + Padstack pdstack; + pdstack.padstack_name = h.padstack_name; + if (h.drill_size_set) + pdstack.drill_size = h.drill_size; + else + pdstack.drill_size = 0; + + /* add new padstack to list of padstacks */ + padstack.push_back(pdstack); + } + + /* new pad */ + Pad p; + + p.layer_name = h.layer_name; + + /* pad type */ + if (h.pad_type_set) p.pad_type = h.pad_type; + else if (p.layer_name == "ADEF") p.pad_type = PAD_TYPE_ANTIPAD; + else p.pad_type = PAD_TYPE_METAL; + + if (h.pad_shape == 0) + p.pad_shape = PAD_SHAPE_OVAL; + else if (h.pad_shape == 1) + p.pad_shape = PAD_SHAPE_RECTANGULAR; + else if (h.pad_shape == 2) + p.pad_shape = PAD_SHAPE_OBLONG; + else if (h.pad_shape == -1) + p.pad_shape = PAD_SHAPE_OVAL; /* workaround for Altium bug */ + else { + error("unknown pad shape"); + return true; + } + + p.pad_sx = h.pad_sx; + p.pad_sy = h.pad_sy; + p.pad_angle = h.pad_angle; + + if (p.pad_type == PAD_TYPE_THERMAL_RELIEF) { + if (h.thermal_clear_shape == 0) + p.thermal_clear_shape = PAD_SHAPE_OVAL; + else if (h.thermal_clear_shape == 1) + p.thermal_clear_shape = PAD_SHAPE_RECTANGULAR; + else if (h.thermal_clear_shape == 2) + p.thermal_clear_shape = PAD_SHAPE_OBLONG; + else { + error("unknown thermal clear shape"); + return true; + } + + p.thermal_clear_sx = h.thermal_clear_sx; + p.thermal_clear_sy = h.thermal_clear_sy; + p.thermal_clear_angle = h.thermal_clear_angle; + } + else { + p.thermal_clear_shape = PAD_SHAPE_OVAL; + p.thermal_clear_sx = 0; + p.thermal_clear_sy = 0; + p.thermal_clear_angle = 0; + } + + /* add new pad to list of pads of current padstack */ + padstack.back().pads.push_back(p); + + return false; +} + +/* + * Note: + * MDEF and ADEF layer names have special meaning. + * MDEF specified the default pad for signal or plane layers. + * If a signal or plane layer has no metal pad, and an MDEF default pad is specified, the MDEF pad is applied to the layer. + * ADEF specifies the default anti-pad (non-conducting hole in the copper) for plane layers. + * If a plane layer has no anti-pad, and an ADEF default pad is specified, the ADEF pad is applied to the layer. + */ + +bool HypFile::Hyp::exec_padstack_end(parse_param& h) +{ + if (trace_hyp) { + cerr << "padstack_end:"; + cerr << endl; + }; + + PadList new_pads; + + /* + * Loop through stackup. + * Calculate new padstack, where all MDEF and ADEF pads have been expanded into one or more metal layers. + */ + + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) { + /* Loop through pad list */ + bool layer_has_pad = false; + bool layer_has_antipad = false; + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) { + if (l->layer_name == p->layer_name) { + new_pads.push_back(*p); + layer_has_pad = layer_has_pad || (p->pad_type == PAD_TYPE_METAL); + layer_has_antipad = layer_has_antipad || (p->pad_type == PAD_TYPE_ANTIPAD); + } + } + + /* If a plane layer has no antipad, add ADEF pad if specified */ + if (!layer_has_antipad && (l->layer_type == LAYER_PLANE)) + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) + if (p->layer_name == "ADEF") { + Pad new_pad = *p; + new_pad.layer_name = l->layer_name; /* change ADEF into current layer name */ + new_pads.push_back(new_pad); + } + + /* If a signal or plane layer has no pad, add MDEF pad if specified */ + if (!layer_has_pad && ((l->layer_type == LAYER_SIGNAL) || (l->layer_type == LAYER_PLANE)) ) + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) + if (p->layer_name == "MDEF") { + Pad new_pad = *p; + new_pad.layer_name = l->layer_name; /* change MDEF into current layer name */ + new_pads.push_back(new_pad); + layer_has_pad = true; + } + + /* + * Determine begin and end layers of via + */ + + /* via is from top to bottom if at least one pad is metal MDEF */ + bool is_through_hole_via = false; + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) + if ((p->layer_name == "MDEF") && (p->pad_type == PAD_TYPE_METAL)) is_through_hole_via = true; + + /* determine via top and bottom layers */ + std::string pad_top_layer; + std::string pad_bottom_layer; + + if (is_through_hole_via) { + pad_top_layer = stackup.front().layer_name; /* via begins at top layer */ + pad_bottom_layer = stackup.back().layer_name; /* via ends at bottom layer */ + } + else { + /* via begins at highest layer with metal pad, and ends at lowest layer with a metal pad */ + bool layer_found = false; + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) + if ((l->layer_name == p->layer_name) && (p->pad_type == PAD_TYPE_METAL)) { + if (!layer_found) { + pad_top_layer = l->layer_name; + layer_found = true; + } + pad_bottom_layer = l->layer_name; + }; + + if (!layer_found) std::cerr << "warning: padstack without metal:" << h.padstack_name << std::endl; + + } + + /* + * Fill in missing via pads + */ + + // XXX Fixme + // XXX Check if loops need to begin at pad_top_layer / end at pad_bottom_layer + // XXX Check if pad_top_layer / pad_bottom_layer need visibility + // XXX Check if need mechanism to remove pads? + + /* If a signal or plane layer has no pad, and no MDEF pad, copy an existing pad */ + bool is_through_hole_pad = is_through_hole_via && (padstack.back().drill_size > 0); + if (!layer_has_pad && is_through_hole_pad && ((l->layer_type == LAYER_SIGNAL) || (l->layer_type == LAYER_PLANE)) ) { + Pad new_pad; + bool pad_found = false; + for (PadList::iterator p = padstack.back().pads.begin(); p != padstack.back().pads.end(); ++p) { + if (p->pad_type != PAD_TYPE_METAL) continue; + if (!pad_found) { + /* if all padstack pads are equal, create a pad on this layer which is like the others */ + new_pad = *p; + new_pad.layer_name = l->layer_name; + pad_found = true; + } + else if ((p->pad_shape != new_pad.pad_shape) || (p->pad_sx != new_pad.pad_sx) || (p->pad_sy != new_pad.pad_sy) || (p->pad_angle != new_pad.pad_angle)) { + /* if padstack pads are different, create a circular pad with a diameter equal to the smallest pad dimension */ + double radius = new_pad.pad_sx; + if (radius > new_pad.pad_sy) radius = new_pad.pad_sy; + if (radius > p->pad_sx) radius = p->pad_sx; + if (radius > p->pad_sy) radius = p->pad_sy; + new_pad.pad_shape = PAD_SHAPE_OVAL; + new_pad.pad_sx = radius; + new_pad.pad_sy = radius; + new_pad.pad_angle = 0; + } + } + /* add newly created pad to padstack */ + if (pad_found) new_pads.push_back(new_pad); + } + + } + + /* put new padstack in place. */ + + padstack.back().pads = new_pads; + + return false; + +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_polygon.cc b/hyp2mat/lib/exec_polygon.cc new file mode 100644 index 0000000..88e8be2 --- /dev/null +++ b/hyp2mat/lib/exec_polygon.cc @@ -0,0 +1,275 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'POLYGON' subrecord of 'NET' record. + * Draws polygonal metal area. + */ + +bool HypFile::Hyp::exec_polygon_begin(parse_param& h) +{ + if (trace_hyp) { + cerr << "polygon begin:"; + if (h.layer_name_set) cerr << " layer_name = " << h.layer_name; + if (h.width_set) cerr << " width = " << h.width; + if (h.polygon_type_set) { + cerr << " polygon_type = " << h.polygon_type << " "; + switch (h.polygon_type) { + case POLYGON_TYPE_PLANE: cerr << "POLYGON_TYPE_PLANE"; break; + case POLYGON_TYPE_POUR: cerr << "POLYGON_TYPE_POUR"; break; + case POLYGON_TYPE_COPPER: cerr << "POLYGON_TYPE_COPPER"; break; + default: cerr << "Error"; break; + } + } + if (h.id_set) cerr << " id = " << h.id; + cerr << " x = " << h.x << " y = " << h.y << endl; + } + + if (!h.layer_name_set) { + error("expected polygon layer L = "); + return true; + } + + if (!h.width_set) h.width = 0; + + if (!h.polygon_type_set) h.polygon_type = POLYGON_TYPE_PLANE; + + if (!h.id_set) { + error("expected polygon id ID = "); + return true; + } + + h.width *= unit; + h.x *= unit; + h.y *= unit; + + current_polygon.id = h.id; + current_polygon.polygon_type = h.polygon_type; + current_polygon.width = h.width; + current_polygon.positive = true; + current_polygon.layer_name = h.layer_name; + current_polygon.vertex.clear(); + current_polygon.vertex.push_back(Point(h.x, h.y)); + + return false; +} + +bool HypFile::Hyp::exec_polygon_end(parse_param& h) +{ + if (trace_hyp) cerr << "polygon end" << endl; + + add_polygon(current_polygon); + + return false; +} + +/* + * Hyperlynx 'POLYVOID' subrecord of 'NET' record. + * Creates polygonal hole in metal area. + */ + +bool HypFile::Hyp::exec_polyvoid_begin(parse_param& h) +{ + if (trace_hyp) { + cerr << "polyvoid begin:"; + if (h.id_set) cerr << " id = " << h.id; + cerr << " x = " << h.x << " y = " << h.y << endl; + } + + if (!h.id_set) { + error("expected polygon id ID = "); + return true; + } + + h.x *= unit; + h.y *= unit; + + current_polygon.id = h.id; + current_polygon.positive = false; + current_polygon.vertex.clear(); + current_polygon.vertex.push_back(Point(h.x, h.y)); + + /* inherit layer_name and width from parent */ + for (PolygonList::iterator i = net.back().metal[h.id].begin(); i != net.back().metal[h.id].end(); ++i) { + if (i->positive) { + current_polygon.polygon_type = i->polygon_type; + current_polygon.width = i->width; + current_polygon.layer_name = i->layer_name; + return false; + } + } + + /* no positive polygon/polyline found with same id */ + error("polyvoid does not have parent polygon or polyline with same id"); + return true; +} + +bool HypFile::Hyp::exec_polyvoid_end(parse_param& h) +{ + if (trace_hyp) cerr << "polyvoid end" << endl; + + add_polygon(current_polygon); + + return false; +} + +/* + * Hyperlynx 'POLYLINE' subrecord of 'NET' record. + * Draws metal trace. + */ + +bool HypFile::Hyp::exec_polyline_begin(parse_param& h) +{ + if (trace_hyp) { + cerr << "polyline begin:"; + if (h.layer_name_set) cerr << " layer_name = " << h.layer_name; + if (h.width_set) cerr << " width = " << h.width; + if (h.polygon_type_set) { + cerr << " polygon_type = " << h.polygon_type << " "; + switch (h.polygon_type) { + case POLYGON_TYPE_PLANE: cerr << "POLYGON_TYPE_PLANE"; break; + case POLYGON_TYPE_POUR: cerr << "POLYGON_TYPE_POUR"; break; + case POLYGON_TYPE_COPPER: cerr << "POLYGON_TYPE_COPPER"; break; + default: cerr << "Error"; break; + } + } + if (h.id_set) cerr << " id = " << h.id; + cerr << " x = " << h.x << " y = " << h.y << endl; + } + + if (!h.layer_name_set) { + error("expected polygon layer L = "); + return true; + } + + if (!h.width_set) { + error("expected polygon width W = "); + return true; + } + + if (!h.polygon_type_set) h.polygon_type = POLYGON_TYPE_PLANE; + + if (!h.id_set) { + error("expected polygon id ID = "); + return true; + } + + h.width *= unit; + h.x *= unit; + h.y *= unit; + + current_polygon.id = h.id; + current_polygon.polygon_type = h.polygon_type; + current_polygon.width = h.width; + current_polygon.positive = true; + current_polygon.layer_name = h.layer_name; + current_polygon.vertex.clear(); + current_polygon.vertex.push_back(Point(h.x, h.y)); + + return false; +} + +bool HypFile::Hyp::exec_polyline_end(parse_param& h) +{ + if (trace_hyp) cerr << "polyline end" << endl; + + // Draw polyline as sequence of line segments + for (PointList::iterator i = current_polygon.vertex.begin(); (i != current_polygon.vertex.end()) && (i != --current_polygon.vertex.end()); ++i) { + Polygon line_segment; + PointList::iterator j = i + 1; + line_segment = segment2poly(i->x, i->y, j->x, j->y, current_polygon.width); + line_segment.id = current_polygon.id; + line_segment.polygon_type = current_polygon.polygon_type; + line_segment.positive = true; + line_segment.layer_name = current_polygon.layer_name; + line_segment.width = 0; + add_polygon(line_segment); + } + + return false; +} + +/* + * Hyperlynx 'LINE' subrecord of 'NET' record. + * Draws straight metal trace. + */ + +bool HypFile::Hyp::exec_line(parse_param& h) +{ + if (trace_hyp) cerr << "line: x = " << h.x << " y = " << h.y << endl; + + h.x *= unit; + h.y *= unit; + + /* add point to current polygon */ + current_polygon.vertex.push_back(Point(h.x, h.y)); + + return false; +} + +/* + * Hyperlynx 'CURVE' subrecord of 'NET' record. + * Draws curved metal trace. + */ + +bool HypFile::Hyp::exec_curve(parse_param& h) +{ + if (trace_hyp) { + cerr << "curve: x1 = " << h.x1 << " y1 = " << h.y1 << " x2 = " << h.x2 << " y2 = " << h.y2; + cerr << " xc = " << h.xc << " yc = " << h.yc << " r = " << h.r << endl; + } + + h.x1 *= unit; + h.y1 *= unit; + h.x2 *= unit; + h.y2 *= unit; + h.xc *= unit; + h.yc *= unit; + h.r *= unit; + h.width *= unit; + + /* calculate arc */ + /* 'CURVE' draws arc counterclockwise */ + Polygon arc = arc2poly(h.x1, h.y1, h.x2, h.y2, h.xc, h.yc, h.r, false); + + /* calculate distance between polygon end vertex and arc begin and arc end */ + double endvertex_x = current_polygon.vertex.back().x; + double endvertex_y = current_polygon.vertex.back().y; + double dist1 = (endvertex_x - h.x1) * (endvertex_x - h.x1) + (endvertex_y - h.y1) * (endvertex_y - h.y1); + double dist2 = (endvertex_x - h.x2) * (endvertex_x - h.x2) + (endvertex_y - h.y2) * (endvertex_y - h.y2); + + /* add arc to current polygon. begin with point closest to end vertex. */ + if (dist1 <= dist2) + /* add arc from (x1, y1) to (x2, y2) */ + current_polygon.vertex.insert(current_polygon.vertex.end(), arc.vertex.begin(), arc.vertex.end()); + else + /* add arc from (x2, y2) to (x1, y1) */ + current_polygon.vertex.insert(current_polygon.vertex.end(), arc.vertex.rbegin(), arc.vertex.rend()); + + return false; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_stackup.cc b/hyp2mat/lib/exec_stackup.cc new file mode 100644 index 0000000..5680cd5 --- /dev/null +++ b/hyp2mat/lib/exec_stackup.cc @@ -0,0 +1,446 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iterator> +#include <iostream> +#include <algorithm> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'STACKUP' record. + * Defines pcb layers. + */ + +/* + * Hyperlynx 'OPTIONS' subrecord of STACKUP record. + * Defines dielectric constant and loss tangent of metal layers. + */ + +bool HypFile::Hyp::exec_options(parse_param& h) +{ + if (trace_hyp) cerr << "options: use_die_for_metal = " << h.use_die_for_metal << endl; + use_die_for_metal = h.use_die_for_metal; + + return false; +} + +/* + * Hyperlynx 'SIGNAL' subrecord of 'STACKUP' record. + * Defines a pcb signal layer. + */ + +bool HypFile::Hyp::exec_signal(parse_param& h) +{ + if (trace_hyp) + cerr << "signal:"; + trace_layer(h); + + add_metal_layer(h); + + /* + *The difference between signal and plane layers is + * - signal layers are drawn positive, plane layers are drawn negative + * - signal layers need a name + */ + + stackup.back().layer_type = LAYER_SIGNAL; + + if (!h.layer_name_set) + error("missing signal layer name" ); + + calc_layer_position(); + calc_layer_epsilon(); + + return false; +} + +/* + * Hyperlynx 'DIELECTRIC' subrecord of 'STACKUP' record. + * Defines a pcb dielectric layer. + */ + +bool HypFile::Hyp::exec_dielectric(parse_param& h) +{ + if (trace_hyp) + cerr << "dielectric:"; + trace_layer(h); + + add_dielectric_layer(h); + + calc_layer_position(); + calc_layer_epsilon(); + + return false; +} + +/* + * Hyperlynx 'PLANE' subrecord of 'STACKUP' record. + * Defines a pcb power or signal plane. + */ + +bool HypFile::Hyp::exec_plane(parse_param& h) +{ + if (trace_hyp) + cerr << "plane:"; + trace_layer(h); + + add_metal_layer(h); + + stackup.back().layer_type = LAYER_PLANE; + + calc_layer_position(); + calc_layer_epsilon(); + + return false; +} + +/* + * Debug output for layers + */ + +bool HypFile::Hyp::trace_layer(parse_param& h) +{ + if (trace_hyp) { + if (h.thickness_set) cerr << " thickness = " << h.thickness; + if (h.plating_thickness_set) cerr << " plating_thickness = " << h.plating_thickness; + if (h.bulk_resistivity_set) cerr << " bulk_resistivity = " << h.bulk_resistivity; + if (h.temperature_coefficient_set) cerr << " temperature_coefficient = " << h.temperature_coefficient; + if (h.epsilon_r_set) cerr << " epsilon_r = " << h.epsilon_r; + if (h.loss_tangent_set) cerr << " loss_tangent = " << h.loss_tangent; + if (h.conformal_set) cerr << " conformal = " << h.conformal; + if (h.prepreg_set) cerr << " prepreg = " << h.prepreg; + if (h.layer_name_set) cerr << " layer_name = " << h.layer_name; + if (h.material_name_set) cerr << " material_name = " << h.material_name; + if (h.plane_separation_set) cerr << " plane_separation = " << h.plane_separation; + cerr << endl; + } + + return true; +} + +/* + * Add a signal or plane layer. + */ + +bool HypFile::Hyp::add_metal_layer(parse_param& h) +{ + Layer l; + + l.layer_type = LAYER_SIGNAL; + + if (h.thickness_set) + l.thickness = h.thickness * metal_thickness_unit; + else { + error ("thickness missing"); + return true; + } + + if (h.plating_thickness_set) + l.plating_thickness = h.plating_thickness * metal_thickness_unit; + else + l.plating_thickness = 0; + + if (h.bulk_resistivity_set) + l.bulk_resistivity = h.bulk_resistivity; + else + l.bulk_resistivity = copper_bulk_resistivity; + + if (h.temperature_coefficient_set) + l.temperature_coefficient = h.temperature_coefficient; + else + l.temperature_coefficient = copper_temperature_coefficient; + + if (h.conformal_set) { + error("only dielectric layers conformal"); + return true; + } + else + l.conformal = false; + + if (h.epsilon_r_set) + l.epsilon_r = h.epsilon_r; + else + l.epsilon_r = fr4_epsilon_r; + + if (h.loss_tangent_set) + l.loss_tangent = h.loss_tangent; + else + l.loss_tangent = fr4_loss_tangent; + + // use use_die_for_metal ? XXX + + if (h.prepreg_set) { + error("only dielectric layers prepreg"); + return true; + } + else + l.prepreg = false; + + if (h.layer_name_set) + l.layer_name = h.layer_name; + else { + error("missing signal layer name" ); + l.layer_name = ""; // Continue anyhow + } + + if (h.material_name_set) + l.material_name = h.material_name; + else + l.material_name = ""; + + if (h.plane_separation_set) + l.plane_separation = h.plane_separation * unit; + else + l.plane_separation = board.plane_separation; + + // Add layer to stackup + stackup.push_back(l); + + return false; +} + +/* + * add a substrate layer + */ + +bool HypFile::Hyp::add_dielectric_layer(parse_param& h) +{ + + Layer l; + + l.layer_type = LAYER_DIELECTRIC; + + if (h.thickness_set) + l.thickness = h.thickness * unit; + else { + error ("thickness missing"); + return true; + } + + if (h.plating_thickness_set) { + error ("dielectric has no plating thickness"); + return true; + } + else + l.plating_thickness = 0; + + if (h.bulk_resistivity_set) { + error ("dielectric has no bulk resistivity"); + return true; + } + else + l.bulk_resistivity = 0; + + if (h.temperature_coefficient_set) { + error ("dielectric has no bulk resistivity temperature coefficient"); + return true; + } + else + l.temperature_coefficient = 0; + + if (h.conformal_set) + l.conformal = h.conformal; + else + l.conformal = false; + + if (h.epsilon_r_set) + l.epsilon_r = h.epsilon_r; + else if (l.conformal) + l.epsilon_r = conformal_epsilon_r; + else + l.epsilon_r = fr4_epsilon_r; + + if (h.loss_tangent_set) + l.loss_tangent = h.loss_tangent; + else + l.loss_tangent = fr4_loss_tangent; + + if (h.prepreg_set) + l.prepreg = h.prepreg; + else + l.prepreg = true; + + if (h.layer_name_set) + l.layer_name = h.layer_name; + else + l.layer_name = ""; + + if (h.material_name_set) + l.material_name = h.material_name; + else + l.material_name = ""; + + if (h.plane_separation_set) { + error ("dielectric has no plane separation"); + return true; + } + else + l.plane_separation = 0; + + // Add layer to stackup + stackup.push_back(l); + + return false; +} + + /* + * Recalculate vertical position of all layers. + * Layers are ordered top first, bottom last. + * Called when a new layer is added. + */ + +bool HypFile::Hyp::calc_layer_position() +{ + // calculate vertical position, from bottom to top + double z_top = 0; // current vertical position + for (LayerList::reverse_iterator it = stackup.rbegin(); it != stackup.rend(); ++it) { + if (it->layer_type == LAYER_DIELECTRIC) { + /* substrate and prepreg */ + it->z0 = z_top; + it->z1 = z_top + it->thickness; + z_top = it->z1; + } + else { + /* metal layers */ + it->z0 = z_top; + it->z1 = z_top; + } + } + + return true; +} + +bool HypFile::Hyp::calc_layer_epsilon() +{ + + /* no calculation needed if dielectric constant have been specified */ + if (use_die_for_metal) + return true; + + /* calculate dielectric constants */ + for (LayerList::iterator i = stackup.begin(); i != stackup.end(); ++i) { + + /* skip dielectric layers */ + if (i->layer_type == LAYER_DIELECTRIC) + continue; + + /* find first dielectric layer above current metal layer */ + bool upper_layer_found = false; + LayerList::iterator upper_layer; + for (LayerList::iterator j = stackup.begin(); j != i; ++j) + if (j->layer_type == LAYER_DIELECTRIC) { + upper_layer_found = true; + upper_layer = j; + }; + + /* find first dielectric layer below current metal layer */ + bool lower_layer_found = false; + LayerList::iterator lower_layer; + for (LayerList::iterator j = i; j != stackup.end(); ++j) + if (j->layer_type == LAYER_DIELECTRIC) { + lower_layer_found = true; + lower_layer = j; + break; + }; + + if (!upper_layer_found || !lower_layer_found) { + /* assume outer copper layers are in air */ + i->epsilon_r = 1.0; + i->loss_tangent = 0.0; + } + else if (upper_layer->prepreg && !lower_layer->prepreg) { + /* upper layer is prepreg. use values of upper layer */ + i->epsilon_r = upper_layer->epsilon_r; + i->loss_tangent = upper_layer->loss_tangent; + } + else if (!upper_layer->prepreg && lower_layer->prepreg) { + /* lower layer is prepreg. use values of lower layer */ + i->epsilon_r = lower_layer->epsilon_r; + i->loss_tangent = lower_layer->loss_tangent; + } + else { + /* use average of upper and lower layer */ + i->epsilon_r = (upper_layer->epsilon_r + lower_layer->epsilon_r)/2; + i->loss_tangent = (upper_layer->loss_tangent + lower_layer->loss_tangent) /2; + } + } + + return false; +} + +/* + * Flood specified layers with copper + */ + +void HypFile::Hyp::flood_layers_with_copper() +{ + + /* check if we have to flood all plane layers */ + bool flood_plane_layers = std::find(flood_layers.begin(), flood_layers.end(), "plane_layers") != flood_layers.end(); /* look for special value "plane_layers" */ + + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) { + + /* metallic layers only */ + if (l->layer_type == LAYER_DIELECTRIC) continue; + + /* check if we have to flood this layer */ + bool flood_this_layer = std::find(flood_layers.begin(), flood_layers.end(), l->layer_name) != flood_layers.end(); + + /* flood with copper if we have to flood this layer, or if this is a plane layer and we have to flood all plane layers */ + if (!(flood_this_layer || (flood_plane_layers && (l->layer_type == LAYER_PLANE)))) continue; + + /* create default copper on plane layer */ + Polygon default_copper; + default_copper.vertex = board.edge.front().vertex; /* polygon is board outline */ + default_copper.polygon_type = POLYGON_TYPE_PLANE; + default_copper.net_name = l->layer_name; /* flood layer VCC with copper belonging to net VCC, layer GND with copper belonging to net GND */ + default_copper.id = -1; + default_copper.positive = true; + default_copper.width = 0.0; + default_copper.plane_separation = -1.0; + default_copper.left_plane_separation = -1.0; + default_copper.layer_name = l->layer_name; + + /* Look up net with same name as layer */ + NetList::iterator n; + for (n = net.begin(); n != net.end(); ++n) + if (n->net_name == l->layer_name) { + add_polygon_to_net(*n, default_copper); + break; + } + + /* Net with same name as layer not found. Create net */ + if (n == net.end()) { + Net plane_net; + plane_net.net_name = l->layer_name; + plane_net.plane_separation = -1.0; + net.push_back(plane_net); + add_polygon_to_net(net.back(), default_copper); + } + + } + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/exec_supplies.cc b/hyp2mat/lib/exec_supplies.cc new file mode 100644 index 0000000..d010516 --- /dev/null +++ b/hyp2mat/lib/exec_supplies.cc @@ -0,0 +1,57 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "hypfile.h" + +using namespace std; +using namespace HypFile; + +/* + * Hyperlynx 'S' subrecord of 'SUPPLIES' record. + * Specifies a power supply net. + */ + +bool HypFile::Hyp::exec_supplies(parse_param& h) +{ + if (trace_hyp) { + cerr << "supplies: name = " << h.name; + cerr << " value_float = " << h.value_float; + cerr << " voltage_specified = " << h.voltage_specified; + cerr << " conversion = " << h.conversion; + cerr << endl; + } + + Supply s; + + s.name = h.name; + + s.value_float = h.value_float; + + s.voltage_specified = h.voltage_specified; + + s.conversion = h.conversion; + + supply.push_back(s); + + return false; +} + +/* not truncated */ diff --git a/hyp2mat/lib/hyp2mat.h b/hyp2mat/lib/hyp2mat.h new file mode 100644 index 0000000..94a095e --- /dev/null +++ b/hyp2mat/lib/hyp2mat.h @@ -0,0 +1,224 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + Class PCB defines a generic pcb board as a stack of dielectric and copper. + The class is intended as the greatest common denominator of formats. + The board outline is defined as a polygon, possibly with polygonal holes. + For each layer, the copper is defined as a list of polygons, each polygon possibly with polygonal holes. + Vias provide connections between the different copper layers. + All dimensions are in meter. + + The following methods are defined: + ReadHyperLynx reads a hyperlynx file. + WritePDF prints a PCB to a pdf file. + WriteCSXCAD converts a PCB to an OpenEMS matlab script. + + */ + + +#ifndef PCB_H +#define PCB_H + +#include <string> +#include <vector> + +namespace Hyp2Mat { + + std::string version(); + + /* polygon types */ + + struct FloatPoint { + double x; + double y; + FloatPoint(double x_val = 0, double y_val = 0): x(x_val), y(y_val) {}; + bool operator==(const FloatPoint &right) const { return ((x == right.x) && ( y == right.y)); }; + bool operator!=(const FloatPoint &right) const { return ((x != right.x) || ( y != right.y)); }; + }; + + typedef std::vector<FloatPoint> FloatPolygon; + + struct FloatPoly { + FloatPolygon poly; + bool is_hole; + int nesting_level; /* hole within polygon within hole within ... */ + }; + + typedef std::vector<FloatPoly> FloatPolygons; + + /* + * a pin has a reference and an outline + */ + + class Pin { + public: + Pin(); + std::string ref; /* reference, e.g. U1.1 */ + double x; /* pad center coordinates */ + double y; + double z0; /* bottom of copper layer */ + double z1; /* top of copper layer */ + std::string layer_name; + FloatPolygon metal; /* pad outline */ + }; + + typedef std::vector<Pin> PinList; + + /* + * Layers + */ + + enum layer_enum { LAYER_SIGNAL, LAYER_DIELECTRIC, LAYER_PLANE }; + + class Layer { + public: + std::string layer_name; /* informational only */ + std::string material_name; /* informational only */ + layer_enum layer_type; + FloatPolygons metal; + double thickness; /* layer thickness */ + double z0; /* vertical position of bottom of layer */ + double z1; /* vertical position of top of layer */ + /* dielectric */ + double epsilon_r; + double loss_tangent; + /* copper on signal and plane layers */ + double bulk_resistivity; + double resistivity_temp_coeff; /* temperature coefficient of resistivity */ + }; + + typedef std::vector<Layer> LayerList; + + /* + * a via is a cylindrical, conductive hole. + */ + + class Via { + public: + double x; + double y; + double z0; /* bottom */ + double z1; /* top */ + double radius; + }; + + typedef std::vector<Via> ViaList; + + /* + * Devices + */ + + enum device_value_enum { DEVICE_VALUE_FLOAT, DEVICE_VALUE_STRING, DEVICE_VALUE_NONE }; + + class Device { + public: + Device(); + std::string name; + std::string ref; /* reference, e.g. R1, C2, L3, IC4 */ + device_value_enum value_type; /* whether to use value_float or value_string */ + double value_float; /* in ohms, farad, henry */ + std::string value_string; + std::string layer_name; + }; + + typedef std::vector<Device> DeviceList; + + /* board dimensions */ + class Bounds { + public: + Bounds(); + double x_min; + double x_max; + double y_min; + double y_max; + double z_min; + double z_max; + }; + + /* + * class PCB represents a pcb board. + */ + + class PCB { + public: + PCB(); + FloatPolygons board; /* board outline */ + LayerList stackup; + ViaList via; + double via_plating_thickness; + DeviceList device; + PinList pin; + + /* + * ReadHyperLynx loads the Hyperlynx file "filename". + * "layers" is the names of the layers to import. Default is importing all layers. + * "nets" is the names of the nets to import. Default is importing all nets. + */ + + void ReadHyperLynx(std::string filename, std::vector<std::string> layers = std::vector<std::string>(), std::vector<std::string> nets = std::vector<std::string>()); + void SetEpsilonR(double epsilon_r); /* set dielectric epsilon r. Overrides value in Hyperlynx file. */ + void SetBulkResistance(double bulk_resistivity); /* set dielectric bulk resistance. Overrides value in Hyperlynx file. */ + void SetLossTangent(double loss_tangent); /* set dielectric loss tangent. Overrides value in Hyperlynx file. */ + void SetGrid(double new_grid); /* set resolution of x and y coordinates */ + void SetArcPrecision(double new_arc_precision); /* set maximum difference between perfect circle arc and polygonal approximation */ + void SetClearance(double new_clearance); /* set trace-to-plane clearance */ + Bounds GetBounds(); /* gets board extension in x,y and z */ + void SetBounds(Bounds new_bounds); /* crops board in x,y and z */ + + void PrintSummary(); /* print layer summary */ + + /* + * WritePDF exports to the pdf file "filename". + * "hue", "saturation" and "brightness" are color options with a value between 0.0 and 1.0. + */ + + void WritePDF(std::string filename, double hue = -1, double saturation = -1, double brightness = -1); + + /* + * WriteCSXCAD exports csxcad matlab code to file "filename". + * If "pcb_outline" is true, the detailed pcb shape is exported, including pcb cutouts. + * If "pcb_outline" is false, a simple rectangular bounding box is output." + */ + + void WriteCSXCAD(std::string filename, bool pcb_outline = false, bool lossy_copper = false, bool metal_3d = false); + + unsigned int debug; /* setting debug to 0 switches debugging off */ + std::vector<std::string> flood_layers; /* names of layers to be flooded with copper */ + bool raw; /* set raw processing */ + private: + void _UserOverrides(); + void _CheckBoardOutline(); + Bounds _bounds; + double _arc_precision; + double _clearance; + double _epsilon_r; + bool _epsilon_r_override; + double _bulk_resistivity; + bool _bulk_resistivity_override; + double _loss_tangent; + bool _loss_tangent_override; + }; + +} + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/hyp2mat.pc.in b/hyp2mat/lib/hyp2mat.pc.in new file mode 100644 index 0000000..8635861 --- /dev/null +++ b/hyp2mat/lib/hyp2mat.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: hyp2mat +Description: Convert Hyperlynx PCB files to matlab scripts +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lhyp2mat +Libs.private: @LIBS@ +Cflags: -I${includedir} diff --git a/hyp2mat/lib/hyperlynx.cc b/hyp2mat/lib/hyperlynx.cc new file mode 100644 index 0000000..adaa043 --- /dev/null +++ b/hyp2mat/lib/hyperlynx.cc @@ -0,0 +1,348 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * HypFile is concerned with parsing the hyperlynx file, and contains many variables and methods + * which are only of interest during parsing. + * + * Hyp2Mat is interested in presenting an uncluttered interface. + * + * This is where data is passed on from HypFile to Hyp2Mat, from the obscure to the obvious. + */ + +#include <iostream> +#include <algorithm> + +#include "config.h" +#include "hyperlynx.h" +#include "polygon.h" +#include "crop.h" + +HyperLynx::HyperLynx () +{ + raw = false; + arc_precision = 0; + clearance = 0.0002; +} + + +/* + * Read a pcb in hyperlynx format + */ + +void HyperLynx::Read (std::string filename, Hyp2Mat::PCB& pcb) +{ + /* + * load hyperlynx file in hyp_file + */ + + bool success = LoadHypFile(pcb, filename, arc_precision); + if (!success) throw ("load hyp file error"); + + /* + * Copy hyperlynx file data from hyp_file to pcb + */ + saved_bounds = bounds; + CopyBoard(pcb); /* copy board outline. crop board in x, y axis */ + + CopyStackUp(pcb); /* copy layer stackup */ + bounds = AdjustBounds(pcb, saved_bounds); + CopyVias(pcb); /* copy vias */ + CopyViaPlating(pcb); /* copy via plating thickness */ + CopyDevices(pcb); /* copy device info */ + CopyPins(pcb); /* copy pin references */ + + bounds = AdjustBounds(pcb, saved_bounds); + CropLayers(pcb, bounds); /* crop board in z axis */ + + if (bounds.z_min == bounds.z_max) std::cerr << "warning: zero height board" << std::endl; + + /* + * Check whether to print all net names + */ + + /* if one of the nets is "?", print list of all nets */ + if (std::find(nets.begin(), nets.end(), "?") != nets.end()) + PrintNets(); + + return; +} + +/* + * Load Hyperlynx file in hyp_file + */ + +bool HyperLynx::LoadHypFile(Hyp2Mat::PCB& pcb, std::string filename, double arc_precision) +{ + hyp_file.trace_scanner = (pcb.debug == 4); + hyp_file.trace_parser = (pcb.debug == 3); + hyp_file.trace_hyp = (pcb.debug == 2); + + if (pcb.debug != 0) { + std::cerr << "hyp2mat " << VERSION << " using ClipperLib " << CLIPPER_VERSION << std::endl; + } + + if (arc_precision < 0) { + std::cerr << "arc precision negative" << std::endl; + arc_precision = 0; + } + hyp_file.arc_precision = arc_precision; + + hyp_file.flood_layers = flood_layers; + + bool success = hyp_file.load(filename); + + if (pcb.debug == 1) { + std::cerr << "writing hyp_file dump to hyp_debug.txt" << std::endl; + hyp_file.save ("hyp_debug.txt"); + } + + return success; +} + +/* + * copy board outline into polygon "board" and crop according to "bounds". + */ + +void HyperLynx::CopyBoard(Hyp2Mat::PCB& pcb) +{ + bool outer_edge = true; + + for (HypFile::PolygonList::iterator i = hyp_file.board.edge.begin(); i != hyp_file.board.edge.end(); ++i) { + Hyp2Mat::Polygon poly; + Hyp2Mat::FloatPolygon edge; + for (HypFile::PointList::iterator j = i->vertex.begin(); j != i->vertex.end(); ++j) + edge.push_back(Hyp2Mat::FloatPoint(j->x, j->y)); + poly.AddEdge(edge); + if (outer_edge) + board.Union(poly); + else + board.Difference(poly); + outer_edge = false; + } + + /* + * Cropping - Remove board outside bounds + */ + + pcb.board = board.Result(); /* store intermediate result */ + bounds = AdjustBounds(pcb, bounds); /* tighten bounds around board */ + board = CropPolygon(board, bounds); /* crop board. store board as integer polygon */ + pcb.board = board.Result(); /* board as floating point polygon */ + + return; +} + +/* + * copy vias + */ + +void HyperLynx::CopyVias(Hyp2Mat::PCB& pcb) +{ + for (HypFile::NetList::iterator i = hyp_file.net.begin(); i != hyp_file.net.end(); ++i) { + + /* check if we're interested in this net. if no nets are specified, copy all nets */ + bool net_wanted = nets.empty() || (std::find(nets.begin(), nets.end(), i->net_name) != nets.end()); + if (!net_wanted) continue; + + /* copy vias of current net */ + for (HypFile::ViaList::iterator j = i->via.begin(); j != i->via.end(); ++j) { + Hyp2Mat::Via v; + v.x = j->x; + v.y = j->y; + v.z0 = j->z0; + v.z1 = j->z1; + v.radius = j->radius; + + /* cropping */ + if (!board.IsEmpty() && ((v.x > bounds.x_max) || (v.x < bounds.x_min) || (v.y > bounds.y_max) || (v.y < bounds.y_min))) continue; // Only crop in x and y if board outline is defined + if ((v.z0 > bounds.z_max) && (v.z1 > bounds.z_max)) continue; + if ((v.z0 < bounds.z_min) && (v.z1 < bounds.z_min)) continue; + if (v.z1 > bounds.z_max) v.z1 = bounds.z_max; + if (v.z0 < bounds.z_min) v.z0 = bounds.z_min; + + /* add to list of vias */ + pcb.via.push_back(v); + } + } + + return; +} + +/* + * copy via plating + */ + +void HyperLynx::CopyViaPlating(Hyp2Mat::PCB& pcb) +{ + /* assume via plating thickness is identical to outer plating thickness */ + for (HypFile::LayerList::iterator l = hyp_file.stackup.begin(); l != hyp_file.stackup.end(); ++l) + if (l->layer_type != HypFile::LAYER_DIELECTRIC) { + /* first metal layer */ + pcb.via_plating_thickness = l->plating_thickness; + break; + } + + return; +} + +/* + * copy devices + */ + +void HyperLynx::CopyDevices(Hyp2Mat::PCB& pcb) +{ + for (HypFile::DeviceList::iterator i = hyp_file.device.begin(); i != hyp_file.device.end(); ++i) { + /* set up device */ + Hyp2Mat::Device dev; + dev.ref = i->ref; + dev.name = i->name; + dev.layer_name = i->layer_name; + + if (i->value_float_set) { + dev.value_float = i->value_float; + dev.value_type = Hyp2Mat::DEVICE_VALUE_FLOAT; + } + else if (i->value_string_set) { + dev.value_string = i->value_string; + dev.value_type = Hyp2Mat::DEVICE_VALUE_STRING; + } + else dev.value_type = Hyp2Mat::DEVICE_VALUE_NONE; + + /* add device to board */ + pcb.device.push_back(dev); + } + return; +} + +/* + * copy pins + */ + +void HyperLynx::CopyPins(Hyp2Mat::PCB& pcb) +{ + /* iterate over all pins */ + for (HypFile::NetList::iterator i = hyp_file.net.begin(); i != hyp_file.net.end(); ++i) { + + /* check if we're interested in this net. if no nets are specified, copy all nets */ + bool net_wanted = nets.empty() || (std::find(nets.begin(), nets.end(), i->net_name) != nets.end()); + if (!net_wanted) continue; + + for (HypFile::PinList::iterator j = i->pin.begin(); j != i->pin.end(); ++j) + /* find pin padstack */ + for (HypFile::PadstackList::iterator p = hyp_file.padstack.begin(); p != hyp_file.padstack.end(); ++p) + if (p->padstack_name == j->padstack_name) + /* copy this pin */ + CopyPin(pcb, *j, *p); + } + + return; +} + +/* + * copy a single pin + */ + +void HyperLynx::CopyPin(Hyp2Mat::PCB& pcb, HypFile::Pin& pin, HypFile::Padstack& padstack) +{ + Hyp2Mat::Pin new_pin; + new_pin.x = Hyp2Mat::Polygon::AlignToGrid(pin.x); + new_pin.y = Hyp2Mat::Polygon::AlignToGrid(pin.y); + new_pin.ref = pin.pin_reference; + + HypFile::Pad pad; + + std::string layer_name; + + /* now find pad */ + + /* how many layers does the padstack have? */ + if (padstack.pads.size() == 1) { + /* only a single layer: that's easy */ + pad = padstack.pads.front(); + } + else { + /* determine pin device name */ + size_t found = pin.pin_reference.find('.'); + if ((found == std::string::npos) || (found == 0)) return; /* device not found */ + std::string device_name = pin.pin_reference.substr(0, found); + + /* find device pin belongs to */ + bool device_found = false; + for (Hyp2Mat::DeviceList::iterator i = pcb.device.begin(); i != pcb.device.end(); ++i) + if (device_name == i->ref) { + device_found = true; + layer_name = i->layer_name; /* device layer */ + } + if (!device_found) return; + + /* we now know the device layer. Find this layer in the pin padstack. */ + bool pad_found = false; + for (HypFile::PadList::iterator p = padstack.pads.begin(); p != padstack.pads.end(); ++p) + if (p->layer_name == layer_name) { + pad_found = true; + pad = *p; + } + if (!pad_found) return; + } + + /* find layer vertical position */ + for (Hyp2Mat::LayerList::iterator l = pcb.stackup.begin(); l != pcb.stackup.end(); ++l) + if (pad.layer_name == l->layer_name) { + new_pin.layer_name = l->layer_name; + new_pin.z0 = l->z0; + new_pin.z1 = l->z1; + } + + /* convert pad to polygon, and copy pad shape */ + HypFile::Polygon pad_poly = hyp_file.pad2poly(new_pin.x, new_pin.y, pad); + new_pin.metal.clear(); + for (HypFile::PointList::iterator j = pad_poly.vertex.begin(); j != pad_poly.vertex.end(); ++j) + new_pin.metal.push_back(Hyp2Mat::FloatPoint(Hyp2Mat::Polygon::AlignToGrid(j->x), Hyp2Mat::Polygon::AlignToGrid(j->y))); + + /* add pin to pcb */ + pcb.pin.push_back(new_pin); + + return; +} + +/* + * Print all available nets + */ + +void HyperLynx::PrintNets() +{ + std::vector<std::string> net_names; + + for (HypFile::NetList::iterator i = hyp_file.net.begin(); i != hyp_file.net.end(); ++i) + net_names.push_back(i->net_name); + + sort(net_names.begin(), net_names.end()); + unique(net_names.begin(), net_names.end()); + + std::cout << "nets:"; + for (std::vector<std::string>::iterator i = net_names.begin(); i != net_names.end(); ++i) + std::cout << " " << *i; + std::cout << std::endl; + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/hyperlynx.h b/hyp2mat/lib/hyperlynx.h new file mode 100644 index 0000000..bfe3033 --- /dev/null +++ b/hyp2mat/lib/hyperlynx.h @@ -0,0 +1,79 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * loads a Hyperlynx file in a Hyp2Mat class + */ + +#ifndef HYPERLYNX_H +#define HYPERLYNX_H + +#include "hyp2mat.h" +#include "hypfile.h" +#include "polygon.h" + +class HyperLynx { +public: + HyperLynx(); + void Read (std::string filename, Hyp2Mat::PCB& pcb); + + /* parameters */ + std::vector<std::string> layers; + std::vector<std::string> nets; + bool raw; + double arc_precision; + double clearance; + std::vector<std::string> flood_layers; + Hyp2Mat::Bounds bounds; + +private: + HypFile::Hyp hyp_file; + Hyp2Mat::Polygon board; /* polygon of board outline */ + Hyp2Mat::Bounds saved_bounds; /* copy of bounds */ + + double layer_plane_separation; /* plane separation for this layer */ + double net_plane_separation; /* plane separation for this net on this layer */ + double polygon_plane_separation; /* plane separation for this polygon on this net on this layer */ + + bool LoadHypFile(Hyp2Mat::PCB& pcb, std::string filename, double arc_precision); + void CopyBoard(Hyp2Mat::PCB& pcb); /* copy board outline */ + + void CopyStackUp(Hyp2Mat::PCB& pcb); /* copy all layers, both metal and dielectric */ + void CopyLayer(Hyp2Mat::PCB& pcb, HypFile::Layer& hyp_layer); /* copy a single layer */ + Hyp2Mat::Polygon CopyCopper(Hyp2Mat::Layer layer, Hyp2Mat::FloatPolygons& raw_polygons); /* copy all the copper of a layer */ + Hyp2Mat::Polygon CopyNet(Hyp2Mat::Layer layer, HypFile::Net& hyp_net, polygon_type_enum poly_type, Hyp2Mat::FloatPolygons& raw_polygons); /* copy a single net on a layer */ + + Hyp2Mat::Polygon CopyPolygon(HypFile::PolygonList metal, Hyp2Mat::FloatPolygons& raw_polygons); /* copy a single polygon of a net on a layer */ + Hyp2Mat::Polygon PlaneSeparation(std::string layer_name, std::string net_name); + + void CopyVias(Hyp2Mat::PCB& pcb); + void CopyViaPlating(Hyp2Mat::PCB& pcb); + + void CopyDevices(Hyp2Mat::PCB& pcb); + + void CopyPins(Hyp2Mat::PCB& pcb); /* copy all pins to pcb */ + void CopyPin(Hyp2Mat::PCB& pcb, HypFile::Pin& pin, HypFile::Padstack& padstack); /* copy a single pin to pcb */ + + void PrintNets(); + }; + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/hypfile.cc b/hyp2mat/lib/hypfile.cc new file mode 100644 index 0000000..3535719 --- /dev/null +++ b/hyp2mat/lib/hypfile.cc @@ -0,0 +1,378 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <iomanip> +#include <sstream> +#include <cstdio> +#include <cmath> +#include "hypfile.h" +#include "parser.h" + +using namespace std; +using namespace HypFile; + +HypFile::Hyp::Hyp() + : trace_scanner(false), trace_parser(false), trace_hyp(false) +{ + unit = 1; + metal_thickness_unit = 1; + use_die_for_metal = false; + + min_circle_segments = 8; // 8 seems minimal, 16 seems more than sufficient. + arc_precision = 0; + pi = M_PI; + inches = 0.0254; /* inches to m */ + copper_imperial_weight = 1.341; /* metal thickness in ounces/ft2. 1 oz/ft2 copper = 1.341 mil */ + copper_metric_weight = 0.1116; /* metal thickness in grams/cm2. 1 gr/cm2 copper = 0.1116 cm */ + copper_bulk_resistivity = 1.724e-8; + copper_temperature_coefficient = 0.00393; + fr4_epsilon_r = 4.3; + fr4_loss_tangent = 0.020; + conformal_epsilon_r = 3.3; /* dielectric constant of conformal layer */ + board.plane_separation = -1.0; /* distance between PLANE polygon and copper of different nets; -1 if not set */ + +} + +/* + * parse a Hyperlynx file + */ + +bool HypFile::Hyp::load(const std::string &filename) +{ + if (trace_hyp) cerr << "parsing " << filename << endl; + + /* open input file */ + if (filename != "-") { + yyin = fopen (filename.c_str(),"r"); + if (!yyin) { + cerr << "Can't open input file " << filename << endl; + return false; + } + } + + /* parse input file */ + yy_flex_debug = trace_scanner; + yydebug = trace_parser; + int retval = yyparse(this); + + /* close input file */ + fclose(yyin); + + bool success = retval == 0; + return success; +} + +/* + * Error output + */ + +void HypFile::Hyp::error(const std::string& msg) +{ + cerr << msg << endl; +} + +/* + * Dumps internal structures for debugging. + */ + +bool HypFile::Hyp::save(const std::string& filename) +{ + /* + * Open file for output + */ + + if ((filename != "-") && (freopen(filename.c_str(), "w", stdout) == NULL)) { + std::ostringstream err_msg; + err_msg << "could not open '" << filename << "' for writing"; + error(err_msg.str()); + return true; + } + + /* + * Variables + */ + + cout << "unit = " << unit << endl; + cout << "metal_thickness_unit = " << metal_thickness_unit << endl; + cout << "use_die_for_metal = " << use_die_for_metal << endl; + cout << "min_circle_segments = " << min_circle_segments << endl; + cout << "arc_precision = " << arc_precision << endl; + cout << "pi = " << pi << endl; + cout << "inches = " << inches << endl; + cout << "copper_imperial_weight = " << copper_imperial_weight << endl; + cout << "copper_metric_weight = " << copper_metric_weight << endl; + cout << "copper_bulk_resistivity = " << copper_bulk_resistivity << endl; + cout << "copper_temperature_coefficient = " << copper_temperature_coefficient << endl; + cout << "fr4_epsilon_r = " << fr4_epsilon_r << endl; + cout << "fr4_loss_tangent = " << fr4_loss_tangent << endl; + cout << "conformal_epsilon_r = " << conformal_epsilon_r << endl; + + /* + * Export board + */ + + cout << "board" << endl; + for (PolygonList::iterator i = board.edge.begin(); i != board.edge.end(); ++i) { + cout << " polygon" << endl; + cout << " positive " << i->positive << endl; + for (PointList::iterator j = i->vertex.begin(); j != i->vertex.end(); ++j) { + cout << " x = " << j->x << endl; + cout << " y = " << j->y << endl; + } + } + + cout << " plane_separation = " << board.plane_separation << endl; + cout << " board_attribute" << endl; + for (AttributeList::iterator j = board.attribute.begin(); j != board.attribute.end(); ++j) { + cout << " name = '" << j->name << "'" << endl; + cout << " value = '" << j->value << "'" << endl; + } + + /* + * Export devices + */ + + cout << "device" << endl; + for (DeviceList::iterator dev_it = device.begin(); dev_it != device.end(); ++dev_it) { + cout << " device" << endl; + cout << " device_type = '" << dev_it->device_type << "'" << endl; + cout << " ref = '" << dev_it->ref << "'" << endl; + cout << " name = '" << dev_it->name << "'" << endl; + cout << " value_float = " << dev_it->value_float << endl; + cout << " value_float_set = " << dev_it->value_float_set << endl; + cout << " value_string = '" << dev_it->value_string << "'" << endl; + cout << " value_string_set = " << dev_it->value_string_set << endl; + cout << " layer_name = '" << dev_it->layer_name << "'" << endl; + cout << " package = '" << dev_it->package << "'" << endl; + } + + /* + * Export supplies + */ + + cout << "supplies" << endl; + for (SupplyList::iterator supp_it = supply.begin(); supp_it != supply.end(); ++supp_it) { + cout << " supply" << endl; + cout << " name = '" << supp_it->name << "'" << endl; + cout << " value_float = " << supp_it->value_float << endl; + cout << " voltage_specified = " << supp_it->voltage_specified << endl; + cout << " conversion = " << supp_it->conversion << endl; + } + + /* + * Export stackup + */ + + cout << "layers" << endl; + for (LayerList::iterator it = stackup.begin(); it != stackup.end(); ++it) { + cout << " layer" << endl; + cout << " layer_name = '" << it->layer_name << "'" << endl; + cout << " layer_type = " << it->layer_type << " "; + switch (it->layer_type) { + case LAYER_SIGNAL: cout << "LAYER_SIGNAL"; break; + case LAYER_DIELECTRIC: cout << "LAYER_DIELECTRIC"; break; + case LAYER_PLANE: cout << "LAYER_PLANE"; break; + default: cout << "Error"; break; + } + cout << endl; + + cout << " bulk_resistivity = " << it->bulk_resistivity << endl; + cout << " conformal = " << it->conformal << endl; + cout << " epsilon_r = " << it->epsilon_r << endl; + cout << " layer_name = " << it->layer_name << endl; + cout << " loss_tangent = " << it->loss_tangent << endl; + cout << " material_name = " << it->material_name << endl; + cout << " plane_separation = " << it->plane_separation << endl; + cout << " plating_thickness = " << it->plating_thickness << endl; + cout << " prepreg = " << it->prepreg << endl; + cout << " temperature_coefficient = " << it->temperature_coefficient << endl; + cout << " thickness = " << it->thickness << endl; + cout << " z0 = " << it->z0 << endl; + cout << " z1 = " << it->z1 << endl; + } + + /* + * Padstack + */ + + cout << "padstacks" << endl; + for (PadstackList::iterator it = padstack.begin(); it != padstack.end(); ++it) { + cout << " padstack" << endl; + cout << " padstack_name = '" << it->padstack_name << "'" << endl; + cout << " drill_size = " << it->drill_size << endl; + cout << " pads" << endl; + for (PadList::iterator p = it->pads.begin(); p != it->pads.end(); ++p) { + cout << " pad" << endl; + cout << " layer_name = '" << p->layer_name << "'" << endl; + cout << " pad_type = " << p->pad_type << " "; + switch (p->pad_type) { + case PAD_TYPE_METAL: cout << "PAD_TYPE_METAL"; break; + case PAD_TYPE_ANTIPAD: cout << "PAD_TYPE_ANTIPAD"; break; + case PAD_TYPE_THERMAL_RELIEF: cout << "PAD_TYPE_THERMAL_RELIEF"; break; + default: cout << "Error"; break; + } + cout << endl; + cout << " pad_shape = " << p->pad_shape << " "; + switch (p->pad_shape) { + case PAD_SHAPE_OVAL: cout << "PAD_SHAPE_OVAL"; break; + case PAD_SHAPE_RECTANGULAR: cout << "PAD_SHAPE_RECTANGULAR"; break; + case PAD_SHAPE_OBLONG: cout << "PAD_SHAPE_OBLONG"; break; + default: cout << "Error"; break; + } + cout << endl; + cout << " pad_sx = " << p->pad_sx << endl; + cout << " pad_sy = " << p->pad_sy << endl; + cout << " pad_angle = " << p->pad_angle << endl; + cout << " thermal_clear_shape = " << p->thermal_clear_shape << " "; + switch (p->thermal_clear_shape) { + case PAD_SHAPE_OVAL: cout << "PAD_SHAPE_OVAL"; break; + case PAD_SHAPE_RECTANGULAR: cout << "PAD_SHAPE_RECTANGULAR"; break; + case PAD_SHAPE_OBLONG: cout << "PAD_SHAPE_OBLONG"; break; + default: cout << "Error"; break; + } + cout << endl; + cout << " thermal_clear_sx = " << p->thermal_clear_sx << endl; + cout << " thermal_clear_sy = " << p->thermal_clear_sy << endl; + cout << " thermal_clear_angle = " << p->thermal_clear_angle << endl; + } + } + + /* + * Net + */ + + cout << "net" << endl; + for (NetList::iterator i = net.begin(); i != net.end(); ++i) { + cout << " net" << endl; + cout << " net_name = '" << i->net_name << "'" << endl; + + /* + * Net Polygons + */ + for (PolygonMap::iterator j = i->metal.begin(); j != i->metal.end(); ++j) { + cout << " polygon id " << j->first << endl; + for (PolygonList::iterator k = j->second.begin(); k != j->second.end(); ++k) { + cout << " polygon" << endl; + cout << " positive " << k->positive << endl; + cout << " width " << k->width << endl; + cout << " layer_name " << k->layer_name << endl; + for (PointList::iterator l = k->vertex.begin(); l != k->vertex.end(); ++l) { + cout << " x = " << l->x << endl; + cout << " y = " << l->y << endl; + } + } + } + + /* + * Net Vias + */ + + cout << " vias" << endl; + for (ViaList::iterator j = i->via.begin(); j != i->via.end(); ++j) { + cout << " via" << endl; + cout << " x = " << j->x << endl; + cout << " y = " << j->y << endl; + cout << " layer0_name = " << j->layer0_name << endl; + cout << " layer1_name = " << j->layer1_name << endl; + cout << " radius = " << j->radius << endl; + } + + /* + * Net Pins + */ + + cout << " pins" << endl; + for (PinList::iterator j = i->pin.begin(); j != i->pin.end(); ++j) { + cout << " pin" << endl; + cout << " x = " << j->x << endl; + cout << " y = " << j->y << endl; + cout << " pin_reference = " << j->pin_reference << endl; + cout << " padstack_name = " << j->padstack_name << endl; + cout << " pin_function = " << j->pin_function << " "; + switch (j->pin_function) { + case PIN_SIM_IN: cout << "PIN_SIM_IN"; break; + case PIN_SIM_OUT: cout << "PIN_SIM_OUT"; break; + case PIN_SIM_BOTH: cout << "PIN_SIM_BOTH"; break; + default: cout << "Error"; break; + } + cout << endl; + } + + /* + * Net Unrouted segments + */ + + cout << " unrouted_segments" << endl; + for (UnroutedSegmentList::iterator j = i->unrouted_segment.begin(); j != i->unrouted_segment.end(); ++j) { + cout << " unrouted_segment" << endl; + cout << " x1 = " << j->x1 << endl; + cout << " y1 = " << j->y1 << endl; + cout << " layer1_name = " << j->layer1_name << endl; + cout << " x2 = " << j->x2 << endl; + cout << " y2 = " << j->y2 << endl; + cout << " layer2_name = " << j->layer2_name << endl; + cout << " zlayer_name_set = " << j->zlayer_name_set << endl; + cout << " zlayer_name = " << j->zlayer_name << endl; + cout << " width = " << j->width << endl; + cout << " impedance_set = " << j->impedance_set << endl; + cout << " impedance = " << j->impedance << endl; + cout << " delay = " << j->delay << endl; + cout << " resistance = " << j->resistance << endl; + } + + /* + * Net Attributes + */ + + if (!i->attribute.empty()) cout << " attribute" << endl; + for (AttributeList::iterator j = i->attribute.begin(); j != i->attribute.end(); ++j) { + cout << " name = '" << j->name << "'" << endl; + cout << " value = '" << j->value << "'" << endl; + } + } + + /* + * Net Class + */ + + cout << "net_class" << endl; + for (NetClassList::iterator i = net_class.begin(); i != net_class.end(); ++i) { + cout << " net_class" << endl; + cout << " net_class_name = '" << i->net_class_name << "'" << endl; + cout << " net_class_element" << endl; + for (StringList::iterator j = i->net_class_element.begin(); j != i->net_class_element.end(); ++j) { + cout << " net_name = '" << *j << "'" << endl; + } + if (!i->attribute.empty()) cout << " attribute" << endl; + for (AttributeList::iterator j = i->attribute.begin(); j != i->attribute.end(); ++j) { + cout << " name = '" << j->name << "'" << endl; + cout << " value = '" << j->value << "'" << endl; + } + } + + cout << "end" << endl; + + fclose(stdout); + + return true; +} + +/* not truncated */ diff --git a/hyp2mat/lib/hypfile.h b/hyp2mat/lib/hypfile.h new file mode 100644 index 0000000..2670672 --- /dev/null +++ b/hyp2mat/lib/hypfile.h @@ -0,0 +1,333 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Loads a hyperlynx file from disk. + */ + +#ifndef HYPFILE_H +#define HYPFILE_H + +#include <string> +#include <vector> +#include <map> +#include "parse_param.h" + +namespace HypFile { + + class Point { + public: + double x; + double y; + Point(double x_val = 0, double y_val = 0): x(x_val), y(y_val) {}; + bool operator==(const Point &right) const { return ((x == right.x) && ( y == right.y)); }; + }; + + typedef std::vector<Point> PointList; + + class Polygon { + public: + Polygon(); + PointList vertex; + polygon_type_enum polygon_type; + std::string net_name; + int id; + bool positive; /* false if hole */ + double width; /* edge width of polygon */ + /* if polygon_type == POLYGON_TYPE_PLANE keep plane_separation distance from other nets */ + double plane_separation; /* -1.0 if not set */ + double left_plane_separation; /* -1.0 if not set */ + std::string layer_name; + }; + + typedef std::vector<Polygon> PolygonList; + + struct Via { + double x; + double y; + std::string layer0_name; /* bottom */ + std::string layer1_name; /* top */ + double z0; + double z1; + double radius; + }; + + typedef std::vector<Via> ViaList; + + struct Pin { + double x; + double y; + std::string pin_reference; + std::string padstack_name; + pin_function_enum pin_function; + }; + + typedef std::vector<Pin> PinList; + + struct UnroutedSegment { + double x1; + double y1; + std::string layer1_name; + double x2; + double y2; + std::string layer2_name; + bool zlayer_name_set; + std::string zlayer_name; + double width; + bool impedance_set; + double impedance; + double delay; + double resistance; + }; + + typedef std::vector<UnroutedSegment> UnroutedSegmentList; + + enum layer_enum { LAYER_SIGNAL, LAYER_DIELECTRIC, LAYER_PLANE }; + + struct Layer { + layer_enum layer_type; + double bulk_resistivity; + bool conformal; + double epsilon_r; + std::string layer_name; + double loss_tangent; + std::string material_name; + double plane_separation; /* trace to plane separation */ + double plating_thickness; + bool prepreg; + double temperature_coefficient; /* temperature coefficient of resistivity */ + double thickness; /* layer thickness */ + double z0; /* vertical position of bottom of layer */ + double z1; /* vertical position of top of layer */ + }; + + typedef std::vector<Layer> LayerList; + + struct Device { + std::string device_type; + std::string ref; + std::string name; + double value_float; + bool value_float_set; + std::string value_string; + bool value_string_set; + std::string layer_name; + std::string package; + }; + + typedef std::vector<Device> DeviceList; + + struct Supply { + std::string name; + double value_float; + bool voltage_specified; + bool conversion; + }; + + typedef std::vector<Supply> SupplyList; + + enum pad_shape_enum { PAD_SHAPE_OVAL, PAD_SHAPE_RECTANGULAR, PAD_SHAPE_OBLONG }; + + struct Pad { + std::string layer_name; + pad_type_enum pad_type; + pad_shape_enum pad_shape; + double pad_sx; + double pad_sy; + double pad_angle; + pad_shape_enum thermal_clear_shape; + double thermal_clear_sx; + double thermal_clear_sy; + double thermal_clear_angle; + }; + + typedef std::vector<Pad> PadList; + + struct Padstack { + std::string padstack_name; + double drill_size; + PadList pads; + }; + + typedef std::vector<Padstack> PadstackList; + + struct Attribute { + std::string name; + std::string value; + }; + + typedef std::vector<Attribute> AttributeList; + + typedef std::vector<std::string> StringList; + + struct NetClass { + std::string net_class_name; + StringList net_class_element; + AttributeList attribute; + }; + + /* PolygonByID orders polygons according to polygon id. PolygonList is a list of polygons with the same polygon id */ + + typedef std::map<int, PolygonList> PolygonMap; + + struct Net { + std::string net_name; + PolygonMap metal; + double plane_separation; + ViaList via; + UnroutedSegmentList unrouted_segment; + PinList pin; + AttributeList attribute; + }; + + typedef std::vector<Net> NetList; + + struct Board { + PolygonList edge; + PolygonList segments; + AttributeList attribute; + double plane_separation; + }; + + typedef std::vector<NetClass> NetClassList; + + class Hyp + { + public: + Hyp(); + bool trace_scanner; // flex scanner debugging + bool trace_parser; // bison parser debugging + bool trace_hyp; // hyperlynx debugging + bool load (const std::string& filename); // load Hyperlynx file + bool save (const std::string& filename); // write to file + + void error(const std::string& m); + + Board board; + LayerList stackup; + DeviceList device; + SupplyList supply; + PadstackList padstack; + NetClassList net_class; + NetList net; + + /* Physical constants */ + double pi; + double inches; /* inches to m */ + double copper_imperial_weight; /* metal thickness in ounces/ft2 */ + double copper_metric_weight; /* metal thickness in grams/cm2 */ + double copper_bulk_resistivity; + double copper_temperature_coefficient; /* temperature coefficient of bulk resistivity */ + double fr4_epsilon_r; /* dielectric constant of substrate */ + double fr4_loss_tangent; /* loss tangent of substrate */ + double conformal_epsilon_r; /* dielectric constant of conformal coating */ + + int min_circle_segments; /* minimum number of polygon segments to draw a full circle; needs to be a multiple of 4 */ + double arc_precision; /* maximum difference between perfect circle arc and polygonal approximation */ + StringList flood_layers; /* layers to be flooded with copper */ + + /* Hyperlynx UNIT and OPTIONS */ + double unit; /* conversion factor: pcb length units to meters */ + double metal_thickness_unit; /* conversion factor: metal thickness to meters */ + bool use_die_for_metal; /* use dielectric constant and loss tangent of dielectric for metal layers */ + + Polygon current_polygon; /* polygon currently being parsed */ + + /* exec_* routines are called by parser to interpret hyperlynx file */ + bool exec_board_file(parse_param& h); + bool exec_version(parse_param& h); + bool exec_data_mode(parse_param& h); + bool exec_units(parse_param& h); + bool exec_plane_sep(parse_param& h); + bool exec_perimeter_segment(parse_param& h); + bool exec_perimeter_arc(parse_param& h); + bool exec_board_attribute(parse_param& h); + + bool exec_options(parse_param& h); + bool exec_signal(parse_param& h); + bool exec_dielectric(parse_param& h); + bool exec_plane(parse_param& h); + bool trace_layer(parse_param& h); + bool add_metal_layer(parse_param& h); + bool add_dielectric_layer(parse_param& h); + bool calc_layer_position(); + bool calc_layer_epsilon(); + + bool exec_devices(parse_param& h); + + bool exec_supplies(parse_param& h); + + bool exec_padstack_element(parse_param& h); + bool exec_padstack_end(parse_param& h); + + bool exec_net(parse_param& h); + bool exec_net_plane_separation(parse_param& h); + bool exec_net_attribute(parse_param& h); + bool exec_seg(parse_param& h); + bool exec_arc(parse_param& h); + bool exec_via(parse_param& h); + bool exec_via_v1(parse_param& h); /* Old style via format */ + bool exec_pin(parse_param& h); + bool exec_pad(parse_param& h); + bool exec_useg(parse_param& h); + + bool exec_polygon_begin(parse_param& h); + bool exec_polygon_end(parse_param& h); + bool exec_polyvoid_begin(parse_param& h); + bool exec_polyvoid_end(parse_param& h); + bool exec_polyline_begin(parse_param& h); + bool exec_polyline_end(parse_param& h); + bool exec_line(parse_param& h); + bool exec_curve(parse_param& h); + + bool exec_net_class(parse_param& h); + bool exec_net_class_element(parse_param& h); + bool exec_net_class_attribute(parse_param& h); + + bool exec_end(parse_param& h); + bool exec_key(parse_param& h); + + /* convert a circular arc to a polygon */ + Polygon arc2poly(double x1, double y1, double x2, double y2, double xc, double yc, double r, bool clockwise); + /* convert a line segment to a polygon */ + Polygon segment2poly(double x1, double y1, double x2, double y2, double width); + /* convert a pad to a polygon */ + Polygon pad2poly(double pad_x, double pad_y, Pad pad); + /* Add a polygon to the board outline */ + void add_perimeter_polygon(Polygon poly); + /* Add a polygon to the current net */ + void add_polygon(Polygon poly); + /* Add a polygon to a current net */ + void add_polygon_to_net(Net& pnet, Polygon poly); + /* Add a pad at coordinates x, y */ + void add_pad(double x, double y, Pad pad); + /* Add a via at coordinates x, y */ + void add_via(double x, double y, std::string layer0_name, std::string layer1_name, double radius); + /* Flood specified layers with copper */ + void flood_layers_with_copper(); + + }; + +} + + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/misc.cc b/hyp2mat/lib/misc.cc new file mode 100644 index 0000000..d8488ae --- /dev/null +++ b/hyp2mat/lib/misc.cc @@ -0,0 +1,65 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <limits> + +#include "config.h" +#include "hyp2mat.h" + +using namespace Hyp2Mat; + +std::string Hyp2Mat::version() +{ + return VERSION; +} + +Device::Device() +{ + name.clear(); + ref.clear(); + value_type = DEVICE_VALUE_NONE; + value_float = -1; + value_string.clear(); +} + +Pin::Pin() +{ + ref.clear(); + x = 0; + y = 0; + z0 = 0; + z1 = 0; + layer_name.clear(); + metal.clear(); +} + +Bounds::Bounds() +{ + /* bounds default to infinity */ + double dbl_max = std::numeric_limits<double>::max(); + x_min = - dbl_max; + x_max = dbl_max; + y_min = - dbl_max; + y_max = dbl_max; + z_min = - dbl_max; + z_max = dbl_max; +} + +/* not truncated */ diff --git a/hyp2mat/lib/palette.cc b/hyp2mat/lib/palette.cc new file mode 100644 index 0000000..b0405a8 --- /dev/null +++ b/hyp2mat/lib/palette.cc @@ -0,0 +1,120 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Generate a palette of colors, given hue, saturation and value (brightness). + * The number of colors needed may be specified, but does not need to be known in advance. + * After Martin Ankerl * "How to Generate Random Colors Programmatically" + */ + +#include <cmath> +#include "palette.h" + +Palette::Palette() +{ + /* default values */ + palette_hue = 0.69; /* blue */ + palette_saturation = 0.99; + palette_value = 0.99; + step = (sqrt(5) - 1)/2; /* golden ratio conjugate */ +} + +void Palette::SetPalette(double hue, double saturation, double value) +{ + /* set user values */ + if ((0 <= hue) && (hue <= 1)) palette_hue = hue; + if ((0 <= saturation) && (saturation <= 1)) palette_saturation = saturation; + if ((0 <= value) && (value <= 1)) palette_value = value; + return; +} + +void Palette::SetNumberOfColors(unsigned int number_of_colors) +{ + if (number_of_colors > 0) step = 1 / (double) number_of_colors; + else step = (sqrt(5) - 1)/2; /* golden ratio conjugate */; + return; +} + +PaletteColor Palette::Color(int color) +{ + + /* hue, saturation and value (brightness) are double, in range 0..1. (HSV color model) */ + + /* calculate hue of color */ + double integer_part; + double h = modf(palette_hue + color * step, &integer_part); + + /* convert hsv color model to rgb */ + double hi; + double f = modf(h * 6, &hi); + double v = palette_value; + double p = v * (1 - palette_saturation); + double q = v * (1 - f * palette_saturation); + double t = v * (1 - (1 - f) * palette_saturation); + + double red, green, blue; + switch(static_cast<int>(hi + 0.5)) { + case 0: + red = v; + green = t; + blue = p; + break; + case 1: + red = q; + green = v; + blue = p; + break; + case 2: + red = p; + green = v; + blue = t; + break; + case 3: + red = p; + green = q; + blue = v; + break; + case 4: + red = t; + green = p; + blue = v; + break; + case 5: + red = v; + green = p; + blue = q; + break; + default: + red = 0; + green = 0; + blue = 0; + break; + } + + PaletteColor clr; + clr.red = red; + clr.green = green; + clr.blue = blue; + + return clr; + +} + +/* not truncated */ diff --git a/hyp2mat/lib/palette.h b/hyp2mat/lib/palette.h new file mode 100644 index 0000000..146e358 --- /dev/null +++ b/hyp2mat/lib/palette.h @@ -0,0 +1,55 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Generate a palette of colors, given hue, saturation and value (brightness). + * The number of colors needed may be specified, but does not need to be known in advance. + * After Martin Ankerl "How to Generate Random Colors Programmatically" + */ + +#ifndef PALETTE_H +#define PALETTE_H + +struct PaletteColor { + double red; + double green; + double blue; + }; + +class Palette { +public: + Palette(); + /* hue, saturation and value (brightness) are double, in range 0..1. (HSV color model) */ + void SetPalette(double hue, double saturation, double value); /* HSV model */ + /* Set the number of colors in the palette. If the number of colors is not known in advance, choose 0 */ + void SetNumberOfColors(unsigned int number_of_colors); + /* Return palette color */ + PaletteColor Color(int color); + +private: + double palette_hue; + double palette_saturation; + double palette_value; + double step; + }; + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/parse.yy b/hyp2mat/lib/parse.yy new file mode 100644 index 0000000..66aeb99 --- /dev/null +++ b/hyp2mat/lib/parse.yy @@ -0,0 +1,826 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +%code requires { +#include "hypfile.h" +} + +%parse-param {class HypFile::Hyp *hyp } +%error-verbose +%debug +%defines + +%union { + int boolval; + int intval; + double floatval; + char* strval; +} + +%{ +#include <cstdlib> +#include <cstdarg> +#include <cstdio> +#include <cstring> +#include <string> +#include <sstream> +#include "parser.h" + +void yyerror(HypFile::Hyp *, const char *); + +/* YYPRINT and yyprint print values of the tokens when debugging is switched on */ +void yyprint(FILE *, int, YYSTYPE); +#define YYPRINT(file, type, value) yyprint (file, type, value) + +/* clear parse_param struct at beginning of new record */ +void new_record(); + +/* struct to pass to calling class */ +parse_param h; + +%} + +/* + * Hyperlynx keywords + */ + + /* Punctuation: {Â } ( ) = , */ + + /* Sections */ + +%token BOARD_FILE VERSION DATA_MODE UNITS PLANE_SEP +%token BOARD STACKUP DEVICES SUPPLIES +%token PAD PADSTACK NET NET_CLASS END KEY + + /* Keywords */ + +%token A ARC COPPER CURVE DETAILED DIELECTRIC ENGLISH LENGTH +%token LINE METRIC N OPTIONS PERIMETER_ARC PERIMETER_SEGMENT PIN +%token PLANE POLYGON POLYLINE POLYVOID POUR S SEG SIGNAL +%token SIMPLIFIED SIM_BOTH SIM_IN SIM_OUT USEG VIA WEIGHT + + /* Assignments */ + +%token A1 A2 BR C C_QM CO_QM D ER F ID +%token L L1 L2 LPS LT M NAME +%token P PKG PR_QM PS R REF SX SY S1 S1X S1Y S2 S2X S2Y T TC +%token USE_DIE_FOR_METAL V V_QM VAL W X X1 X2 +%token XC Y Y1 Y2 YC Z ZL ZLEN ZW + + /* Booleans */ + +%token YES NO + +%token <boolval> BOOL +%token <intval> POSINT +%token <floatval> FLOAT +%token <strval> STRING + +%start hyp_file + +%% + +/* + * Note: + * Use left recursion when parsing board perimeter and nets. + * When using left recursion cpu time is linear with board size. + * When using right recursion we run out of memory on large boards. + * (Typical error message: line xxx: memory exhausted at 'yyy' ) + */ + + /* + hyperlynx file sections: + + board_file + version + data_mode* + units + plane_sep* + board* + stackup* + devices + supplies* + padstack* + net + net_class* + end + + * = optional section + + */ + +hyp_file + : hyp_file hyp_section + | hyp_section ; + +hyp_section + : board_file + | version + | data_mode + | units + | plane_sep + | board + | stackup + | devices + | supplies + | padstack + | net + | netclass + | end + | key + | '{' error '}' ; + + /* board_file */ + +board_file + : '{' BOARD_FILE { if (hyp->exec_board_file(h)) YYERROR; } '}' ; + + /* version */ + +version + : '{' VERSION '=' FLOAT { h.vers = yylval.floatval; } '}' { if (hyp->exec_version(h)) YYERROR; } ; + + /* data_mode */ + +data_mode + : '{' DATA_MODE '=' mode '}' { if (hyp->exec_data_mode(h)) YYERROR; }; + +mode + : SIMPLIFIED { h.detailed = false; } + | DETAILED { h.detailed = true; } ; + + /* units */ + +units + : '{' UNITS '=' unit_system metal_thickness_unit '}' { if (hyp->exec_units(h)) YYERROR; } ; + +unit_system + : ENGLISH { h.unit_system_english = true; } + | METRIC { h.unit_system_english = false; }; + +metal_thickness_unit + : WEIGHT { h.metal_thickness_weight = true; } + | LENGTH { h.metal_thickness_weight = false; } ; + + /* plane_sep */ +plane_sep + : '{' PLANE_SEP '=' FLOAT { h.default_plane_separation = yylval.floatval; } '}' { if (hyp->exec_plane_sep(h)) YYERROR; } ; + + /* board */ + +board + : '{' BOARD board_paramlist '}' + | '{' BOARD '}' ; + +board_paramlist + : board_paramlist board_param + | board_param ; + +board_param + : perimeter_segment + | perimeter_arc + | board_attribute + | '(' error ')' ; + +perimeter_segment + : '(' PERIMETER_SEGMENT coord_line ')' { if (hyp->exec_perimeter_segment(h)) YYERROR; } ; + +perimeter_arc + : '(' PERIMETER_ARC coord_arc ')' { if (hyp->exec_perimeter_arc(h)) YYERROR; } ; + +board_attribute + : '(' A N '=' STRING { h.name = yylval.strval; } V '=' STRING { h.value = yylval.strval; } ')' { if (hyp->exec_board_attribute(h)) YYERROR; } ; + + /* stackup */ + +stackup + : '{' STACKUP stackup_paramlist '}' ; + +stackup_paramlist + : stackup_paramlist stackup_param + | stackup_param ; + +stackup_param + : options + | signal + | dielectric + | plane + | '(' error ')' ; + +options + : '(' OPTIONS options_params { if (hyp->exec_options(h)) YYERROR; } ; + +options_params + : USE_DIE_FOR_METAL '=' BOOL { h.use_die_for_metal = yylval.boolval; } ')' + | ')' + ; + +signal + : '(' SIGNAL { new_record(); } signal_paramlist ')' { if (hyp->exec_signal(h)) YYERROR; } ; + +signal_paramlist + : signal_paramlist signal_param + | signal_param ; + +signal_param + : thickness + | plating_thickness + | C '=' FLOAT { h.bulk_resistivity = yylval.floatval; h.bulk_resistivity_set = true; } + | bulk_resistivity + | temperature_coefficient + | epsilon_r + | loss_tangent + | layer_name + | material_name + | plane_separation ; + +dielectric + : '(' DIELECTRIC { new_record(); } dielectric_paramlist ')' { if (hyp->exec_dielectric(h)) YYERROR; } ; + +dielectric_paramlist + : dielectric_paramlist dielectric_param + | dielectric_param ; + +dielectric_param + : thickness + | C '=' FLOAT { h.epsilon_r = yylval.floatval; h.epsilon_r_set = true; } + | epsilon_r + | loss_tangent + | conformal + | prepreg + | layer_name + | material_name + ; + +plane + : '(' PLANE { new_record(); } plane_paramlist ')' { if (hyp->exec_plane(h)) YYERROR; } ; + +plane_paramlist + : plane_paramlist plane_param + | plane_param ; + +plane_param + : thickness + | C '=' FLOAT { h.bulk_resistivity = yylval.floatval; h.bulk_resistivity_set = true; } + | bulk_resistivity + | temperature_coefficient + | epsilon_r + | loss_tangent + | layer_name + | material_name + | plane_separation ; + +thickness + : T '=' FLOAT { h.thickness = yylval.floatval; h.thickness_set = true; } + +plating_thickness + : P '=' FLOAT { h.plating_thickness = yylval.floatval; h.plating_thickness_set = true; } + +bulk_resistivity + : BR '=' FLOAT { h.bulk_resistivity = yylval.floatval; h.bulk_resistivity_set = true; } + +temperature_coefficient + : TC '=' FLOAT { h.temperature_coefficient = yylval.floatval; h.temperature_coefficient_set = true; } + +epsilon_r + : ER '=' FLOAT { h.epsilon_r = yylval.floatval; h.epsilon_r_set = true; } + +loss_tangent + : LT '=' FLOAT { h.loss_tangent = yylval.floatval; h.loss_tangent_set = true; } + +layer_name + : L '=' STRING { h.layer_name = yylval.strval; h.layer_name_set = true; } + +material_name + : M '=' STRING { h.material_name = yylval.strval; h.material_name_set = true; } + +plane_separation + : PS '=' FLOAT { h.plane_separation = yylval.floatval; h.plane_separation_set = true; } + +conformal + : CO_QM '=' BOOL { h.conformal = yylval.boolval; h.conformal_set = true; } + +prepreg + : PR_QM '=' BOOL { h.prepreg = yylval.boolval; h.prepreg_set = true; } + + /* devices */ + +devices + : '{' DEVICES device_list '}' + | '{' DEVICES '}' ; + +device_list + : device_list device + | device ; + +device + : '(' { new_record(); } STRING { h.device_type = yylval.strval; } REF '=' STRING { h.ref = yylval.strval; } device_paramlist ')' { if (hyp->exec_devices(h)) YYERROR; } + | '(' error ')' ; + +device_paramlist + : name device_value + | device_value + ; + +device_value + : value device_layer + | device_layer + ; + +device_layer + : layer_name package + | layer_name + ; + +name + : NAME '=' STRING { h.name = yylval.strval; h.name_set = true; } ; + +value + : value_float + | value_string + ; + +value_float + : VAL '=' FLOAT { h.value_float = yylval.floatval; h.value_float_set = true; } ; + +value_string + : VAL '=' STRING { h.value_string = yylval.strval; h.value_string_set = true; } ; + +package + : PKG '=' STRING { h.package = yylval.strval; h.package_set = true; } ; + + /* supplies */ + +supplies + : '{' SUPPLIES supply_list '}' ; + +supply_list + : supply_list supply + | supply ; + +supply + : '(' S name value_float voltage_spec conversion ')' { if (hyp->exec_supplies(h)) YYERROR; } + | '(' error ')' ; + +voltage_spec + : V_QM '=' BOOL { h.voltage_specified = yylval.boolval; } ; + +conversion + : C_QM '=' BOOL { h.conversion = yylval.boolval; } + + /* padstack */ + +padstack + : '{' PADSTACK { new_record(); } '=' STRING { h.padstack_name = yylval.strval; h.padstack_name_set = true; } drill_size '}' { if (hyp->exec_padstack_end(h)) YYERROR; } ; + +drill_size + : ',' FLOAT { h.drill_size = yylval.floatval; h.drill_size_set = true; } padstack_list + | ',' padstack_list ; + | padstack_list ; + +padstack_list + : padstack_list padstack_def + | padstack_def ; + +padstack_def + : '(' STRING { h.layer_name = yylval.strval; h.layer_name_set = true; } ',' pad_shape pad_coord pad_type { if (hyp->exec_padstack_element(h)) YYERROR; new_record(); } + | '(' error ')' ; + +pad_shape + : FLOAT { h.pad_shape = yylval.floatval; } ',' + | ',' { h.pad_shape = -1; } /* Workaround: Altium sometimes prints an empty pad shape */ + ; + +pad_coord + : FLOAT { h.pad_sx = yylval.floatval; } ',' FLOAT { h.pad_sy = yylval.floatval; } ',' FLOAT { h.pad_angle = yylval.floatval; } + +pad_type + : ')' + | ',' M ')' { h.pad_type = PAD_TYPE_METAL; h.pad_type_set = true; } + | ',' A ')' { h.pad_type = PAD_TYPE_ANTIPAD; h.pad_type_set = true; } + | ',' FLOAT { h.thermal_clear_shape = yylval.floatval; } + ',' FLOAT { h.thermal_clear_sx = yylval.floatval; } + ',' FLOAT { h.thermal_clear_sy = yylval.floatval; } + ',' FLOAT { h.thermal_clear_angle = yylval.floatval; } + ',' T ')' { h.pad_type = PAD_TYPE_THERMAL_RELIEF; h.pad_type_set = true; } + ; + + /* net */ + +net + : '{' NET '=' STRING { h.net_name = yylval.strval; if (hyp->exec_net(h)) YYERROR; } net_def '}' ; + +net_def + : plane_separation { if (hyp->exec_net_plane_separation(h)) YYERROR; } net_subrecord_list + | net_subrecord_list ; + +net_subrecord_list + : net_subrecord_list net_subrecord + | net_subrecord ; + +net_subrecord + : seg + | arc + | via + | pin + | pad + | useg + | polygon + | polyvoid + | polyline + | net_attribute + | '(' error ')' + | '{' error '}' + ; + +seg + : '(' SEG { new_record(); } coord_line width layer_name ps_lps_param { if (hyp->exec_seg(h)) YYERROR; } ; + +arc + : '(' ARC { new_record(); } coord_arc width layer_name ps_lps_param { if (hyp->exec_arc(h)) YYERROR; } ; + +ps_lps_param + : plane_separation lps_param + | lps_param + ; + +lps_param + : left_plane_separation ')' + | ')' + ; + +width + : W '=' FLOAT { h.width = yylval.floatval; h.width_set = true; } ; + +left_plane_separation + : LPS '=' FLOAT { h.left_plane_separation = yylval.floatval; h.left_plane_separation_set = true; } ; + +via + : '(' VIA { new_record(); } coord_point via_new_or_old_style + ; + +via_new_or_old_style + : via_new_style + | via_old_style + ; + +via_new_style + : via_new_style_l1_param { if (hyp->exec_via(h)) YYERROR; } ; + +via_new_style_l1_param + : layer1_name via_new_style_l2_param + | via_new_style_l2_param + ; + +via_new_style_l2_param + : layer2_name via_new_style_padstack_param + | via_new_style_padstack_param + ; + +via_new_style_padstack_param + : padstack_name ')' + ; + +padstack_name + : P '=' STRING { h.padstack_name = yylval.strval; h.padstack_name_set = true; } ; + +layer1_name + : L1 '=' STRING { h.layer1_name = yylval.strval; h.layer1_name_set = true; } ; + +layer2_name + : L2 '=' STRING { h.layer2_name = yylval.strval; h.layer2_name_set = true; } ; + +via_old_style + : D '=' FLOAT { h.drill_size = yylval.floatval; } /* deprecated hyperlynx v1.x VIA format */ + layer1_name + layer2_name + S1 '=' STRING { h.pad1_shape = yylval.strval; } + S1X '=' FLOAT { h.pad1_sx = yylval.floatval; } + S1Y '=' FLOAT { h.pad1_sy = yylval.floatval; } + A1 '=' FLOAT { h.pad1_angle = yylval.floatval; } + S2 '=' STRING { h.pad2_shape = yylval.strval; } + S2X '=' FLOAT { h.pad2_sx = yylval.floatval; } + S2Y '=' FLOAT { h.pad2_sy = yylval.floatval; } + A2 '=' FLOAT { h.pad2_angle = yylval.floatval; } + ')' { if (hyp->exec_via_v1(h)) YYERROR; } ; + ; + +pin + : '(' PIN { new_record(); } coord_point pin_reference pin_param { if (hyp->exec_pin(h)) YYERROR; } ; + +pin_param + : padstack_name pin_function_param + | pin_function_param + ; + +pin_function_param + : pin_function ')' + | ')' + ; + +pin_reference + : R '=' STRING { h.pin_reference = yylval.strval; h.pin_reference_set = true; } ; + +pin_function + : F '=' SIM_OUT { h.pin_function = PIN_SIM_OUT; h.pin_function_set = true; } + | F '=' SIM_IN { h.pin_function = PIN_SIM_IN; h.pin_function_set = true; } + | F '=' SIM_BOTH { h.pin_function = PIN_SIM_BOTH; h.pin_function_set = true; } + ; + +pad + : '(' PAD { new_record(); } /* deprecated hyperlynx v1.x only */ + coord_point + layer_name + S '=' STRING { h.pad1_shape = yylval.strval; } + SX '=' FLOAT { h.pad1_sx = yylval.floatval; } + SY '=' FLOAT { h.pad1_sy = yylval.floatval; } + A '=' FLOAT { h.pad1_angle = yylval.floatval; } + ')' { if (hyp->exec_pad(h)) YYERROR; } ; + ; + +useg + : '(' USEG { new_record(); } coord_point1 layer1_name coord_point2 layer2_name useg_param { if (hyp->exec_useg(h)) YYERROR; } ; + +useg_param + : useg_stackup + | useg_impedance + ; + +useg_stackup + : ZL '=' STRING { h.zlayer_name = yylval.strval; h.zlayer_name_set = true; } + ZW '=' FLOAT { h.width = yylval.floatval; } + ZLEN '=' FLOAT { h.length = yylval.floatval; } + ')' + ; + +useg_impedance + : Z '=' FLOAT { h.impedance = yylval.floatval; h.impedance_set = true; } + D '=' FLOAT { h.delay = yylval.floatval; } + useg_resistance; + +useg_resistance + : R '=' FLOAT { h.resistance = yylval.floatval; h.resistance_set = true;} + ')' + | ')' + ; + +polygon + : '{' POLYGON { new_record(); } polygon_param_list coord_point { if (hyp->exec_polygon_begin(h)) YYERROR; } + lines_and_curves '}' { if (hyp->exec_polygon_end(h)) YYERROR; } ; + +polygon_param_list + : polygon_param_list polygon_param + | polygon_param + ; + +polygon_param + : layer_name + | width + | polygon_type + | polygon_id + ; + +polygon_id + : ID '=' POSINT { h.id = yylval.intval; h.id_set = true; } /* polygon id is a non-negative integer */ + ; + +polygon_type + : T '=' POUR { h.polygon_type = POLYGON_TYPE_POUR; h.polygon_type_set = true; } + | T '=' PLANE { h.polygon_type = POLYGON_TYPE_PLANE; h.polygon_type_set = true; } + | T '=' COPPER { h.polygon_type = POLYGON_TYPE_COPPER; h.polygon_type_set = true; } + ; + +polyvoid + : '{' POLYVOID { new_record(); } polygon_id coord_point { if (hyp->exec_polyvoid_begin(h)) YYERROR; } + lines_and_curves '}' { if (hyp->exec_polyvoid_end(h)) YYERROR; } ; + +polyline + : '{' POLYLINE { new_record(); } polygon_param_list coord_point { if (hyp->exec_polyline_begin(h)) YYERROR; } + lines_and_curves '}' { if (hyp->exec_polyline_end(h)) YYERROR; } ; + +lines_and_curves + : lines_and_curves line_or_curve + | line_or_curve + ; + +line_or_curve + : line + | curve + | '(' error ')' + ; + +line + : '(' LINE { new_record(); } coord_point ')' { if (hyp->exec_line(h)) YYERROR; } ; + +curve + : '(' CURVE { new_record(); } coord_arc ')' { if (hyp->exec_curve(h)) YYERROR; } ; + +net_attribute + : '(' A N '=' STRING { h.name = yylval.strval; } V '=' STRING { h.value = yylval.strval; } ')' { if (hyp->exec_net_attribute(h)) YYERROR; } ; + + /* net class */ + +netclass + : '{' NET_CLASS '=' STRING { h.net_class_name = yylval.strval; if (hyp->exec_net_class(h)) YYERROR; } netclass_subrecords ; + +netclass_subrecords + : netclass_paramlist '}' + | '}' + ; + +netclass_paramlist + : netclass_paramlist netclass_param + | netclass_param + ; + +netclass_param + : netclass_attribute + | net_name + | '(' error ')' + ; + +net_name + : '(' N N '=' STRING { h.net_name = yylval.strval; } ')' { if (hyp->exec_net_class_element(h)) YYERROR; } ; + +netclass_attribute + : '(' A N '=' STRING { h.name = yylval.strval; } V '=' STRING { h.value = yylval.strval; } ')' { if (hyp->exec_net_class_attribute(h)) YYERROR; } ; + + /* end */ + +end + : '{' END '}' { if (hyp->exec_end(h)) YYERROR; } ; + + /* key */ + +key + : '{' KEY '=' STRING { h.key = yylval.strval; } '}' { if (hyp->exec_key(h)) YYERROR; } ; + + /* coordinates */ + +coord_point + : X '=' FLOAT { h.x = yylval.floatval; } Y '=' FLOAT { h.y = yylval.floatval; } ; + +coord_point1 + : X1 '=' FLOAT { h.x1 = yylval.floatval; } Y1 '=' FLOAT { h.y1 = yylval.floatval; } ; + +coord_point2 + : X2 '=' FLOAT { h.x2 = yylval.floatval; } Y2 '=' FLOAT { h.y2 = yylval.floatval; } ; + +coord_line + : coord_point1 coord_point2 ; + +coord_arc + : coord_line XC '=' FLOAT { h.xc = yylval.floatval; } YC '=' FLOAT { h.yc = yylval.floatval; } R '=' FLOAT { h.r = yylval.floatval; } ; + +%% + +/* + * Supporting C routines + */ + +void yyerror(HypFile::Hyp *hyp, const char *msg) +{ + std::ostringstream err_msg; + + err_msg << "line " << yylineno << ": " << msg << " at '" << yytext << "'"; + hyp->error(err_msg.str()); +} + +void yyprint(FILE *file, int type, YYSTYPE value) +{ + if (type == STRING) + fprintf (file, "%s", value.strval); + else if (type == FLOAT) + fprintf (file, "%g", value.floatval); + else if (type == BOOL) + fprintf (file, "%i", value.boolval); + return; +} + +/* + * reset parse_param struct at beginning of record + */ + +void new_record() +{ + h.vers = 0; + h.detailed = false; + h.unit_system_english = false; + h.metal_thickness_weight = false; + h.default_plane_separation = 0; + h.use_die_for_metal = false; + h.bulk_resistivity = 0; + h.conformal = false; + h.epsilon_r = 0; + h.layer_name.clear(); + h.loss_tangent = 0; + h.material_name.clear(); + h.plane_separation = 0; + h.plating_thickness = 0; + h.prepreg = false; + h.temperature_coefficient = 0; + h.thickness = 0; + h.bulk_resistivity_set = false; + h.conformal_set = false; + h.epsilon_r_set = false; + h.layer_name_set = false; + h.loss_tangent_set = false; + h.material_name_set = false; + h.plane_separation_set = false; + h.plating_thickness_set = false; + h.prepreg_set = false; + h.temperature_coefficient_set = false; + h.thickness_set = false; + h.device_type.clear(); + h.ref.clear(); + h.value_float = 0; + h.value_string.clear(); + h.package.clear(); + h.name_set = false; + h.value_float_set = false; + h.value_string_set = false; + h.package_set = false; + h.voltage_specified = false; + h.conversion = false; + h.padstack_name.clear(); + h.drill_size = 0; + h.pad_shape = 0; + h.pad_sx = 0; + h.pad_sy = 0; + h.pad_angle = 0; + h.thermal_clear_shape = 0; + h.thermal_clear_sx = 0; + h.thermal_clear_sy = 0; + h.thermal_clear_angle = 0; + h.pad_type = PAD_TYPE_METAL; + h.padstack_name_set = false; + h.drill_size_set = false; + h.pad_type_set = false; + h.width = 0; + h.left_plane_separation = 0; + h.width_set = false; + h.left_plane_separation_set = false; + h.layer1_name.clear(); + h.layer1_name_set = false; + h.layer2_name.clear(); + h.layer2_name_set = false; + h.pad1_shape.clear(); + h.pad1_sx = 0; + h.pad1_sy = 0; + h.pad1_angle = 0; + h.pad2_shape.clear(); + h.pad2_sx = 0; + h.pad2_sy = 0; + h.pad2_angle = 0; + h.pin_reference.clear(); + h.pin_reference_set = false; + h.pin_function = PIN_SIM_BOTH; + h.pin_function_set = false; + h.zlayer_name.clear(); + h.zlayer_name_set = false; + h.length = 0; + h.impedance = 0; + h.impedance_set = false; + h.delay = 0; + h.resistance = 0; + h.resistance_set = false; + h.id = -1; + h.id_set = false; + h.polygon_type = POLYGON_TYPE_PLANE; + h.polygon_type_set = false; + h.net_class_name.clear(); + h.net_name.clear(); + h.key.clear(); + h.name.clear(); + h.value.clear(); + h.x = 0; + h.y = 0; + h.x1 = 0; + h.y1 = 0; + h.x2 = 0; + h.y2 = 0; + h.xc = 0; + h.yc = 0; + h.r = 0; + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/parse_param.h b/hyp2mat/lib/parse_param.h new file mode 100644 index 0000000..47ed1bb --- /dev/null +++ b/hyp2mat/lib/parse_param.h @@ -0,0 +1,172 @@ + +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PARSE_PARAM_H +#define PARSE_PARAM_H + +#include <string> + + /* + * Parameters passed on by the parser. + * All variables added here are initialized in new_record(). + */ + + enum pad_type_enum { PAD_TYPE_METAL, PAD_TYPE_ANTIPAD, PAD_TYPE_THERMAL_RELIEF }; + + enum pin_function_enum { PIN_SIM_IN, PIN_SIM_OUT, PIN_SIM_BOTH }; + + enum polygon_type_enum { POLYGON_TYPE_POUR, POLYGON_TYPE_PLANE, POLYGON_TYPE_COPPER, POLYGON_TYPE_PAD, POLYGON_TYPE_ANTIPAD }; + + struct parse_param { + double vers; /* version of the hyp file format */ + bool detailed; /* data detailed enough for power integrity */ + bool unit_system_english; /* english or metric units */ + bool metal_thickness_weight; /* copper by weight or by length */ + double default_plane_separation; /* trace to plane separation */ + + /* stackup record */ + bool use_die_for_metal; /* dielectric constant and loss tangent of dielectric for metal layers */ + double bulk_resistivity; + bool conformal; + double epsilon_r; + std::string layer_name; + double loss_tangent; + std::string material_name; + double plane_separation; + double plating_thickness; + bool prepreg; + double temperature_coefficient; /* temperature coefficient of resistivity */ + double thickness; /* layer thickness */ + + /* stackup record flags */ + bool bulk_resistivity_set; + bool conformal_set; + bool epsilon_r_set; + bool layer_name_set; + bool loss_tangent_set; + bool material_name_set; + bool plane_separation_set; + bool plating_thickness_set; + bool prepreg_set; + bool temperature_coefficient_set; + bool thickness_set; + + /* device record */ + std::string device_type; + std::string ref; + double value_float; + std::string value_string; + std::string package; + + /* device record flags */ + bool name_set; + bool value_float_set; + bool value_string_set; + bool package_set; + + /* supplies record */ + bool voltage_specified; + bool conversion; + + /* padstack record */ + std::string padstack_name; + double drill_size; + double pad_shape; + double pad_sx; + double pad_sy; + double pad_angle; + double thermal_clear_shape; + double thermal_clear_sx; + double thermal_clear_sy; + double thermal_clear_angle; + pad_type_enum pad_type; + + /* padstack record flags */ + bool padstack_name_set; + bool drill_size_set; + bool pad_type_set; + + /* net record */ + double width; + double left_plane_separation; + bool width_set; + bool left_plane_separation_set; + + /* via subrecord of net */ + std::string layer1_name; + bool layer1_name_set; + std::string layer2_name; + bool layer2_name_set; + std::string pad1_shape; + double pad1_sx; + double pad1_sy; + double pad1_angle; + std::string pad2_shape; + double pad2_sx; + double pad2_sy; + double pad2_angle; + + /* pin subrecord of net */ + std::string pin_reference; + bool pin_reference_set; + pin_function_enum pin_function; + bool pin_function_set; + + /* useg subrecord of net */ + std::string zlayer_name; + bool zlayer_name_set; + double length; + double impedance; + bool impedance_set; + double delay; + double resistance; + bool resistance_set; + + /* polygon subrecord of net */ + int id; + bool id_set; + polygon_type_enum polygon_type; + bool polygon_type_set; + + /* net class record */ + std::string net_class_name; + std::string net_name; + + /* key record */ + std::string key; + + /* Attributes */ + std::string name; /* attribute name */ + std::string value; /* attribute value */ + + /* point, line and arc coordinates */ + double x; /* coordinates point */ + double y; /* coordinates point */ + double x1; /* coordinates point 1 */ + double y1; /* coordinates point 1 */ + double x2; /* coordinates point 2 */ + double y2; /* coordinates point 2 */ + double xc; /* coordinates arc */ + double yc; /* coordinates arc */ + double r; /* coordinates arc */ + }; + +#endif diff --git a/hyp2mat/lib/parser.h b/hyp2mat/lib/parser.h new file mode 100644 index 0000000..217ed8c --- /dev/null +++ b/hyp2mat/lib/parser.h @@ -0,0 +1,40 @@ + +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PARSER_H +#define PARSER_H + +#include <string> +#include "hypfile.h" + + /* + * hooks in bison (parser) and flex (scanner) code + */ + + extern FILE *yyin; /* file the parser reads from */ + extern int yy_flex_debug ; /* debug scanner if == 1 */ + extern int yydebug ; /* debug parser if == 1 */ + extern int yylineno; + extern char *yytext; + extern int yyparse(HypFile::Hyp *); + extern int yylex(void); + +#endif diff --git a/hyp2mat/lib/pcb.cc b/hyp2mat/lib/pcb.cc new file mode 100644 index 0000000..86bc657 --- /dev/null +++ b/hyp2mat/lib/pcb.cc @@ -0,0 +1,333 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <iomanip> + +#include "config.h" +#include "hyp2mat.h" + +#include "hyperlynx.h" +#include "pdf.h" +#include "csxcad.h" + +using namespace Hyp2Mat; + +PCB::PCB() +{ + debug = 0; + raw = false; + via_plating_thickness = 0; + _arc_precision = 0; + _clearance = -1.0; + _epsilon_r = -1.0; + _epsilon_r_override = false; + _bulk_resistivity = -1.0; + _bulk_resistivity_override = false; + _loss_tangent = -1.0; + _loss_tangent_override = false; +} + +/* + * Read a pcb in hyperlynx format. + * "filename" is the Hyperlynx file to import. + * "layers" is the names of the layers to import. Default is importing all layers. + * "nets" is the names of the nets to import. Default is importing all nets. + */ + +void PCB::ReadHyperLynx (std::string filename, std::vector<std::string> layers, std::vector<std::string> nets) +{ + + /* set accuracy with which circle arcs are converted to polygons */ + Polygon::SetArcPrecision(_arc_precision); + + /* read hyperlynx file */ + HyperLynx hyperlynx; + + hyperlynx.layers = layers; + hyperlynx.nets = nets; + hyperlynx.raw = raw; + hyperlynx.arc_precision = _arc_precision; + hyperlynx.clearance = _clearance; + hyperlynx.flood_layers = flood_layers; + hyperlynx.bounds = _bounds; + + hyperlynx.Read(filename, *this); + + /* Check for empty board outline; add default board outline if outline empty */ + _CheckBoardOutline(); + + /* Epsilon_r, bulk resistivity and loss tangent overrides */ + _UserOverrides(); + + return; +} + +/* + * Write a pcb in pdf format + */ + +void PCB::WritePDF (std::string filename, double hue, double saturation, double brightness) +{ + PDF pdf; + pdf.SetPalette(hue, saturation, brightness); + pdf.Write(filename, *this); +} + +/* + * Write a pcb in csxcad format + */ + +void PCB::WriteCSXCAD (std::string filename, bool pcb_outline, bool lossy_copper, bool metal_3d) +{ + CSXCAD csxcad; + csxcad.Write(filename, *this, pcb_outline, lossy_copper, metal_3d); +} + +/* + * Set dielectric epsilon r. Overrides value in Hyperlynx file. + */ + +void PCB::SetEpsilonR(double new_epsilon_r) +{ + /* save the new value of epsilon r. run before loading hyperlynx file. */ + _epsilon_r = new_epsilon_r; + _epsilon_r_override = true; + + return; +} + +void PCB::SetLossTangent(double new_loss_tangent) +{ + /* save the new value of loss_tangent. run before loading hyperlynx file. */ + _loss_tangent = new_loss_tangent; + _loss_tangent_override = true; + return; +} + +void PCB::SetBulkResistance(double new_bulk_resistivity) +{ + /* save the new value of bulk_resistivity. run before loading hyperlynx file. */ + _bulk_resistivity = new_bulk_resistivity; + _bulk_resistivity_override = true; + return; +} + +/* + * Modify the value of epsilon r. Run after loading hyperlynx file. + */ + +void PCB::_UserOverrides() +{ + + /* set all layers to epsilon_r, except outer copper layers, which we assume are in air (er = 1) */ + + /* iterate over all layers, setting epsilon */ + for (LayerList::iterator i = stackup.begin(); i != stackup.end(); ++i) { + + /* override epsilon and loss tangent of dielectric layers */ + if (_epsilon_r_override && (i->layer_type == LAYER_DIELECTRIC)) i->epsilon_r = _epsilon_r; + if (_loss_tangent_override && (i->layer_type == LAYER_DIELECTRIC)) i->loss_tangent = _loss_tangent; + + /* override resistivity of metal layers */ + if (_bulk_resistivity_override && (i->layer_type != LAYER_DIELECTRIC)) i->bulk_resistivity = _bulk_resistivity; + + /* check for outer copper layer */ + if (i->layer_type == LAYER_DIELECTRIC) + continue; + + /* find dielectric layer above current metal layer */ + bool upper_layer_found = false; + for (LayerList::iterator j = stackup.begin(); j != i; ++j) + if (j->layer_type == LAYER_DIELECTRIC) upper_layer_found = true; + + /* find dielectric layer below current metal layer */ + bool lower_layer_found = false; + for (LayerList::iterator j = i; j != stackup.end(); ++j) + if (j->layer_type == LAYER_DIELECTRIC) lower_layer_found = true; + + /* assume outer copper layers are in air */ + if (!upper_layer_found || !lower_layer_found) { + i->epsilon_r = 1.0; + i->loss_tangent = 0.0; + } + } + return; +} + +/* + * set resolution of x and y coordinates + */ + +void PCB::SetGrid(double new_grid) +{ + if (new_grid > 0.0) Polygon::SetGrid(new_grid); + return; +} + +/* + * set maximum difference between perfect circle arc and polygonal approximation + */ + +void PCB::SetArcPrecision(double new_arc_precision) +{ + if (new_arc_precision >= 0.0) _arc_precision = new_arc_precision; + return; +} + +/* + * set track to plane clearance + */ + +void PCB::SetClearance(double new_clearance) +{ + if (new_clearance >= 0.0) _clearance = new_clearance; + return; +} + +/* + * Calculate the bounding rectangle of a pcb. + */ + +Bounds PCB::GetBounds() { + Bounds bounds; + bounds.x_min = 0; + bounds.x_max = 0; + bounds.y_min = 0; + bounds.y_max = 0; + bounds.z_min = 0; + bounds.z_max = 0; + + /* iterate over layers; find z_max and z_min */ + if (!stackup.empty()) { + bounds.z_min = stackup.back().z0; + bounds.z_max = stackup.front().z1; + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) { + if (l->z0 < bounds.z_min) bounds.z_min = l->z0; + if (l->z1 > bounds.z_max) bounds.z_max = l->z1; + } + } + + /* iterate over board edge; find x_min, x_max, y_min, y_max */ + bool first_point = true; + + for (FloatPolygons::iterator i = board.begin(); i != board.end(); ++i) + for (FloatPolygon::iterator j = i->poly.begin(); j != i->poly.end(); ++j) { + if ((j->x > bounds.x_max) || first_point) bounds.x_max = j->x; + if ((j->y > bounds.y_max) || first_point) bounds.y_max = j->y; + if ((j->x < bounds.x_min) || first_point) bounds.x_min = j->x; + if ((j->y < bounds.y_min) || first_point) bounds.y_min = j->y; + first_point = false; + } + + /* iterate over layers */ + for (LayerList::iterator l = stackup.begin(); l != stackup.end(); ++l) + for (FloatPolygons::iterator i = l->metal.begin(); i != l->metal.end(); ++i) + for (FloatPolygon::iterator j = i->poly.begin(); j != i->poly.end(); ++j) { + if ((j->x > bounds.x_max) || first_point) bounds.x_max = j->x; + if ((j->y > bounds.y_max) || first_point) bounds.y_max = j->y; + if ((j->x < bounds.x_min) || first_point) bounds.x_min = j->x; + if ((j->y < bounds.y_min) || first_point) bounds.y_min = j->y; + first_point = false; + } + + /* iterate over vias */ + for (ViaList::iterator i = via.begin(); i != via.end(); ++i) { + if ((i->x + i->radius > bounds.x_max) || first_point) bounds.x_max = i->x + i->radius; + if ((i->y + i->radius > bounds.y_max) || first_point) bounds.y_max = i->y + i->radius; + if ((i->x - i->radius < bounds.x_min) || first_point) bounds.x_min = i->x - i->radius; + if ((i->y - i->radius < bounds.y_min) || first_point) bounds.y_min = i->y - i->radius; + first_point = false; + } + + return bounds; + } + +/* + * Set the bounding rectangle of a pcb. + */ + +void PCB::SetBounds(Bounds new_bounds) +{ + _bounds = new_bounds; +} + +void PCB::_CheckBoardOutline() +{ + if (!board.empty()) return; + + if (debug) std::cerr << "board outline empty" << std::endl; + + Bounds bounds = GetBounds(); + + if ((bounds.x_min != bounds.x_max) && (bounds.y_min != bounds.y_max)) { + + if (debug) std::cerr << "adding default board outline" << std::endl; + + /* add rectangular outline to pcb */ + + Hyp2Mat::FloatPoly outline; + outline.poly.push_back(Hyp2Mat::FloatPoint(bounds.x_min, bounds.y_min)); + outline.poly.push_back(Hyp2Mat::FloatPoint(bounds.x_min, bounds.y_max)); + outline.poly.push_back(Hyp2Mat::FloatPoint(bounds.x_max, bounds.y_max)); + outline.poly.push_back(Hyp2Mat::FloatPoint(bounds.x_max, bounds.y_min)); + outline.is_hole = false; + outline.nesting_level = 0; + + board.push_back(outline); + } +} + +void PCB::PrintSummary() { + Bounds bounds = GetBounds(); + + /* save stream flags and precision */ + std::ios::fmtflags saved_flgs = std::cerr.flags(); + std::streamsize saved_prec = std::cerr.precision(); + std::cerr.flags(std::ios::fixed); + std::cerr.precision(2); + + double m_to_mm = 1000; + std::cerr << "board size:" << std::endl; + std::cerr << " x = " << std::setw(8) << bounds.x_min*m_to_mm << " : " << std::setw(8) << bounds.x_max*m_to_mm << " mm" << std::endl; + std::cerr << " y = " << std::setw(8) << bounds.y_min*m_to_mm << " : " << std::setw(8) << bounds.y_max*m_to_mm << " mm" << std::endl; + std::cerr << " z = " << std::setw(8) << bounds.z_min*m_to_mm << " : " << std::setw(8) << bounds.z_max*m_to_mm << " mm" << std::endl; + + std::cerr << "layers:" << std::endl; + if (stackup.empty()) std::cerr << "(none)"; + for (LayerList::iterator it = stackup.begin(); it != stackup.end(); ++it) { + std::cerr << " "; + switch (it->layer_type) { + case LAYER_SIGNAL: std::cerr << "signal " << std::setw(8) << it->thickness * 1E6 << " um" ; break; + case LAYER_DIELECTRIC: std::cerr << "dielectric " << std::setw(8) << it->thickness * 1E3 << " mm" ; break; + case LAYER_PLANE: std::cerr << "plane " << std::setw(8) << it->thickness * 1E6 << " um" ; break; + default: std::cerr << "*** " << std::setw(8) << it->thickness * 1E3 << " mm" ; break; + } + std::cerr << " eps_r " << it->epsilon_r << " '" << it->layer_name << "'" << std::endl; + } + + /* reset flags and precision */ + std::cerr.flags(saved_flgs); + std::cerr.precision(saved_prec); + + return; +} + +/* not truncated */ diff --git a/hyp2mat/lib/pdf.cc b/hyp2mat/lib/pdf.cc new file mode 100644 index 0000000..a233f29 --- /dev/null +++ b/hyp2mat/lib/pdf.cc @@ -0,0 +1,292 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Uses libharu by Takeshi Kanno to generate pdf documents. + */ + +#include <iostream> +#include <cstdio> +#include <algorithm> +#include "pdf.h" +#include <hpdf_types.h> + +using namespace Hyp2Mat; + +PDF::PDF() +{ + text_height = 10; /* caption size in points */ + margin = text_height; /* page margin */ + m_to_points = 1 / (2.54 / 100 / 72); /* convert m to points (1/72 of an inch) */ + x_max = x_min = y_max = y_min = 0; +} + +/* + * PDF error handler + */ + +void error_handler (HPDF_STATUS error_no, HPDF_STATUS detail_no, void *user_data) +{ + printf ("ERROR: error_no=%04X, detail_no=%d\n", (unsigned int) error_no, (int) detail_no); + throw std::exception (); /* throw exception on error */ +} + +/* + * set page size to size of pcb board + */ + +void PDF::set_page_size(HPDF_Page page) +{ + double width = x_max - x_min + 2 * margin; + double height = y_max - y_min + 2 * margin + text_height; /* add margin for caption */ + + if (width < 3) width = 3; + if (height < 3) height = 3; + + HPDF_Page_SetWidth(page, width); + HPDF_Page_SetHeight(page, height); +} + +/* + * Generate color palette given hue, saturation and value (brightness). + * After Martin Ankerl * "How to Generate Random Colors Programmatically" + */ + +void PDF::set_color(HPDF_Page page, int color_idx) +{ + PaletteColor rgb = Color(color_idx); + + HPDF_Page_SetRGBStroke(page, rgb.red, rgb.green, rgb.blue); + HPDF_Page_SetRGBFill(page, rgb.red, rgb.green, rgb.blue); +} + +/* + * Set stroke and fill color to black + */ + +void PDF::set_color_black(HPDF_Page page) +{ + HPDF_Page_SetRGBStroke(page, 0, 0, 0); + HPDF_Page_SetRGBFill(page, 0, 0, 0); +} + +/* + * fill all polygons + */ + +void PDF::page_fill(HPDF_Page page) +{ + if (HPDF_Page_GetGMode(page) == HPDF_GMODE_PATH_OBJECT) HPDF_Page_Fill (page); + return; +} + +/* + * draw all polygon outlines + */ + +void PDF::page_stroke(HPDF_Page page) +{ + if (HPDF_Page_GetGMode(page) == HPDF_GMODE_PATH_OBJECT) HPDF_Page_Stroke (page); + return; +} + +/* + * Write layer name on top of page + */ + +void PDF::draw_caption(HPDF_Page page, HPDF_Font font, std::string txt) +{ + HPDF_Page_SetFontAndSize(page, font, text_height); + HPDF_Page_BeginText(page); + HPDF_Page_MoveTextPos(page, margin, y_max - y_min + margin + text_height); /* page top left */ + HPDF_Page_ShowText(page, txt.c_str()); + HPDF_Page_EndText(page); +} + +/* + * Export copper polygon as pdf + */ + +void PDF::draw(HPDF_Page page, FloatPolygon& poly) +{ + double old_px, old_py; + + /* pdf requires outer edge of polygon to be clockwise; holes to be counterclockwise */ + + bool firstpoint = true; + for (FloatPolygon::iterator i = poly.begin(); i != poly.end(); ++i) { + + double px = i->x * m_to_points - x_min + margin; + double py = i->y * m_to_points - y_min + margin; + if (firstpoint) { + HPDF_Page_MoveTo(page, px, py); + firstpoint = false; + } + else { + if ((px != old_px) || (py != old_py)) + HPDF_Page_LineTo(page, px, py); + } + old_px = px; old_py = py; + } + HPDF_Page_ClosePath (page); + return; +} + +/* + * Export all polygons of a copper layer as pdf. May contain holes. + */ + +void PDF::draw(HPDF_Page page, FloatPolygons& polygons) +{ + for (FloatPolygons::iterator i = polygons.begin(); i != polygons.end(); ++i) + draw(page, i->poly); +} + +/* + * Draws a via as a circle. + */ + +void PDF::draw(HPDF_Page page, Hyp2Mat::Via& via) +{ + double x = via.x * m_to_points - x_min + margin; + double y = via.y * m_to_points - y_min + margin; + double r = via.radius * m_to_points; + HPDF_Page_Circle(page, x, y, r); +} + +void PDF::draw_composite_view(HPDF_Doc pdf, HPDF_Font font, Hyp2Mat::PCB& pcb) +{ + /* new page */ + HPDF_Page page = HPDF_AddPage(pdf); + set_page_size(page); + HPDF_Page_SetLineWidth(page, 0); + draw_caption(page, font, "Composite"); + + /* draw board */ + set_color_black(page); + draw(page, pcb.board); + page_stroke (page); + + /* draw layers */ + int current_color = 0; + for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) { + if (l->layer_type == LAYER_DIELECTRIC) continue; + set_color(page, current_color++); + draw(page, l->metal); + page_stroke (page); + } + + /* draw vias */ + set_color_black(page); + for (ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) + PDF::draw(page, *i); + page_fill (page); + + return; +} + +void PDF::draw_layer_view(HPDF_Doc pdf, HPDF_Font font, Hyp2Mat::PCB& pcb) +{ + + int current_color = 0; + for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) { + if (l->layer_type == LAYER_DIELECTRIC) continue; + + HPDF_Page page = HPDF_AddPage(pdf); + set_page_size(page); + HPDF_Page_SetLineWidth(page, 0); + + /* draw caption */ + draw_caption(page, font, l->layer_name); + + /* draw board */ + set_color_black(page); + draw(page, pcb.board); + page_stroke (page); + + /* draw layer */ + set_color(page, current_color++); + draw(page, l->metal); + page_fill (page); + + /* draw vias */ + set_color_black(page); + for (ViaList::iterator i = pcb.via.begin(); i != pcb.via.end(); ++i) { + /* blind vias: only draw via if via goes through current layer */ + if ((i->z0 <= l->z0) && (i->z1 >= l->z1)) { + draw(page, *i); + } + } + page_fill (page); + } +} + +/* + * Save current board as pdf. + * First page is the composite view of all signal layers, + * subsequent pages are one layer each. + */ + +void PDF::Write(const std::string& filename, Hyp2Mat::PCB& pcb) +{ + /* choose a color palette. Change HSV values to what suits your taste */ + int number_of_colors = 0; + for (LayerList::reverse_iterator l = pcb.stackup.rbegin(); l != pcb.stackup.rend(); ++l) + if (l->layer_type != LAYER_DIELECTRIC) number_of_colors++; /* count layers to determine number of colors needed */ + + SetNumberOfColors(number_of_colors); + + HPDF_Doc pdf = HPDF_New (error_handler, NULL); /* set error-handler */ + if (!pdf) { + std::cerr << "cannot create pdf object" << std::endl; + return; + } + + try { + /* get pcb size */ + Bounds bounds = pcb.GetBounds(); + x_max = bounds.x_max * m_to_points; + y_max = bounds.y_max * m_to_points; + x_min = bounds.x_min * m_to_points; + y_min = bounds.y_min * m_to_points; + + /* Document settings */ + HPDF_Font font = HPDF_GetFont(pdf, "Helvetica", NULL); + HPDF_SetInfoAttr(pdf, HPDF_INFO_CREATOR, "hyp2mat"); + HPDF_SetCompressionMode (pdf, HPDF_COMP_ALL); + + /* Draw composite page */ + draw_composite_view(pdf, font, pcb); + + /* Draw individual layers' pages */ + draw_layer_view(pdf, font, pcb); + + HPDF_SaveToFile (pdf, filename.c_str()); + } + catch (...) { + HPDF_Free (pdf); + return; + } + + HPDF_Free (pdf); + + return; +} +/* not truncated */ diff --git a/hyp2mat/lib/pdf.h b/hyp2mat/lib/pdf.h new file mode 100644 index 0000000..98d654e --- /dev/null +++ b/hyp2mat/lib/pdf.h @@ -0,0 +1,59 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PDF_H +#define PDF_H + +#include <cmath> +#include <hpdf.h> + +#include "palette.h" +#include "hyp2mat.h" + +class PDF: public Palette { +public: + PDF(); + void Write(const std::string& filename, Hyp2Mat::PCB& pcb); + +private: + Palette palette; + double x_max; /* board dimensions */ + double y_max; + double x_min; + double y_min; + double text_height; /* in points */ + double margin; /* in points */ + double m_to_points; /* convert meter to points */ + void set_page_size(HPDF_Page page); + void set_color(HPDF_Page page, int color_idx); + void set_color_black(HPDF_Page page); + void draw_composite_view(HPDF_Doc pdf, HPDF_Font font, Hyp2Mat::PCB& pcb); + void draw_layer_view(HPDF_Doc pdf, HPDF_Font font, Hyp2Mat::PCB& pcb); + void draw_caption(HPDF_Page page, HPDF_Font font, std::string txt); + void draw(HPDF_Page page, Hyp2Mat::FloatPolygons& polygons); /* output a polygon */ + void draw(HPDF_Page page, Hyp2Mat::FloatPolygon& polygon); /* output a polygon edge */ + void draw(HPDF_Page page, Hyp2Mat::Via& via); /* output a via */ + void page_fill(HPDF_Page page); /* fill */ + void page_stroke(HPDF_Page page); /* draw lines */ + }; + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/polygon.cc b/hyp2mat/lib/polygon.cc new file mode 100644 index 0000000..5d3d9d1 --- /dev/null +++ b/hyp2mat/lib/polygon.cc @@ -0,0 +1,335 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cmath> +#include <iostream> +#include <algorithm> + +#include "polygon.h" + +using namespace Hyp2Mat; + +double Polygon::_grid = 10E-6; /* 10 micron */ +double Polygon::_arc_precision = 0.0; + +Polygon::Polygon() +{ + _filltype = ClipperLib::pftNonZero; // Use edge orientation to determine whether or not polygon edge is a hole. +} + +void Polygon::SetGrid(double new_grid) +{ + _grid = new_grid; +} + +void Polygon::SetArcPrecision(double new_arc_precision) +{ + _arc_precision = new_arc_precision; +} + +/* + * Align coordinates to grid + */ + +double Polygon::AlignToGrid(double x) +{ + return floor(x / _grid + 0.5) * _grid; +} + +/* + * Return true if polygon is empty + */ + +bool Polygon::IsEmpty() +{ + return _subject.empty(); +} + +/* + * Add an outer edge to the polygon + */ + +void Polygon::AddEdge(FloatPolygon edge) +{ + _AddEdge(edge, true); +} + +/* + * Add an inner edge to the polygon + */ + +void Polygon::AddHole(FloatPolygon hole) +{ + _AddEdge(hole, false); +} + +/* + * Add an edge - positive or negative - to the polygon + */ + +void Polygon::_AddEdge(FloatPolygon edge, bool orientation) +{ + + /* trivial case */ + if (edge.empty()) return; + + /* convert edge to Clipper polygon */ + ClipperLib::Polygon poly = _convert(edge); + + /* orient edge clockwise */ + if (!Orientation(poly) == orientation) ClipperLib::ReversePolygon(poly); + + /* Add to polygon */ + _subject.push_back(poly); + + return; +} + +/* + * convert from Hyp2Mat floating point point to Clipper long64 point + */ + +ClipperLib::IntPoint Polygon::_convert(FloatPoint p) +{ + return ClipperLib::IntPoint(floor(p.x / _grid + 0.5), floor(p.y / _grid + 0.5)); +} + +/* + * convert from Hyp2Mat floating point polygon to Clipper long64 polygon + */ + +ClipperLib::Polygon Polygon::_convert(FloatPolygon edge) +{ + ClipperLib::Polygon poly; + for (FloatPolygon::iterator i = edge.begin(); i != edge.end(); ++i) + poly.push_back(_convert(*i)); + + /* remove repeated vertices */ + poly = _unique(poly); + + return poly; +} + +/* + * Remove repeated vertices from a polygon + */ + +ClipperLib::Polygon Polygon::_unique(ClipperLib::Polygon p) +{ + ClipperLib::Polygon q = p; + q.clear(); + + if (p.empty()) return q; + + /* initial condition: add first vertex */ + q.push_back(p.front()); + + /* create list of non-repeating vertices */ + for (ClipperLib::Polygon::iterator i = p.begin(); i != p.end(); ++i) + if ((i->X != q.back().X) || (i->Y != q.back().Y)) q.push_back(*i); + + /* Remove last vertex if first and last vertex same */ + while ((q.size() > 1) && ((q.front().X == q.back().X) && (q.front().Y == q.back().Y))) + q.pop_back(); + + return q; +} + +/* + * Expand the polygon by a distance + */ + +void Polygon::Offset(double delta) +{ + ClipperLib::Polygons result; + double d = delta / _grid; + double limit = _arc_precision / _grid; + + if (delta == 0.0) return; + + if (_arc_precision == 0) + /* square corners */ + OffsetPolygons(_subject, result, d, ClipperLib::jtSquare); + else + /* round corners */ + OffsetPolygons(_subject, result, d, ClipperLib::jtRound, limit); + + _subject = result; + + return; +} + +/* + * Remove self-intersections + */ + +void Polygon::Simplify() +{ + ClipperLib::Polygons result; + ClipperLib::SimplifyPolygons(_subject, result, _filltype); + _subject = result; + +} + +/* + * Basic polygon operations + */ + +void Polygon::Intersection(Polygon clip) +{ + _Execute(ClipperLib::ctIntersection, clip); +} + +void Polygon::Union(Polygon clip) +{ + _Execute(ClipperLib::ctUnion, clip); +} + +void Polygon::Difference(Polygon clip) +{ + _Execute(ClipperLib::ctDifference, clip); +} + +void Polygon::Xor(Polygon clip) +{ + _Execute(ClipperLib::ctXor, clip); +} + +void Polygon::_Execute(ClipperLib::ClipType op, Polygon clip) +{ + ClipperLib::Polygons result; + ClipperLib::Clipper clipper; + + clipper.AddPolygons(_subject, ClipperLib::ptSubject); + clipper.AddPolygons(clip._subject, ClipperLib::ptClip); + + clipper.Execute(op, result, _filltype, _filltype); + _subject = result; + + return; +} + +/* + * Convert from Clipper long64 polygon to Hyp2Mat floating point polygon + */ + +FloatPolygons Polygon::Result() +{ + ClipperLib::Clipper clipper; + ClipperLib::PolyTree polytree; + + /* convert to PolyTree */ + clipper.AddPolygons(_subject, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, polytree, _filltype, _filltype); + + /* recursively descend polytree, converting nodes */ + FloatPolygons result; + for (int i = 0; i < polytree.ChildCount(); ++i) + _AddToPolygonList(*polytree.Childs[i], result, 0); + + return result; +} + +void Polygon::_AddToPolygonList(ClipperLib::PolyNode& polynode, FloatPolygons& polygons, int level) +{ + FloatPoly outer_edge; + + outer_edge.poly = _convert(polynode.Contour); + outer_edge.is_hole = !ClipperLib::Orientation(polynode.Contour); + outer_edge.nesting_level = level; + /* make outer edges of polygon clockwise; holes counterclockwise. XXX is this superfluous? */ + if (outer_edge.is_hole == IsClockwise(outer_edge.poly)) Reverse(outer_edge.poly); + + polygons.push_back(outer_edge); + + for (int i = 0; i < polynode.ChildCount(); ++i) { + FloatPoly inner_edge; + inner_edge.poly = _convert(polynode.Childs[i]->Contour); + inner_edge.is_hole = !ClipperLib::Orientation(polynode.Childs[i]->Contour); + inner_edge.nesting_level = level + 1; + /* make outer edges of polygon clockwise; holes counterclockwise. XXX is this superfluous? */ + if (inner_edge.is_hole == IsClockwise(inner_edge.poly)) Reverse(inner_edge.poly); + polygons.push_back(inner_edge); + } + + /* Add outer polygons nested within holes */ + for (int i = 0; i < polynode.ChildCount(); ++i) + for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) + _AddToPolygonList(*polynode.Childs[i]->Childs[j], polygons, level + 2); + + return; +} + + /* + * Convert edge back from ClipperLib to Hyp2Mat + */ + +FloatPolygon Polygon::_convert (ClipperLib::Polygon clip_poly) +{ + FloatPolygon edge; + + for (ClipperLib::Polygon::iterator i = clip_poly.begin(); i != clip_poly.end(); ++i) + edge.push_back(_convert(*i)); + + return edge; +} + + /* + * Convert point back from ClipperLib to Hyp2Mat + */ + +FloatPoint Polygon::_convert (ClipperLib::IntPoint p) +{ + return FloatPoint(p.X * _grid, p.Y * _grid); +} + +/* + * Calculate if a polygon is clockwise or not. + */ + +bool Hyp2Mat::IsClockwise(FloatPolygon& poly) { + + /* calculate area of polygon */ + double area = 0; + for (FloatPolygon::iterator i = poly.begin(); i != poly.end(); ++i) { + /* i points to current element; j points to next element */ + FloatPolygon::iterator j = i; + ++j; + /* polygon is closed */ + if (j == poly.end()) j = poly.begin(); + area += i->x * j->y - j->x * i->y; + } + + /* positive area is clockwise. Arbitrarily choose clockwise if area 0 */ + return area >= 0; + +} + +/* + * Reverse order of vertices. + */ + +void Hyp2Mat::Reverse(FloatPolygon& poly) +{ + std::reverse(poly.begin(), poly.end()); +} + + +/* not truncated */ diff --git a/hyp2mat/lib/polygon.h b/hyp2mat/lib/polygon.h new file mode 100644 index 0000000..3df23ef --- /dev/null +++ b/hyp2mat/lib/polygon.h @@ -0,0 +1,74 @@ +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef POLYGON_H +#define POLYGON_H + +#include "clipper.hpp" +#include "hyp2mat.h" + +namespace Hyp2Mat { + + bool IsClockwise (FloatPolygon& poly); + void Reverse (FloatPolygon& poly); + + class Polygon { + public: + Polygon(); + static void SetGrid(double new_grid); + static double AlignToGrid(double x); + static void SetArcPrecision(double new_arc_precision); + void AddEdge(FloatPolygon edge); + void AddHole(FloatPolygon hole); + void Offset(double delta); + void Simplify(); + void Intersection(Polygon clip); + void Union(Polygon clip); + void Difference(Polygon clip); + void Xor(Polygon clip); + bool IsEmpty(); + FloatPolygons Result(); + private: + /* convert Hyp2Mat to Clipper */ + void _AddEdge(FloatPolygon edge, bool orientation); + ClipperLib::IntPoint _convert(FloatPoint p); + ClipperLib::Polygon _convert(FloatPolygon edge); + ClipperLib::Polygon _unique(ClipperLib::Polygon p); + + /* Clipper polygon operations */ + void _Execute(ClipperLib::ClipType op, Polygon clip); + ClipperLib::PolyFillType _filltype; + + /* convert Clipper to Hyp2Mat */ + void _AddToPolygonList(ClipperLib::PolyNode& polynode, FloatPolygons& polygons, int level); + FloatPolygon _convert (ClipperLib::Polygon clip_poly); + FloatPoint _convert (ClipperLib::IntPoint p); + + /* data */ + ClipperLib::Polygons _subject; + static double _grid; /* resolution of x and y coordinates, identical for all polygons. */ + static double _arc_precision; /* maximum difference between perfect circle arc and polygonal approximation */ + }; + +}; + +#endif + +/* not truncated */ diff --git a/hyp2mat/lib/scan.ll b/hyp2mat/lib/scan.ll new file mode 100644 index 0000000..3e07902 --- /dev/null +++ b/hyp2mat/lib/scan.ll @@ -0,0 +1,371 @@ + +/* + * hyp2mat - convert hyperlynx files to matlab scripts + * Copyright 2012 Koen De Vleeschauwer. + * + * This file is part of hyp2mat. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +%option noyywrap nodefault yylineno debug + +%{ +#include <stdlib.h> +#include "parse.h" +#include "parser.h" + +/* copy a string between quotes */ +char *strunquote(const char *); + +/* remember hyperlynx file section we're in */ +int section = -1; + +%} + + /* + * The scanner knows five states: INITIAL, STATE_STRING, STATE_POSINT, STATE_FLOAT and STATE_COMMENT + * + * In state INITIAL the scanner recognizes: + * - punctuation (such as {}()=, ) + * - keywords (such as VERSION, BOARD, NET), + * - strings (both between double quotes " and unquoted), + * - and floating point numbers (with optional suffix, eg. 10 uF), + * + * In STATE_STRING the scanner recognizes punctuation and strings only. + * This avoids unquoted strings being interpreted as numbers, or keywords. + * + * In STATE_POSINT the scanner recognizes a positive integer. + * + * In STATE_FLOAT the scanner recognizes a floating point number (without suffix). + * + * In STATE_COMMENT the scanner discards all text up to the next + * right brace } , and then continues in state INITIAL. + * + */ + +%x STATE_STRING STATE_POSINT STATE_FLOAT STATE_COMMENT STATE_COMMENT_EOL + + /* whitespace: space, tab, vertical tab, form feed */ +WS [ \t\v\f] + + /* accept windows and unix newlines */ +NEWLINE [\r\n]+ + + /* + * lines with an asterisk in the first column are comments + */ +COMMENT ^\*[^\n\r]*[\n\r]+ + + /* Left-hand side of an assignment: check whether next token is equals sign */ +LHS [ \t\v\f]*"=" + + /* + * Positive integer + */ + +POSINT [0-9]+ + + /* + * Floating point numbers + */ + + /* ordinary floating point numbers */ +SIMPLE_FLOAT [-+]?([0-9]+"."[0-9]*|"."?[0-9]+)([Ee][-+]?[0-9]+)? + + /* floating point numbers with suffix, e,g. pF, nH */ +FLOAT_SUFFIX [A-Za-z]*{WS}+ +FLOAT_YOTTA {SIMPLE_FLOAT}{WS}*"Y"{FLOAT_SUFFIX} +FLOAT_ZETA {SIMPLE_FLOAT}{WS}*"Z"{FLOAT_SUFFIX} +FLOAT_EXA {SIMPLE_FLOAT}{WS}*"E"{FLOAT_SUFFIX} +FLOAT_PETA {SIMPLE_FLOAT}{WS}*"P"{FLOAT_SUFFIX} +FLOAT_TERA {SIMPLE_FLOAT}{WS}*"T"{FLOAT_SUFFIX} +FLOAT_GIGA {SIMPLE_FLOAT}{WS}*"G"{FLOAT_SUFFIX} +FLOAT_MEGA {SIMPLE_FLOAT}{WS}*"M"{FLOAT_SUFFIX} +FLOAT_KILO {SIMPLE_FLOAT}{WS}*[Kk]{FLOAT_SUFFIX} +FLOAT_MILLI {SIMPLE_FLOAT}{WS}*"m"{FLOAT_SUFFIX} +FLOAT_MICRO {SIMPLE_FLOAT}{WS}*[uU]{FLOAT_SUFFIX} +FLOAT_NANO {SIMPLE_FLOAT}{WS}*[nN]{FLOAT_SUFFIX} +FLOAT_PICO {SIMPLE_FLOAT}{WS}*[pP]{FLOAT_SUFFIX} +FLOAT_FEMTO {SIMPLE_FLOAT}{WS}*[fF]{FLOAT_SUFFIX} +FLOAT_ATTO {SIMPLE_FLOAT}{WS}*"a"{FLOAT_SUFFIX} +FLOAT_ZEPTO {SIMPLE_FLOAT}{WS}*"z"{FLOAT_SUFFIX} +FLOAT_YOCTO {SIMPLE_FLOAT}{WS}*"y"{FLOAT_SUFFIX} + + /* + * Strings + */ + + /* an unquoted string */ +STRING [^ \t\v\f\r\n\{\}\(\)=\"]+ + + /* a string enclosed in double quotes " */ +QUOTED_STRING \"([^\"\n]|\"\")*\" + +%% + + /* When in STATE_COMMENT skip all comment until next right brace */ +<STATE_COMMENT>{ +[^\}]* { BEGIN INITIAL; /* skip all comment until next right brace */ } +} + + /* When in STATE_COMMENT_EOL skip all comment until end-of-line */ +<STATE_COMMENT_EOL>{ +[^\r\n]*{NEWLINE}+ { BEGIN INITIAL; /* skip all comment until next end-of-line */ } +} + + + /* skip comments and whitespace */ +<*>{ + + {COMMENT} { /* skip comments */ } + + {WS}+ { /* skip whitespace */ } + + {NEWLINE}+ { /* skip newlines */ } + +} + + /* + * Hyperlynx keywords + */ + + /* Sections */ + +"BOARD_FILE" {section = BOARD_FILE; return BOARD_FILE;} +"VERSION" {section = VERSION; return VERSION;} +"DATA_MODE" {section = DATA_MODE; return DATA_MODE;} +"UNITS" {section = UNITS; return UNITS;} +"PLANE_SEP" {section = PLANE_SEP; return PLANE_SEP;} +"BOARD" {section = BOARD; BEGIN STATE_COMMENT_EOL; return BOARD;} +"STACKUP" {section = STACKUP; BEGIN STATE_COMMENT_EOL; return STACKUP;} +"DEVICES" {section = DEVICES; BEGIN STATE_COMMENT_EOL; return DEVICES;} +"SUPPLIES" {section = SUPPLIES; BEGIN STATE_COMMENT_EOL; return SUPPLIES;} +"PADSTACK" {section = PADSTACK; BEGIN STATE_STRING; return PADSTACK;} +"NET" {section = NET; BEGIN STATE_STRING; return NET;} +"NET_CLASS" {section = NET_CLASS; return NET_CLASS;} +"END" {section = END; return END;} +"KEY" {section = KEY; return KEY;} + + /* Keywords */ + +"A" {return A;} +"ARC" {return ARC;} +"COPPER" {return COPPER;} +"CURVE" {return CURVE;} +"DETAILED" {if (section == DATA_MODE) BEGIN STATE_COMMENT; return DETAILED;} +"DIELECTRIC" {return DIELECTRIC;} +"ENGLISH" {return ENGLISH;} +"LENGTH" {if (section == UNITS) BEGIN STATE_COMMENT; return LENGTH;} +"LINE" {return LINE;} +"METRIC" {return METRIC;} +"M" {return M;} +"N" {return N;} +"OPTIONS" {return OPTIONS;} +"PAD" {return PAD;} +"PERIMETER_ARC" {return PERIMETER_ARC;} +"PERIMETER_SEGMENT" {return PERIMETER_SEGMENT;} +"PIN" {return PIN;} +"PLANE" {return PLANE;} +"POLYGON" {return POLYGON;} +"POLYLINE" {return POLYLINE;} +"POLYVOID" {return POLYVOID;} +"POUR" {return POUR;} +"S" {return S;} +"T" {return T;} +"SEG" {return SEG;} +"SIGNAL" {return SIGNAL;} +"SIMPLIFIED" {if (section == DATA_MODE) BEGIN STATE_COMMENT; return SIMPLIFIED; } +"SIM_BOTH" {return SIM_BOTH;} +"SIM_IN" {return SIM_IN;} +"SIM_OUT" {return SIM_OUT;} +"USEG" {return USEG;} +"VIA" {return VIA;} +"WEIGHT" {if (section == UNITS) BEGIN STATE_COMMENT; return WEIGHT;} + + /* Assignments */ + +"A"/{LHS} {return A;} +"A1"/{LHS} {return A1;} +"A2"/{LHS} {return A2;} +"BR"/{LHS} {return BR;} +"C"/{LHS} {return C;} +"C?"/{LHS} {return C_QM;} +"CO?"/{LHS} {return CO_QM;} +"D"/{LHS} {return D;} +"ER"/{LHS} {return ER;} +"F"/{LHS} {return F;} +"ID"/{LHS} {BEGIN STATE_POSINT; return ID;} +"L"/{LHS} {BEGIN STATE_STRING; return L;} +"L1"/{LHS} {BEGIN STATE_STRING; return L1;} +"L2"/{LHS} {BEGIN STATE_STRING; return L2;} +"LPS"/{LHS} {return LPS;} +"LT"/{LHS} {return LT;} +"M"/{LHS} {BEGIN STATE_STRING; return M;} +"N"/{LHS} {BEGIN STATE_STRING; return N;} +"NAME"/{LHS} {BEGIN STATE_STRING; return NAME;} + /* P is used as "plating thickness" in "stackup/signal" and as "padstack" in "net/via" */ +"P"/{LHS} {if (section == NET) BEGIN STATE_STRING; return P;} +"PKG"/{LHS} {BEGIN STATE_STRING; return PKG;} +"PR?"/{LHS} {return PR_QM;} +"PS"/{LHS} {return PS;} +"R"/{LHS} {return R;} +"REF"/{LHS} {BEGIN STATE_STRING; return REF;} +"S"/{LHS} {BEGIN STATE_STRING; return S;} +"SX"/{LHS} {return SX;} +"SY"/{LHS} {return SY;} +"S1"/{LHS} {BEGIN STATE_STRING; return S1;} +"S1X"/{LHS} {return S1X;} +"S1Y"/{LHS} {return S1Y;} +"S2"/{LHS} {BEGIN STATE_STRING; return S2;} +"S2X"/{LHS} {return S2X;} +"S2Y"/{LHS} {return S2Y;} +"T"/{LHS} {return T;} +"TC"/{LHS} {return TC;} +"USE_DIE_FOR_METAL"/{LHS} {return USE_DIE_FOR_METAL;} +"V"/{LHS} {BEGIN STATE_STRING; return V;} +"V?"/{LHS} {return V_QM;} +"VAL"/{LHS} {return VAL;} +"W"/{LHS} {return W;} +"X"/{LHS} {return X;} +"X1"/{LHS} {return X1;} +"X2"/{LHS} {return X2;} +"XC"/{LHS} {return XC;} +"Y"/{LHS} {return Y;} +"Y1"/{LHS} {return Y1;} +"Y2"/{LHS} {return Y2;} +"YC"/{LHS} {return YC;} +"Z"/{LHS} {return Z;} +"ZL"/{LHS} {return ZL;} +"ZLEN"/{LHS} {return ZLEN;} +"ZW"/{LHS} {return ZW;} + + /* Booleans */ + +"YES"|"yes" {yylval.boolval = 1; return BOOL; } +"NO"|"no" {yylval.boolval = 0; return BOOL; } + + /* Floats */ + + /* ordinary floating point numbers */ +{SIMPLE_FLOAT} {yylval.floatval = strtod(yytext, NULL); return FLOAT;} + + /* floating point numbers with suffix, e,g. pF, nH */ +{FLOAT_YOTTA} {yylval.floatval = strtod(yytext, NULL) * 1e24; return FLOAT;} +{FLOAT_ZETA} {yylval.floatval = strtod(yytext, NULL) * 1e21; return FLOAT;} +{FLOAT_EXA} {yylval.floatval = strtod(yytext, NULL) * 1e18; return FLOAT;} +{FLOAT_PETA} {yylval.floatval = strtod(yytext, NULL) * 1e15; return FLOAT;} +{FLOAT_TERA} {yylval.floatval = strtod(yytext, NULL) * 1e12; return FLOAT;} +{FLOAT_GIGA} {yylval.floatval = strtod(yytext, NULL) * 1e9; return FLOAT;} +{FLOAT_MEGA} {yylval.floatval = strtod(yytext, NULL) * 1e6; return FLOAT;} +{FLOAT_KILO} {yylval.floatval = strtod(yytext, NULL) * 1e3; return FLOAT;} +{FLOAT_MILLI} {yylval.floatval = strtod(yytext, NULL) * 1e-3; return FLOAT;} +{FLOAT_MICRO} {yylval.floatval = strtod(yytext, NULL) * 1e-6; return FLOAT;} +{FLOAT_NANO} {yylval.floatval = strtod(yytext, NULL) * 1e-9; return FLOAT;} +{FLOAT_PICO} {yylval.floatval = strtod(yytext, NULL) * 1e-12; return FLOAT;} +{FLOAT_FEMTO} {yylval.floatval = strtod(yytext, NULL) * 1e-15; return FLOAT;} +{FLOAT_ATTO} {yylval.floatval = strtod(yytext, NULL) * 1e-18; return FLOAT;} +{FLOAT_ZEPTO} {yylval.floatval = strtod(yytext, NULL) * 1e-21; return FLOAT;} +{FLOAT_YOCTO} {yylval.floatval = strtod(yytext, NULL) * 1e-24; return FLOAT;} + + /* floating point numbers in VERSION and PLANE_SEP have no suffix and are followed by optional comments */ +<STATE_FLOAT>{ +{SIMPLE_FLOAT} {yylval.floatval = strtod(yytext, NULL); BEGIN STATE_COMMENT; return FLOAT;} +} + + /* A positive integer is used only in polygon/polyline/polyvoid "ID = posint" */ +<STATE_POSINT>{ +{POSINT} { BEGIN INITIAL; yylval.intval = atoi(yytext); return POSINT; } +} + + + /* + * This is a workaround for syntactically incorrect .hyp files. + * We accept the following constructs as representing an empty string: + * NAME= L1=somelayer + * NAME= ) + * NAME= } + */ +<STATE_STRING>{ +([A-Z][A-Z1-2_]*{WS}*"="|")"|"}") { yyless(0); BEGIN INITIAL; yylval.strval = strdup(""); return STRING; } /* emit empty string and reprocess */ +} + +<*>{ + + "{" {return '{';} + + "}" {BEGIN STATE_COMMENT_EOL; return '}';} + + "(" {if (section == PADSTACK) BEGIN STATE_STRING; return '(';} + + /* allow for comment after the closing bracket ) */ + ")" {BEGIN STATE_COMMENT_EOL; return ')';} + + "," {return ',';} + + "=" {if ((section == VERSION) || (section == PLANE_SEP)) BEGIN STATE_FLOAT; return '=';} + + /* string */ + {STRING} { + /* + * Commas are not allowed in strings in the padstack section + * unless the string is enclosed in double quotes ("). + */ + + if ((section == PADSTACK) && strchr(yytext, ',')) + REJECT + else + { + BEGIN INITIAL; + yylval.strval = strdup(yytext); + return STRING; + } + } + + /* string in double quotes */ + {QUOTED_STRING} {BEGIN INITIAL; yylval.strval = strunquote(yytext); return STRING;} + + <<EOF>> {yyterminate();} + + /* have bison catch all unrecognized characters with parse errors */ + . {return yytext[0];} + +} + +%% + + /* + * copy a quoted string. + * e.g. "data 0" -> data 0 + * a double quote inside the string can be escaped by writing two consecutive double quotes + * e.g. "net ""hi""" -> net "hi" + */ + + char *strunquote(const char *src) + { + char* dst; + size_t len = strlen(src) + 1; + dst = (char *)malloc(len); + if (dst != NULL) + { + char* p = (char *)src + 1; /* first char after initial quote */ + char* q = dst; + do + if (*p == '"') p++; + while ((*q++ = *p++) != '\0'); + } + return dst; + } + + /* not truncated */ |