#usage "HPGL2SCR v0.1 imports HP Graphics Language files"
"
Creates one or more paths of wires in the specified layer. "
"A .scr file is created in the same folder as the .hpgl input. "
"This file may be used to repeat the import.
"
"A typical use would create a vector graphic in inkscape with a given dimension in mm. "
"Export as HPGL with 1016 DPI."
"By default the HPGL is scaled to 1/40 to give wire traces at the same mm scale as the original image.
"
"In normal use choose the Wire output type. When using polygon output, note that setting the pour type = cutout may be used after import for subtractive effects.
"
"The Scale to Fit Box setting will rescale the image (preserving aspect ratio) so that it fits within a box of the specified size (in mm).
"
"The imported image can be positioned in three ways
"
"1. Aligned to the x and y axes with all wires at positive coordinate values, i.e. in the correct position for a board outline.
"
"2. Centred at a chosen position
"
"3. Off-board (on the opposite side of the origin compared to option 1). This is the best place to group-select the shape and manually move it into place.
"
"4. Absolute positioning; (0,0) of the original graphic maps to (0,0) on the board. Generally avoid Scale to Fit, noting that it scales from (0,0) to (max x, max y) into the specified box."
" Absolute positioning may be useful if several layers are used in an image and each converted to separate HPGL files for separate importing."
"Beware that the origin for graphics software may be top left, whereas for boards it is bottom left.
"
//idea based on http://badcafe.co.uk/2011/03/20/dxf-hpgl-to-eagle-script-conversion/
//but realised as ULP
//Adam Cooper 2015. MIT Open Source Licence.
//user-controlled import parameters
real scaleDiv=40;//actual scale to use. may change according to dialog box selection.
real wireWidth=0.1;
string fileName;
//bounding box for auto-scale option
int doBound = 0;
real boundX=50;
real boundY=50;
//layer combo data
string layerNames[] = {"top", "bottom", "dimension (board)", "tPlace", "bPlace", "tNames", "bNames"};
int layerNumber[] = {1, 16, 20, 21, 22, 25, 26};
int layerSel = 2;
real defaultWidth[] = {1,1,0.1,0.5,0.5,0.5,0.5};
//radio button position selection and XY centre input
int radPos=0;
real centreX=25;
real centreY=25;
//radio button to select wire or polygon
int radType =0;
//*** dialog box for parameter and file selection
int Result = dlgDialog("Import HPGL") {
dlgHBoxLayout {
dlgLabel("All units are mm");
dlgStretch(1);
}
dlgSpacing(10);
dlgHBoxLayout {
dlgLabel("Layer");
dlgComboBox(layerNames, layerSel) if(radType == 0) wireWidth = defaultWidth[layerSel];
dlgStretch(1);
}
dlgGroup("Shape Type"){
dlgVBoxLayout {
dlgRadioButton("Wire (normal)", radType) wireWidth = defaultWidth[layerSel];
dlgRadioButton("Polygon", radType) wireWidth = 0.4;
}
}
dlgHBoxLayout {
dlgLabel("Width");
dlgRealEdit(wireWidth,0.1,10);
dlgLabel("(use 0.1 if layer = dimension)");
dlgStretch(1);
}
dlgSpacing(10);
dlgGroup("Import Position"){
dlgVBoxLayout {
dlgRadioButton("Align to Axes (board position)", radPos);
dlgHBoxLayout{
dlgRadioButton("Centre at", radPos);
dlgRealEdit(centreX, 0, 200);
dlgRealEdit(centreY, 0, 200);
}
dlgRadioButton("Off-board", radPos);
dlgRadioButton("Absolute", radPos);
}
}
dlgHBoxLayout {
dlgGroup("Scale to Fit Box (0,0) to (X,Y)") {
dlgCheckBox("&Fit", doBound);
dlgHBoxLayout {
dlgLabel("X");
dlgRealEdit(boundX, 4, 200);
dlgLabel(" Y");
dlgRealEdit(boundY, 4, 200);
}
}
}
dlgSpacing(10);
dlgHBoxLayout {
dlgLabel("File &name:");
dlgStringEdit(fileName);
dlgPushButton("Bro&wse") {
fileName = dlgFileOpen("Select a HPGL file", EAGLE_HOME, "*.hpgl");
}
}
dlgSpacing(10);
dlgHBoxLayout {
dlgStretch(1);
dlgPushButton("+OK") dlgAccept();
dlgPushButton("Cancel") dlgReject();
}
};
//exit quietly if cancelled dialog or failed to select a file
if(Result==0) exit(0);
if(fileName==""){
dlgMessageBox("No file chosen, cancelling","OK");
exit(0);
}
// **** read in the file, parse it, and calculate XY min and max for scaling and positioning
string hpgl[];
int nLines = fileread(hpgl, fileName);
//string messLines;
//sprintf(messLines, "Lines of HPGL=%d",nLines);
//dlgMessageBox(messLines,"OK");
//split HPGL into statements.
string hpglS[];
int S = strsplit(hpglS,hpgl[0],';');
//first two statments are "IN" and "SP1" (assuming pen 1)
//last two statements are "PU" and ""
string tmpPos[];
//check 3rd statement starts "PU" as expected
if(strstr(hpglS[2],"PU")!=0){
dlgMessageBox("HPGL file appears to be invalid, or is not simple enough");
exit(1);
}
strsplit(tmpPos,strsub(hpglS[2],2),',');
real lastX = strtod(tmpPos[0]);
real lastY = strtod(tmpPos[1]);
int penDown;
real X;
real Y;
//scan for min values - required for positioning and Scale to Fit Box
//also test for messed up HPGL
real minX=lastX;
real minY=lastY;
real maxX=lastX;
real maxY=lastY;
for(int i = 3; i<(S-2); i++){
if((strstr(hpglS[i],"PD")==0) || (strstr(hpglS[i],"PU")==0)){
strsplit(tmpPos,strsub(hpglS[i],2),',');
X = strtod(tmpPos[0]);
Y = strtod(tmpPos[1]);
if(XmaxX) maxX=X;
if(Y>maxY) maxY=Y;
}else{
dlgMessageBox("HPGL file appears to be invalid, or is not simple enough");
exit(1);
}
}
// **** offset and scaling calculated here
//if absolute positioning, the implied min values are zeros.
if(radPos==3){
minX=0;
minY=0;
}
//scaleDiv = 40 gives mm scaling assuming HPGL export ws 1016dpi. Factor 40 is assumed in several places in this section
if(doBound==1){
real rangeX=maxX-minX;//+wireWidth*40;
real rangeY=maxY-minY;//+wireWidth*40;
boundX = boundX-wireWidth;
boundY = boundY-wireWidth;
if((rangeX/boundX)>(rangeY/boundY)){
scaleDiv=rangeX/boundX;
}else{
scaleDiv=rangeY/boundY;
}
}else{
scaleDiv = 40;
}
//in all cases, the shape is effectively shifted so that (minX,minY)->(0,0) before scaling and additional offsetting
real offsetX=0;//in mm
real offsetY=0;
if(radPos == 0){
//align to axes in board position
offsetX=wireWidth/2;
offsetY=wireWidth/2;
}else if(radPos == 1){
//centre
offsetX = centreX-maxX/2/scaleDiv;
offsetY = centreY-maxY/2/scaleDiv;
}else if(radPos == 2){
//off-board
offsetX=-10-wireWidth/2+(minY-maxX)/scaleDiv;
offsetY=-10-wireWidth/2+(minY-maxY)/scaleDiv;
}//else radPos == 3 (i.e. absolute)
lastX=(lastX-minX)/scaleDiv+offsetX;
lastY=(lastY-minY)/scaleDiv+offsetY;
// **** create the wire generation script, save it and execute it
string scrFile=filesetext(fileName,".scr");
output(scrFile, "wt"){
printf("#File generated using hpgl2scr.ulp v0.1\n");
printf("grid mm\n");
printf("grid 0.1\n");
if(radType==0){
printf("set wire_bend 2\n");
}
printf("change width %f\n",wireWidth);
printf("layer %d\n",layerNumber[layerSel]);
if(radType==1){
printf("change isolate %f\n",wireWidth);
printf("poly (%f %f)", lastX, lastY);
}
for(int i = 3; i<(S-2); i++){
penDown = (strstr(hpglS[i],"PD")==0);
strsplit(tmpPos,strsub(hpglS[i],2),',');
X = (strtod(tmpPos[0])-minX)/scaleDiv+offsetX;
Y = (strtod(tmpPos[1])-minY)/scaleDiv+offsetY;
if(radType==0){
if(penDown){
printf("wire (%f %f) (%f %f)\n", lastX, lastY, X, Y);
}
}else{
if(penDown){
printf(" (%f %f)", X, Y);
}else{
printf("\npoly (%f %f)", X, Y);
}
}
lastX = X;
lastY = Y;
}
}
string execScript;
sprintf(execScript,"SCRIPT %s",scrFile);
exit (execScript);