#usage "<b>export 3d-data from board to IDF-FileFormat V3.0</b><p>"
       "More info can be found here: <a href=\"ftp://ftp.cadsoft.de/eagle/userfiles/ulp/generate_3d_data_eng.pdf\"> Manual </a><br>"
       "<author>Author: Neubacher Andy</author><br>"
       "<author>co-Author: alf@cadsoft.de</author>"

#require 6.0400
string UlpVersion = "V1.2";

//############################################################################
// Author: Andy Neubacher
// Version |     Date   |  log
//---------+------------+-----------------------------------------------------
// v1.3    | 2013-03-14 |  - correct using ARC coorinates
// v1.2    | 2013-01-14 |  - Link in description to download page  - fixes by alf@cadsoft.de
// v1.1    | 2012-12-04 |  - korrect Loop Label for contours in layer Dimension - fixes by alf@cadsoft.de
// v1.0    | 2012-08-20 |  - more resolution %.6f of coordinates   - fixes by alf@cadsoft.de
// v0.9    | 2012-02-08 |  - used function u2mm() for Z-axis value - fixes by alf@cadsoft.de
//         |            |
// v0.8    | 2011-05-31 |  - changed characters that caused import problems
//         |            |  - changed output file extension to .emn and .emp
//         |            |  - fixes by morten@riftlabs.com
//         |            |
// v0.7    | 2006-10-24 |  - outline of board can also be drawn direct in brd-file
//         |            |  - any hole is taken from devices and board direct
//         |            |  - close open polygons on request
//         |            |  - drill vias on request
//         |            |  - bugfix : error if device contains more than one poly on top and bottom
//         |            |  - bugfix : sorting of points possible failed if polygon was open
//         |            |  - bugfix : partheights on bottomlayer were ignored
// v0.6    | 2006-08-31 |  now multiple partheights are possible
// v0.5    | 2006-08-25 |  modification to 'RIR-standard'
// v0.4    | 2005-11-09 |  1st export of a real board+parts successfull
// v0.3    | 2005-11-03 |  correct mirrors at top and bottom side
// v0.2    | 2005-10-21 |  1st working IDF-data
// v0.1    | 2005-09-30 |  start of project
//---------+------------+-----------------------------------------------------
//
// HOWTO:
// Create your board outline on layer 50.
// In your library, draw the component outlines on layer 57 (tCad) and use the line thickness to set component height.
// 1000 mic = 1 mm (e.g. if your grid is in mm, then a line thickness of 0.001 equals a component height of 1 mm).
// More info can be found here: ftp://ftp.cadsoft.de/eagle/userfiles/ulp/generate_3d_data_eng.pdf
//
//############################################################################


int Layer3dBoardDimension = 50;
int tLayer3Ddata = 57;
int bLayer3Ddata = 58;
int maxCutoutLineWidth = 0;  // -->> is linewidth 0.0 -> line is a cutout
string UlpName = filename(argv[0]);	// gets ulp name
string HelpText =  "<b>1)</b> <p>3D use u2mm()</p>\n";

// partoptions
int  NumParts;            // number of parts
string PartName[];        // name of part


// vars for polygon calculation
int NumSegments = 0;      // number of points
int PointX1[];            // start x
int PointY1[];            // start y
int PointX2[];            // end x
int PointY2[];            // end y
int SegmentType[];        // ARC, LINE, CIRCLE
int SegmentCurve[];       // used by ARCs : - if drawn CCW,  + if drawn CW
int SegmentWidth[];       // linewitdh = partheight

int CutoutNumSegments = 0;
int CutoutPointX1[];         // start x
int CutoutPointY1[];         // start y
int CutoutPointX2[];         // end x
int CutoutPointY2[];         // end y
int CutoutSegmentType[];     // ARC, LINE, CIRCLE
int CutoutSegmentCurve[];    // used by ARCs : -180 if drawn CCW,  +180 if drawn CW
int CutoutSegmentWidth[];    // linewitdh = partheight

enum { LINE = 1, ARC = 2, CIRCLE = 3 };
enum { CUTOUT = 0, OUTLINE = -1 };
enum { false = 0, true = 1};


// vars for library
int NumPacInLib = 0;                  // number of pacs in library
int PacIn_LIB_heights[];              // if 0 = only one partheight, if != 0 more than one height
real PacIn_LIB_angle[];                // rotation of part in board
string PacIn_LIB_name[];              // names of pack's in library
int PacIn_LIB_Device_mountside[];      // TOP, BOTTOM
string PacIn_LIB_SubPart_mountside[];  // mountside of subparts


int NumCutouts = 0;                    // number of holes in the pcb (drills, millings)
int OutlineInDeviceFound = false;      // is outline drawn in device symbol

int  CloseOpenPoly = false;            // close polygon if gap is smaller than ...mm
real MaxGapWidth = 0.1;                // value of maximum gap to close

int  DrillViaHoles = true;            // drill via-holes in pcb
real MinViaHoleDia = 1;                // diameter of drill



////////////////////////////////////////////////////
void CollectPartData(UL_BOARD brd) {      //   read out all parts
  NumParts = 0;
  brd.elements(E) {
    PartName[NumParts] = E.name;
    PacIn_LIB_angle[NumParts] = -1;          // fill with invalid rotation and ...
    PacIn_LIB_Device_mountside[NumParts] = -1;      // fill with invalid mountside (mirror)   for "void IDF_LibaryElectrical(UL_BOARD BRD)" function
    PacIn_LIB_heights[NumParts] = 0;        // part is only one package
    NumParts++;
  }
}


////////////////////////////////////////////////////
void IDF_Circle(int x1, int y1, int x2, int y2, int type) {       // output circle to file in IDF format
  int x=0;
  if(type == CUTOUT)  x = NumCutouts;
  printf("%d %.6f %.6f 0\n", x, u2mm(x1), u2mm(y1));
  printf("%d %.6f %.6f 360\n", x, u2mm(x2), u2mm(y2));
  return;
}

////////////////////////////////////////////////////
void IDF_Arc(int x1, int y1, int x2, int y2, int angle, int type) {   // output arc to file in IDF format
  int x = 0;
  if(type == CUTOUT) x = NumCutouts;
  printf("%d %.6f %.6f 0\n", x, u2mm(x1), u2mm(y1));
  printf("%d %.6f %.6f %d\n", x, u2mm(x2), u2mm(y2), angle);
  return;
}

////////////////////////////////////////////////////
void IDF_Line(int x1, int y1, int x2, int y2, int type) {         // output line to file in IDF format
  int x = 0;
  if(type == CUTOUT) x = NumCutouts;
  printf("%d %.6f %.6f 0\n", x, u2mm(x1), u2mm(y1));
  printf("%d %.6f %.6f 0\n", x, u2mm(x2), u2mm(y2));
  return;
}

////////////////////////////////////////////////////
void OutputLines(int originx, int originy, int type) {       // write to file
  for(int i = 0; i < NumSegments; i++) {
    switch(SegmentType[i]) {
      case LINE   : IDF_Line(PointX1[i]-originx, PointY1[i]-originy, PointX2[i]-originx, PointY2[i]-originy, type);
                    break;
      case ARC    : IDF_Arc(PointX1[i]-originx, PointY1[i]-originy, PointX2[i]-originx, PointY2[i]-originy, SegmentCurve[i], type);
                    break;
      case CIRCLE : IDF_Circle(PointX1[i]-originx, PointY1[i]-originy, PointX2[i]-originx, PointY2[i]-originy, type);
                    break;
      default     : string s;
                    sprintf(s, "Unknown type in Segment %d", i);
                    if (dlgMessageBox(s, "OK", "esc") != 0) exit(-154);
    }
  }
  return;
}

////////////////////////////////////////////////////
int IsPointEqual (int x1, int y1, int x2, int y2) {             // really need a func-description ??
  if(x1 == x2 && y1 == y2) return true;
  return false;
}

////////////////////////////////////////////////////
real getPartHeight() {                     // returns maximum partlevel

  real ret = u2mm(SegmentWidth[0])*1000.0;  // 2012-02-08
  return ret;
}

////////////////////////////////////////////////////
int IsPointInCircle(int target_x, int target_y, int circle_x, int circle_y, real radius) {
  real dist,a,b;
  a = abs(target_y-circle_y);
  b = abs(target_x-circle_x);
  // calculate distance
  dist = sqrt(a*a + b*b);
  if(dist <= radius) return true;
  return false;
}

////////////////////////////////////////////////////
//int GetArcOptions(UL_WIRE w) {                             // is arc drawn CW or CCW
//  if(IsPointEqual(w.x1, w.y1, w.x2, w.y2))  return (w.arc.angle1 - w.arc.angle2);
//  else  return (w.arc.angle2 - w.arc.angle1);
//}

////////////////////////////////////////////////////
int DataOnLayerPresent(UL_ELEMENT E, int layer) {
  E.package.circles(CIR) { // circle found
    if(CIR.layer == layer && CIR.width > maxCutoutLineWidth)  return true;
  }
  E.package.wires(W) {
    if(W.arc) {         // arc found
      if(W.layer == layer && W.width > maxCutoutLineWidth) return true;
    }
    else {             // straight line found
      if(W.layer == layer && W.width > maxCutoutLineWidth) return true;
    }
  }
  return false;    // no 3d-data on given layer found
}

////////////////////////////////////////////////////
void CollectRemainingPoints(int start, int nr) {
  int i;
  for(i = 0; i < nr; i++) {
    PointX1[i] = PointX1[i+start];
    PointY1[i] = PointY1[i+start];
    PointX2[i] = PointX2[i+start];
    PointY2[i] = PointY2[i+start];
    SegmentType[i] = SegmentType[i+start];
    SegmentCurve[i] = SegmentCurve[i+start];
    SegmentWidth[i] = SegmentWidth[i+start];
  }
  NumSegments = nr;
  return;
}


////////////////////////////////////////////////////
int SortPointsEx (void) {     // sort all points to a continous polygon
  int i;
  int ptfound;
  int ResultPointX1[];        // start x
  int ResultPointY1[];        // start y
  int ResultPointX2[];        // end x
  int ResultPointY2[];        // end y
  int ResultSegmentType[];      // SegmentType
  int ResultSegmentCurve[];    // options of segment (only used for ARCs)
  int ResultSegmentWidth[];      // width of line
  int PointUsed[];
  enum { NOT_FOUND = 0, FOUND = 1, ERROR = 2 };
  enum { NOT_USED = 0, USED = 1 };

  for(i = 0; i < NumSegments; i++)  PointUsed[i] = NOT_USED;

  ResultPointX1[0] = PointX1[0];  // startpoint
  ResultPointY1[0] = PointY1[0];
  ResultPointX2[0] = PointX2[0];
  ResultPointY2[0] = PointY2[0];
  ResultSegmentType[0] = SegmentType[0];
  ResultSegmentWidth[0] = SegmentWidth[0];
  ResultSegmentCurve[0] = SegmentCurve[0];
  PointUsed[0] = USED;


  for(int x=1; x<NumSegments; x++) {
    i = 1;
    ptfound = NOT_FOUND;
    do {
      if(!PointUsed[i]) {
        // search left point of segment in list
        if( IsPointEqual(PointX1[i], PointY1[i], ResultPointX2[x-1], ResultPointY2[x-1]) ) { // x1,y1 = startpoint  |  x2,y2 = endpoint
          ptfound = FOUND;
          PointUsed[i] = USED;
          ResultPointX1[x] = PointX1[i];
          ResultPointY1[x] = PointY1[i];
          ResultPointX2[x] = PointX2[i];
          ResultPointY2[x] = PointY2[i];
          ResultSegmentType[x] = SegmentType[i];
          ResultSegmentWidth[x] = SegmentWidth[i];
          ResultSegmentCurve[x] = SegmentCurve[i];
        }

        // search right point of segment in list
        if( IsPointEqual(PointX2[i], PointY2[i], ResultPointX2[x-1], ResultPointY2[x-1]) ) { // x2,y2 = startpoint  |  x1,y1 = endpoint
          ptfound = FOUND;
          PointUsed[i] = USED;
          ResultPointX1[x] = PointX2[i];
          ResultPointY1[x] = PointY2[i];
          ResultPointX2[x] = PointX1[i];
          ResultPointY2[x] = PointY1[i];
          ResultSegmentType[x] = SegmentType[i];
          ResultSegmentWidth[x] = SegmentWidth[i];
          ResultSegmentCurve[x] = SegmentCurve[i] * (-1);    // swap also drawing direction of ARC (clockwise, counter-clockwise)
        }
      }  // point used


      // checked full array ?
      if(i++ >= (NumSegments-1)) {
        if(ptfound != FOUND) ptfound = ERROR;
      }
    } while(ptfound == NOT_FOUND);

    // if point was not found -> end sorting of points
    if (ptfound == ERROR) break;
  }  // for NumSegments

  // count how many points are not used
  int n;
  int NumPointsUsed = 0;
  for(i=0; i < NumSegments; i++)  {
    if(PointUsed[i] == USED) NumPointsUsed++;
  }

  // copy not used points to end of ResultPoint[]
  for(i = NumSegments-1; i >= NumPointsUsed; i--) {
    for(n = 0; n < NumSegments; n++) {
      if(PointUsed[n] == NOT_USED) { // copy if point is not used
        PointUsed[n] = USED;
        ResultPointX1[i] = PointX1[n];
        ResultPointY1[i] = PointY1[n];
        ResultPointX2[i] = PointX2[n];
        ResultPointY2[i] = PointY2[n];
        ResultSegmentCurve[i] = SegmentCurve[n];
        ResultSegmentType[i] = SegmentType[n];
        ResultSegmentWidth[i] = SegmentWidth[n];
        break;
      }
    }
  }

  // copy back sorted list
  for(i=0; i<NumSegments;  i++) {
    PointX1[i] = ResultPointX1[i];
    PointY1[i] = ResultPointY1[i];
    PointX2[i] = ResultPointX2[i];
    PointY2[i] = ResultPointY2[i];
    SegmentType[i] = ResultSegmentType[i];
    SegmentCurve[i] = ResultSegmentCurve[i];
    SegmentWidth[i] = ResultSegmentWidth[i];
  }
  return NumSegments-NumPointsUsed;    // return number of pending points
} // SortPointsEx


////////////////////////////////////////////////////
void CloseOpenPolygon (void) {
  int i, LineAdded;
  int RemainingPoints, LastPoint;

  do  {
    LineAdded = false;
    RemainingPoints = SortPointsEx();
    LastPoint = NumSegments - RemainingPoints - 1;

    if(RemainingPoints) {   // only search for gaps if remainingpoints != 0
      for(i=LastPoint+1; i<NumSegments; i++) {   // start from last+1 poly-segment -> search for points within circle
        if(IsPointInCircle(PointX1[i], PointY1[i], PointX2[LastPoint], PointY2[LastPoint], mm2u(MaxGapWidth))) { // 2012-02-08
          PointX1[NumSegments] = PointX2[LastPoint];  // add new line with start at last-point
          PointY1[NumSegments] = PointY2[LastPoint];
          PointX2[NumSegments] = PointX1[i];
          PointY2[NumSegments] = PointY1[i];
          SegmentType[NumSegments] = LINE;
          SegmentWidth[NumSegments] = SegmentWidth[LastPoint];
          SegmentCurve[++NumSegments] = 0;
          LineAdded = true;
          break;    // point within circle found and added -> escape from "for"-loop
        }
        if(IsPointInCircle(PointX2[i], PointY2[i], PointX2[LastPoint], PointY2[LastPoint], mm2u(MaxGapWidth))) { // 2012-02-08
          PointX1[NumSegments] = PointX2[LastPoint];  // add new line with start at last-point
          PointY1[NumSegments] = PointY2[LastPoint];
          PointX2[NumSegments] = PointX2[i];
          PointY2[NumSegments] = PointY2[i];
          SegmentType[NumSegments] = LINE;
          SegmentWidth[NumSegments] = SegmentWidth[LastPoint];
          SegmentCurve[++NumSegments] = 0;
          LineAdded = true;
          break;    // point within circle found and added -> escape from "for"-loop
        }
      }
    }
  } while(LineAdded);    // resort points and close gaps  if new line added

  // finished correct -> but is polygon closed ?!? (firstpoint and lastpoint may no be the same coordinate !)
  if(IsPointEqual(PointX1[0], PointY1[0], PointX2[LastPoint], PointY2[LastPoint]) == false) { // poly closed ?
    if(IsPointInCircle(PointX1[0], PointY1[0], PointX2[LastPoint], PointY2[LastPoint], mm2u(MaxGapWidth))) { // 2012-02-08
      PointX1[NumSegments] = PointX2[LastPoint];  // add new line with start at last-point
      PointY1[NumSegments] = PointY2[LastPoint];
      PointX2[NumSegments] = PointX1[0];
      PointY2[NumSegments] = PointY1[0];
      SegmentType[NumSegments] = LINE;
      SegmentWidth[NumSegments] = SegmentWidth[LastPoint];
      SegmentCurve[++NumSegments] = 0;
    }
  }
  return;
}


////////////////////////////////////////////////////
int SortPoints(string ElementName) {     // sort all points to a continous polygon
  int LastPoint, RemainingPoints;
  // close open polygon
  if (CloseOpenPoly) CloseOpenPolygon();
  // do final sorting
  RemainingPoints = SortPointsEx();
  // check if polygon is closed
  LastPoint = NumSegments - RemainingPoints - 1;
  if (LastPoint < 0) {
    dlgMessageBox("!Negativ Index in function SortPoints().", "OK");
    exit(-401);
  }
  if(IsPointEqual(PointX1[0], PointY1[0], PointX2[LastPoint], PointY2[LastPoint]) == false) {
    if(SegmentType[0] != CIRCLE) {
      string x;
      int pt_x, pt_y;
      pt_x = PointX2[LastPoint];
      pt_y = PointY2[LastPoint];
      sprintf(x,"!%s contains an open polygon !\n\ncoordinate : X= %.6f[mm], Y= %.6f[mm]", ElementName, u2mm(pt_x), u2mm(pt_y));
      if (dlgMessageBox(x, "OK", "Show") != 0) {
        sprintf(x, "WIN (%.6fmm %.6fmm);\n", u2mm(pt_x), u2mm(pt_y));
        exit(x);
      }
    }
  }
  return RemainingPoints;
}


////////////////////////////////////////////////////
void IDF_LibaryHeader(UL_BOARD BRD) {              //->   create header of libary
  int t = time();
  string date;
  string sourceID;
  sprintf(date, "%d/%02d/%02d.%02d:%02d:%02d", t2year(t), t2month(t), t2day(t), t2hour(t), t2minute(t), t2second(t));
  sprintf(sourceID, "Commend International >%s.ulp %s %s<", UlpName, UlpVersion, EAGLE_SIGNATURE); // 2013-01-14
  printf(".HEADER\n");
  printf("LIBRARY_FILE 3.0 \"%s\" %s 1\n", sourceID, date);
  printf(".END_HEADER\n");
  return;
}


////////////////////////////////////////////////////
int CollectLibOutlineSegments (UL_ELEMENT E, int layer) {           // get all segments of elements on 3D-layer
  NumSegments=0;
  E.package.circles(CIR) {     // create circles
    if(CIR.layer == layer && CIR.width > maxCutoutLineWidth) {
      PointX1[NumSegments] = CIR.x;
      PointY1[NumSegments] = CIR.y;
      PointX2[NumSegments] = CIR.x + CIR.radius;
      PointY2[NumSegments] = CIR.y;
      SegmentType[NumSegments] = CIRCLE;
      SegmentWidth[NumSegments] = CIR.width;
      SegmentCurve[++NumSegments] = 0;
    }
  }

  E.package.wires(W) {
    if(W.curve) {       // create arcs
      if(W.layer == layer && W.width > maxCutoutLineWidth) {
        PointX1[NumSegments] = W.x1;  // 2013-03-14 use wire coordinate, not arc!
        PointY1[NumSegments] = W.y1;
        PointX2[NumSegments] = W.x2;
        PointY2[NumSegments] = W.y2;
        SegmentType[NumSegments] = ARC;
        SegmentWidth[NumSegments] = W.width;
        SegmentCurve[++NumSegments] = W.curve; //GetArcOptions(W);
      }
    }
    else {             // create straight lines
      if(W.layer == layer && W.width > maxCutoutLineWidth) {
        PointX1[NumSegments] = W.x1;
        PointY1[NumSegments] = W.y1;
        PointX2[NumSegments] = W.x2;
        PointY2[NumSegments] = W.y2;
        SegmentType[NumSegments] = LINE;
        SegmentWidth[NumSegments] = W.width;
        SegmentCurve[++NumSegments] = 0;
      }
    }
  }


  if(NumSegments != 0) {     // show warningbox if element contains no 3d-data
    if(layer == bLayer3Ddata) {      // is given layer the bottom layer -> mirror
      for(int i=0; i<NumSegments; i++) {    // swap x-coordinate for bottom parts
        PointX1[i] = E.x - ((E.x - PointX1[i])*(-1));
        PointX2[i] = E.x - ((E.x - PointX2[i])*(-1));
      }
    }
  }
  return NumSegments;
}


////////////////////////////////////////////////////
void IDF_LibaryElectrical(UL_BOARD BRD) {             //->   create electrical parts (R, C, IC, ...)
  int i,x,inlib,NrOfPolygon;
  int RemainingPoints;
  string MountSide[] = {"TOP","BOTTOM"};
  int layer[] = {tLayer3Ddata, bLayer3Ddata};
  for(i=0;i<NumParts;i++) {
    BRD.elements(E) {
      inlib = 0;
      for(x=0; x<NumPacInLib; x++) {   // check if new package
        if(PacIn_LIB_name[x] == E.package.name &&
           PacIn_LIB_angle[x] == E.angle &&
           PacIn_LIB_Device_mountside[x] == E.mirror
          ) inlib = 1;
      }

      if(E.name == PartName[i] && inlib == 0) {   // is package still in LIB-file
        NrOfPolygon = 0;
        if(DataOnLayerPresent(E, bLayer3Ddata))   NrOfPolygon=1; // if poly on bot-side found start from "1"
        for(int b=0;b<2;b++){
          if(CollectLibOutlineSegments(E, layer[b])) { // only add package to lib if line-segments in layer found
            do {
              RemainingPoints = SortPoints(E.name);  // returns number of open/not_used points
              NumSegments -= RemainingPoints;        // calculate nr of correct/used points in poly

              // more than one outline in device present ?
              if((NrOfPolygon == 0) && (RemainingPoints != 0)) NrOfPolygon=1;

              printf(".ELECTRICAL\n");
              printf("%s_ANGLE%.1f_%s.%d CI_LIB MM %.6f\n", E.package.name, E.angle, MountSide[b], NrOfPolygon, getPartHeight());
              OutputLines(E.x, E.y, OUTLINE);      // add package to libfile -> subract given offsets ->  E.x, E.y
              printf(".END_ELECTRICAL\n");

              CollectRemainingPoints(NumSegments, RemainingPoints);

              PacIn_LIB_heights[NumPacInLib] = NrOfPolygon;              // 0=package has only 1 poly; >0 package has more than one poly-outline
              PacIn_LIB_name[NumPacInLib] = E.package.name;              // mark that package is now in LIB-file
              PacIn_LIB_Device_mountside[NumPacInLib] = E.mirror;        // safe top- or bottom-side
              PacIn_LIB_SubPart_mountside[NumPacInLib] = MountSide[b];  // safe mountside of subparts
              PacIn_LIB_angle[NumPacInLib++] = E.angle;                  // safe angle of partplacement

              if(RemainingPoints != 0 || NrOfPolygon != 0)  NrOfPolygon++;

            } while(RemainingPoints != 0);    // get multiple outline with multiple partheights
          }
        }
      }
    }
  }
  return;
}



////////////////////////////////////////////////////
void IDF_LibaryMechanical(UL_BOARD BRD) {             //->   create mechanical parts (drills, holes, ...)
  // not implemented yet
  return;
}


////////////////////////////////////////////////////
int CollectBoardOutlineSegments(UL_BOARD BRD) {             //++   get all segments of boardoutline
  NumSegments=0;
  BRD.elements(E) {
    E.package.circles(CIR) {       // create board-outline of package-libary
      if(CIR.layer == Layer3dBoardDimension && CIR.width > maxCutoutLineWidth) {
        PointX1[NumSegments] = CIR.x;
        PointY1[NumSegments] = CIR.y;
        PointX2[NumSegments] = CIR.x + CIR.radius;
        PointY2[NumSegments] = CIR.y;
        SegmentType[NumSegments] = CIRCLE;
        SegmentWidth[NumSegments] = CIR.width;
        SegmentCurve[++NumSegments] = 0;
      }
    }
  }
  BRD.elements(E) {
    E.package.wires(W) {
      if(W.curve) {         // create arcs direct from board
        if(W.layer == Layer3dBoardDimension && W.width > maxCutoutLineWidth) {
          PointX1[NumSegments] = W.x1; // 2013-03-14 use direct wire coordinate, not arc-coordinate
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = ARC;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = W.curve; // GetArcOptions(W);
        }
      }
      else {             // create straight lines direct from board
        if(W.layer == Layer3dBoardDimension && W.width > maxCutoutLineWidth) {
          PointX1[NumSegments] = W.x1;
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = LINE;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = 0;
        }
      }
    }
  }

  if(NumSegments != 0) {
    OutlineInDeviceFound = true;
    return NumSegments;      // outline was found in a device -> return
  }

  // outline of board was not found in any device -> check if drawn in boardfile direct
  BRD.circles(CIR) {        // create board-outline of package-libary
    if(CIR.layer == Layer3dBoardDimension) {
      if (CIR.width > maxCutoutLineWidth) {
        PointX1[NumSegments] = CIR.x;
        PointY1[NumSegments] = CIR.y;
        PointX2[NumSegments] = CIR.x + CIR.radius;
        PointY2[NumSegments] = CIR.y;
        SegmentType[NumSegments] = CIRCLE;
        SegmentWidth[NumSegments] = CIR.width;
        SegmentCurve[++NumSegments] = 0;
      }
      else {
        CutoutPointX1[CutoutNumSegments] = CIR.x;
        CutoutPointY1[CutoutNumSegments] = CIR.y;
        CutoutPointX2[CutoutNumSegments] = CIR.x + CIR.radius;
        CutoutPointY2[CutoutNumSegments] = CIR.y;
        CutoutSegmentType[CutoutNumSegments]   = CIRCLE;
        CutoutSegmentWidth[CutoutNumSegments]  = CIR.width;
        CutoutSegmentCurve[++CutoutNumSegments] = 0;
      }
    }
  }
  BRD.wires(W) {
    if(W.curve) {         // create arcs direct from board
      if(W.layer == Layer3dBoardDimension) {
        if (W.width > maxCutoutLineWidth) {
          PointX1[NumSegments] = W.x1;  // 2013-03-14 use direct wire coordinate, not arc-coordinate!
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = ARC;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = W.curve; // GetArcOptions(W);
        }
        else {
          CutoutPointX1[CutoutNumSegments] = W.x1;
          CutoutPointY1[CutoutNumSegments] = W.y1;
          CutoutPointX2[CutoutNumSegments] = W.x2;
          CutoutPointY2[CutoutNumSegments] = W.y2;
          CutoutSegmentType[CutoutNumSegments] = ARC;
          CutoutSegmentWidth[CutoutNumSegments] = W.width;
          CutoutSegmentCurve[++CutoutNumSegments] = W.curve; //GetArcOptions(W);
        }
      }
    }
    else {             // create straight lines direct from board
      if(W.layer == Layer3dBoardDimension) {
        if (W.width > maxCutoutLineWidth) {
          PointX1[NumSegments] = W.x1;
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = LINE;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = 0;
        }
        else {
          CutoutPointX1[CutoutNumSegments] = W.x1;
          CutoutPointY1[CutoutNumSegments] = W.y1;
          CutoutPointX2[CutoutNumSegments] = W.x2;
          CutoutPointY2[CutoutNumSegments] = W.y2;
          CutoutSegmentType[CutoutNumSegments] = LINE;
          CutoutSegmentWidth[CutoutNumSegments] = W.width;
          CutoutSegmentCurve[++CutoutNumSegments] = 0;
        }
      }
    }
  }
  if (CutoutNumSegments) {
    string s;
    sprintf(s, "%d cutout segmente found", CutoutNumSegments);
    if (dlgMessageBox(s, "ok", "esc") != 0) exit(-660);
  }
  if(NumSegments == 0) {
    string error;
    sprintf(error,"!No board-outline on layer %d found !", Layer3dBoardDimension);
    dlgMessageBox(error);
    exit(-639);
  }
  return NumSegments;
}



////////////////////////////////////////////////////
int CollectBoardCutoutSegments(UL_ELEMENT E, int layer, int type) {
  NumSegments=0;

  if(type == CIRCLE) {
    E.package.circles(CIR) {       // create circles direct from board
      if(CIR.layer == layer && CIR.width < (maxCutoutLineWidth+1)) {
        PointX1[NumSegments] = CIR.x;
        PointY1[NumSegments] = CIR.y;
        PointX2[NumSegments] = CIR.x + CIR.radius;
        PointY2[NumSegments] = CIR.y;
        SegmentType[NumSegments] = CIRCLE;
        SegmentWidth[NumSegments] = CIR.width;
        SegmentCurve[++NumSegments] = 0;
      }
    }
  }
  else {
    E.package.wires(W) {
      if(W.arc) {         // create arcs direct from board
        if(W.layer == layer && W.width < (maxCutoutLineWidth+1)) {
          PointX1[NumSegments] = W.x1;  // 2013-03-14 use wire coordinate, not arc!
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = ARC;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = W.curve; // GetArcOptions(W);
        }
      }
      else {             // create straight lines direct from board
        if(W.layer == layer && W.width < (maxCutoutLineWidth+1)) {
          PointX1[NumSegments] = W.x1;
          PointY1[NumSegments] = W.y1;
          PointX2[NumSegments] = W.x2;
          PointY2[NumSegments] = W.y2;
          SegmentType[NumSegments] = LINE;
          SegmentWidth[NumSegments] = W.width;
          SegmentCurve[++NumSegments] = 0;
        }
      }
    }
  }
  return NumSegments;
}


////////////////////////////////////////////////////
void DoCutoutsFromElements(UL_BOARD BRD) {
  int ret, RemainingPoints;

  // get cutouts from elements (holes, ...)
  BRD.elements(E) {
    // get cutout segments (LINEs and ARCs)
    ret = CollectBoardCutoutSegments(E, Layer3dBoardDimension, LINE);
    if(ret) {
      do {
        NumCutouts++;                                  // increment board cutouts
        RemainingPoints = SortPoints(E.name);          // elementname
        NumSegments -= RemainingPoints;                // calculate nr of correct points of poly
        OutputLines(0, 0, CUTOUT);                    // subract given offsets ->  E.x, E.y

        if(RemainingPoints) CollectRemainingPoints(NumSegments, RemainingPoints);
      }while(RemainingPoints != 0);
    }

    // get cutout holes (CIRCLEs)
    ret = CollectBoardCutoutSegments(E, Layer3dBoardDimension, CIRCLE);
    if(ret) {
      for(int i=0;i<ret;i++) {
        NumCutouts++;
        IDF_Circle(PointX1[i], PointY1[i], PointX2[i], PointY2[i], CUTOUT);
      }
    }

    // get cutouts from 3d-toplayer (for each device)
    if(CollectBoardCutoutSegments(E, tLayer3Ddata, LINE)) {
      NumCutouts++;                // increment board cutouts
      SortPoints(E.name);          // elementname
      OutputLines(0, 0, CUTOUT);  // subract given offsets ->  E.x, E.y
    }

    // get cutouts from 3d-bottomlayer (for each device)
    if(CollectBoardCutoutSegments(E, bLayer3Ddata, LINE)) {
      NumCutouts++;                // increment board cutouts
      SortPoints(E.name);          // elementname
      OutputLines(0, 0, CUTOUT);  // subract given offsets ->  E.x, E.y
    }
  } //BRD.elements(E)
  return;
}



////////////////////////////////////////////////////
void DoCutoutsFromBoardDirect(UL_BOARD BRD) {
  // drill circles if linewidth = 0
  BRD.circles(CIR) {       // create circles direct from board
    if(CIR.layer == Layer3dBoardDimension && CIR.width < (maxCutoutLineWidth+1)) {
      NumCutouts++;
      IDF_Circle(CIR.x, CIR.y, CIR.x+CIR.radius, CIR.y, CUTOUT);
    }
  }
  return;
}



////////////////////////////////////////////////////
void IDF_BoardHeader(UL_BOARD BRD) {                 //-##   create header of boardfile
  int t = time();
  string date;
  string sourceID;

  sprintf(date, "%d/%02d/%02d.%02d:%02d:%02d", t2year(t), t2month(t), t2day(t), t2hour(t), t2minute(t), t2second(t));
  sprintf(sourceID, "Commend International >%s.ulp %s %s<", UlpName, UlpVersion, EAGLE_SIGNATURE); // 2013-01-14

  printf(".HEADER\n");
  printf("BOARD_FILE 3.0 \"%s\" %s 1\n", sourceID, date);
  printf("\"%s\" MM\n", filename(BRD.name));
  printf(".END_HEADER\n");
  return;
}



////////////////////////////////////////////////////
void IDF_BoardOutline(UL_BOARD BRD) {               //-##   create outline of board
  // get outline
  CollectBoardOutlineSegments(BRD);
  SortPoints("Board-Outline");                  // boardname

  // draw outline
  printf(".BOARD_OUTLINE UNOWNED\n");
  printf("%.6f\n", getPartHeight());
  OutputLines(0, 0, OUTLINE);    // subract given offsets ->  E.x, E.y

  // draw cutouts
  DoCutoutsFromElements(BRD);    // get cutouts from devices -> board is a device

  if(OutlineInDeviceFound)      // get cutouts from board direct -> outline direct drawn in board
    DoCutoutsFromBoardDirect(BRD);

  printf(".END_BOARD_OUTLINE\n");
  return;
}


////////////////////////////////////////////////////
void IDF_BoardPlaceParts(UL_BOARD BRD) {         //-##   mount parts on board
  int i, inlib;
  string MountSide[] = {"TOP","BOTTOM"};

  printf(".PLACEMENT\n");

  BRD.elements(E) {
    inlib = 0;
    for(i=0; i<NumPacInLib; i++) {   // check if elements was found in libary
      if(PacIn_LIB_name[i] == E.package.name && PacIn_LIB_Device_mountside[i] == E.mirror && PacIn_LIB_angle[i] == E.angle) {
        inlib = 1;
        break;
      }
    }

    if(inlib) {   // only add part if part is in lib-file
      if(PacIn_LIB_heights[i] == 0) {     // part has only one height
        printf("%s_ANGLE%.1f_%s.0 CI_LIB %s.0\n", E.package.name, E.angle, PacIn_LIB_SubPart_mountside[i], E.name);
        printf("%.6f %.6f 0 0 %s PLACED\n", u2mm(E.x), u2mm(E.y), MountSide[E.mirror]);
      }
      else {       // part has multiple heights -> more than one part on same x/y position
        int lib_start = i;
        do {
          printf("%s_ANGLE%.1f_%s.%d CI_LIB %s.%d\n", E.package.name, E.angle, PacIn_LIB_SubPart_mountside[lib_start], PacIn_LIB_heights[lib_start], E.name, PacIn_LIB_heights[lib_start]);
          printf("%.6f %.6f 0 0 %s PLACED\n", u2mm(E.x), u2mm(E.y), PacIn_LIB_SubPart_mountside[lib_start]);
          lib_start++;
        }while(PacIn_LIB_heights[lib_start] > 1);
      }
    }
  }
  printf(".END_PLACEMENT\n");
  return;
}


////////////////////////////////////////////////////
void IDF_BoardHoles(UL_BOARD BRD) {             //-##   create holes in board (cutout, partholes, ...)
  printf(".DRILLED_HOLES\n");

  // drill holes from board direct
  BRD.holes(H)
    printf("%.6f %.6f %.6f NPTH BOARD Other UNOWNED\n", u2mm(H.drill), u2mm(H.x), u2mm(H.y));

  // drill holes from elements
  BRD.elements(E) {
    E.package.holes(H)
      printf("%.6f %.6f %.6f NPTH BOARD Other UNOWNED\n", u2mm(H.drill), u2mm(H.x), u2mm(H.y));
  }

  // drill big via's
  BRD.signals(S) {
    S.vias(V) {
      if((DrillViaHoles == true) && (u2mm(V.drill) > MinViaHoleDia))    // drill via-hole
        printf("%.6f %.6f %.6f PTH BOARD VIA UNOWNED\n", u2mm(V.drill), u2mm(V.x), u2mm(V.y));
    }
  }
  printf(".END_DRILLED_HOLES\n");
  return;
}


////////////////////////////////////////////////////
void IDF_CreateLibaryFile(UL_BOARD BRD, string fname) { //   create libary for the IDF-board
  output(fname, "wt") {
    IDF_LibaryHeader(BRD);
    IDF_LibaryElectrical(BRD);
    IDF_LibaryMechanical(BRD);
  }
  return;
}


////////////////////////////////////////////////////
void IDF_CreateBoardFile(UL_BOARD BRD, string fname) {    //   create boardfile with parts of libary
  output(fname, "wt") {
    IDF_BoardHeader(BRD);
    IDF_BoardOutline(BRD);
    IDF_BoardHoles(BRD);
    IDF_BoardPlaceParts(BRD);
  }
  return;
}

////////////////////////////////////////////////////
//void IDF_CreatePanelFile(UL_BOARD BRD, string fname) {   //   create panel, containing board and parts of libary
//  // not yet implemented
//  return;
//}


////////////////////////////////////////////////////
void DisplayHelp(void) {                       //    show helptext
  dlgDialog("generation of different variants - Help")  {
    dlgHBoxLayout dlgSpacing(500);
    dlgHBoxLayout {
      dlgVBoxLayout dlgSpacing(200);
      dlgTextView(HelpText);
    }
    dlgHBoxLayout {
      dlgStretch(1);
      dlgPushButton("-Close") dlgReject();
    }
  };
  return;
}


////////////////////////////////////////////////////
//   S T A R T   of   U L P
//
if (board) {
  int proceed = 0;
  string FilenameLibary;
  string FilenameBoard;
  string FilenamePanel;

  board(BRD) {
    FilenameBoard = filesetext(BRD.name, ".emn");
    FilenameLibary = filesetext(BRD.name, ".emp");
  }

  dlgDialog("Generate 3D data format (IDF-file)") {
    dlgHBoxLayout {
      dlgGroup(" settings ") {
        dlgGridLayout {
          dlgCell(0,0) dlgCheckBox("close polygon if gap is smaller than   : ", CloseOpenPoly);
          dlgCell(0,1) dlgRealEdit(MaxGapWidth);
          dlgCell(0,2) dlgLabel("  mm                               ");

          dlgCell(1,0) dlgCheckBox("drill via-holes if via is bigger than  : ", DrillViaHoles);
          dlgCell(1,1) dlgRealEdit(MinViaHoleDia);
          dlgCell(1,2) dlgLabel("  mm                               ");
        }
      }
    }
    dlgHBoxLayout {
      dlgLabel("enter output-filename : ");
      dlgStringEdit(FilenameBoard);
      dlgPushButton("Browse") {
        string FileName = dlgFileSave("Save IDF file", FilenameBoard, "IDF files (*.emn)");
        if (FileName) {
          if(strchr(FileName, '.') < 0)    // add fileextension if missing
            FileName += ".emn";

          FilenameLibary = filesetext(FileName, ".emp");
          FilenameBoard = FileName;
        }
      }
    }
    dlgHBoxLayout {
      dlgPushButton("OK") { proceed = 1; dlgAccept(); }
      dlgPushButton("Help") DisplayHelp();
      dlgPushButton("Cancel") dlgReject();
    }
  };

  if(proceed)  {
    project.board(BRD) {
      CollectPartData(BRD);    // read out data from BRD

      IDF_CreateLibaryFile(BRD, FilenameLibary);
      IDF_CreateBoardFile(BRD, FilenameBoard);
      //IDF_CreatePanelFile(BRD, FilenamePanel);
    }
  }
}

else {
  dlgMessageBox(usage + "<hr><b>ERROR: No board!</b><p>\nThis program can only work in the layout editor.");
  exit(0);
}
