/****************************************************************************************
*                                                                                       *
*   Designer Info Drawing for boards                                                    *
*                                                                                       *
*   Filename: DrawDesignerInfo.ulp                                                      *
*   Version: 1.3                                                                        *
*   Author: Tennessee Carmel-Veilleux <veilleux@ameth.org>                              *
*   Date: January 18th 2007                                                             *
*   Company: Entreprises TenTech                                                        *
*                                                                                       *                                                                                       *
*   This ULP program generates a script to draw the designer information on the board.  *
*   This information is often required by board manufacturers. The project name and     *
*   contact info are available to write. The layer name is automatically added for      *
*   every layer selected in the output list.                                            *
*                                                                                       *
*   Some parts of this ULP are based on other ULPs:                                     *
*      * drilegend.ulp by Christian Bohrer for the config loading/saving ideas          *
*      * bom-bio3.ulp by Robert A. Rioja for the Add/Remove from list ideas             *
*      * adimv4_0.ulp by Michel Dagenais and Jeff Moore for extents-finding ideas       *
****************************************************************************************/
#usage "<b>Designer Info Drawing for boards v1.3</b><p></p>\n"
       "<p>This ULP generates a script to draw the designer information and layer names for board manufacturers.</p>\n"
       "<author>Author: Tennessee Carmel-Veilleux (veilleux@tentech.ca)</author>"
#require 4.13

string VERSION = "1.3";
int EV = EAGLE_VERSION;
int ER = EAGLE_RELEASE;

real x_coord, y_coord, m_x_coord; // Coordinates for drawing
real x_text_offset = 0.05; // Distance of dimension text from closest edge (x-axis)
real y_text_offset = 0.07; // Distance of dimension text from closest edge (y-axis)
real text_size = 0.05;
int text_ratio = 7;
real text_spacing; // Spacing between text lines

int right_limit = 0.0;
int top_limit = 0.0;
int left_limit = 0.0;
int bottom_limit = 0.0;

string version_number; // File version number
string file_name;
string designer_string = "John Doe";
string project_string = "xxxx REV A";
string contact_string = "514-555-1234 / email@myadress.org";
string lines[];

int NUM_LAYERS = 11;

// Names for layers
string layer_names[] = {
    "Top Copper (*.top)",
    "Inner 1 (*.ly2)",
    "Inner 2 (*.ly3)",
    "Bottom Copper (*.bot)",
    "Board Dimension Outline (*.bro)",
    "Board Milling Outline (*.bro)",
    "Silk Screen Top (*.sst)",
    "Silk Screen Bottom (*.ssb)",
    "Solder Mask Top (*.smt)",
    "Solder Mask Bottom (*.smb)",
    "Drill Drawing and Dimensions (*.drd)"
};

// Mappings of layer numbers from layer list
int layer_numbers[] = {1, 2, 15, 16, 20, 46, 21, 22, 29, 30, 47};

int out_size = 0; // Size of out_string_list
int layers_list[]; // List of output layer numbers
numeric string out_string_list[]; // String names of output layers for output ListView
int shown_list[] = {1, 1, 1, 1}; // Text lines shown (4 items)

enum { PlacementTOP, PlacementRIGHT };

int opt_placement = PlacementTOP; // Placement of designer info text (0 = Top, 1 = Right)
int opt_mirrored_text = 0;// If opt_mirrored_text == 1: text will be right-to-left on bottom layers
int opt_show_contact = 1; // If opt_show_contact == 1: contact info will be shown
int opt_show_designer = 1; // If opt_show_designer == 1: designer name will be shown
int opt_run_script = 1; // If opt_run_script == 1: run the script after it is generated

string user_cfg_file = "designerinfo.cfg"; // Config file filename
string project_path, user_settings[]; // Paths and settings for config
int cfg_file_exists = 0; // 0 if file does not exist, 1 if it exists (after ProcessConfig())
string cfg_path; // Config filename path
string cfg[]; // File list for fileglob
int n_lines; // Number of lines in config file

/* ------------ Utility routines ------------- */

//
// Returns 1 if layer number is in output layer list. Else returns 0
//

int InLayerList(int layer) {
    int i;

    for (i = 0; i < out_size; i++) {
        if (layer == layers_list[i] || layer == 20) return 1;
    }

    return 0;
}

//
// Update the maximum extents from a wire
//

void UpdateMaxFromWire(UL_WIRE W) {
    if (!InLayerList(W.layer)) return;

    switch (W.y2 - W.y1)
    {
        case 0:
        {
            if (W.y2 > top_limit)
                top_limit = W.y2;
            else
            {
                if (W.y2 < bottom_limit)
                    bottom_limit = W.y2;
            }
        }
        default:
        {
            if (W.y2 > W.y1)
            {
                if (W.y2 > top_limit)
                    top_limit = W.y2;
                if (W.y1 < bottom_limit)
                    bottom_limit = W.y1;
            }
            else
            {
                if (W.y1 > top_limit)
                    top_limit = W.y1;
                if (W.y2 < bottom_limit)
                    bottom_limit = W.y2;
            }
        }
    }
    switch (W.x2 - W.x1)
    {
        case 0:
        {
            if (W.x2 > right_limit)
                right_limit = W.x2;
            else
            {
                if (W.x2 < left_limit)
                    left_limit = W.x2;
            }
        }
        default:
        {
            if (W.x2 > W.x1)
            {
                if (W.x2 > right_limit)
                    right_limit = W.x2;
                if (W.x1 < left_limit)
                    left_limit = W.x1;
            }
            else
            {
                if (W.x1 > right_limit)
                    right_limit = W.x1;
                if (W.x2 < left_limit)
                    left_limit = W.x2;
            }
        }
    }
}

//
// Gathers the extents of the board and all elements that are already drawn
//

void GetExtents(void) {
    board(B)
    {
        B.wires(W) {
            UpdateMaxFromWire(W);
        }
        B.texts(T) {
            T.wires(W) {
                UpdateMaxFromWire(W);
            }
        }
    }
}

//
// Return unit size of a string.
// Based on the following formulas:
// * Thickness = (ratio/100) * Size
// * Width = (2/3) * Size + (1/3) * Thickness
// * Spacing = (3/8) * Width - (1/3) * Thickness
//
real GetStringSize(string s, real size, int ratio) {
    real thickness, width, spacing;
    
    thickness = real(ratio) * 0.01 * size;
    width = ((2.0 / 3.0) * size) + ((1.0 / 3.0) * thickness);
    spacing = ((3.0 / 8.0) * width) - ((1.0 / 3.0) * thickness);

    return (width * real(strlen(s))) + (spacing * real((strlen(s) - 1)));
}

//
// Returns the index number of the named layer
//

int GetIndex(string s) {
    int i;
    int index = -1;

    for(i = 0; i < NUM_LAYERS; i++) {
        if (s == layer_names[i]) {
            index = i;
            break;
        }
    }
 
    return index;
}
      
//
// Removes a layer from the output list
//

void RemoveFromOut(int n) {
    int i;

    if (n == out_size - 1) 
        // Handle removal of last item
        out_string_list[n] = "";
    else 
        // Handle removal of item from middle
        for (i = n; i < out_size; i++) out_string_list[i] = out_string_list[i + 1];
    out_size--;
}

//
// Adds a layer to the output list
//

void AddToOut(int n) {
    int i;

    // Make sure we do not add an item that is already there.
    for (i = 0; i < out_size; i++) {
        if (out_string_list[i] == layer_names[n]) return;
    }

    out_string_list[out_size++] = layer_names[n];  
}

/* ------------ Configuration routines ------------- */

//
// Finds a variable in the config file and returns the value string
//

string LoadSetting(string var) {
    string params[], line, str; 
    int n,i;

    for(i = 0; i < n_lines; i++) {
        line = user_settings[i];
        n = strsplit(params, line, ' ');

        // Split around ' = ' using space. If parameter is single word or digit, params[2]  contains the value
        if (n == 3) {
            if(var == params[0]) { 
                return params[2];
            }
        } else if (n > 3) {
            // If parameter contains more than one space, it is a longer string
            if(var == params[0]) { 
                if(strsplit(params, line, '=') == 2) {
                    str = strsub(params[1],1);
                    return str;
                }
            }
        }
    }
    return var;
}

//
// Load the user settings
//
void LoadUserCfgFile(string f_name) {
    int i;
    string key;

    // Load file
    cfg_file_exists = fileglob(cfg, f_name);
    if (!cfg_file_exists) {
        sprintf(key, ":Config file '%s' does not exist !", f_name);
        dlgMessageBox(key);
        return;
    }

    // Load config file
    n_lines = fileread(user_settings, f_name);

    // Load variables
    opt_placement = strtol(LoadSetting("opt_placement"));
    opt_mirrored_text = strtol(LoadSetting("opt_mirrored_text"));
    opt_show_contact = strtol(LoadSetting("opt_show_contact"));
    opt_show_designer = strtol(LoadSetting("opt_show_designer"));
    opt_run_script = strtol(LoadSetting("opt_run_script"));
    x_text_offset = strtod(LoadSetting("x_text_offset"));
    y_text_offset = strtod(LoadSetting("y_text_offset"));
    text_size = strtod(LoadSetting("text_size"));
    text_ratio = strtol(LoadSetting("text_ratio"));
    designer_string = LoadSetting("designer_string");
    project_string = LoadSetting("project_string");
    contact_string = LoadSetting("contact_string");

    version_number = LoadSetting("version_number");

    // v1.1 if string is absent
    if (version_number == "version_number") version_number = "1.1"; 
    
    // Load layer parameters from file only if version is not 1.1
    if (version_number != "1.1") {
        // Clear output string list
        for (i = 0; i < out_size; i++) {
            out_string_list[i] = "";
        }
        
        out_size = strtol(LoadSetting("out_size"));
        NUM_LAYERS = strtol(LoadSetting("NUM_LAYERS"));
    
        // Load Arrays
        for (i = 0; i < NUM_LAYERS; i++) {
            sprintf(key,"layer_names_%d",i);
            layer_names[i] = LoadSetting(key);
            sprintf(key,"layer_numbers_%d",i);
            layer_numbers[i] = strtol(LoadSetting(key));
        }
    
        for (i = 0; i < out_size; i++) {
            sprintf(key,"out_string_list_%d",i);
            out_string_list[i] = LoadSetting(key);
        }
    } else {
        sprintf(key,"File from version %s, ULP is version %s.\n Arrays NOT loaded (defaults kept).",
                version_number, VERSION);
        dlgMessageBox(key);
    }
    
    dlgRedisplay();

    sprintf(key,";Config file '%s' loaded !",f_name);
    dlgMessageBox(key);
}


//
// Save the user settings
//
void SaveUserCfgFile(string f_name) {
    int i;
    string s;

    output(f_name) {
        printf("opt_placement = %d\n", opt_placement);
        printf("opt_mirrored_text = %d\n", opt_mirrored_text);
        printf("opt_show_contact = %d\n", opt_show_contact);
        printf("opt_show_designer = %d\n", opt_show_designer);
        printf("opt_run_script = %d\n", opt_run_script);
        printf("out_size = %d\n", out_size);
        printf("x_text_offset = %.3f\n", x_text_offset);
        printf("y_text_offset = %.3f\n", y_text_offset);
        printf("text_size = %.3f\n", text_size);
        printf("text_ratio = %d\n", text_ratio);
        printf("designer_string = %s\n",designer_string);
        printf("project_string = %s\n",project_string);
        printf("contact_string = %s\n",contact_string);
        printf("NUM_LAYERS = %d\n", NUM_LAYERS);
        printf("version_number = %s\n", VERSION);

        // Output Arrays
        for (i = 0; i < NUM_LAYERS; i++) {
            printf("layer_names_%d = %s\n", i, layer_names[i]);
            printf("layer_numbers_%d = %d\n", i, layer_numbers[i]);
        }

        for (i = 0; i < out_size; i++) {
            printf("out_string_list_%d = %s\n", i, out_string_list[i]);
        }
    }
    
    sprintf(s,";Config file '%s' saved !",f_name);
    dlgMessageBox(s);
}

//
// Handles the setup of paths.
//

void ProcessPaths(void) {
    // Gather project path and create default script name
    board(B) {
        file_name = filesetext(B.name, "_di.scr");
        project_path = filedir(B.name);
    }  

    // See if config file exists. If it doesn't, create it. If it does, load it.
    sprintf(cfg_path,"%s/%s",project_path,user_cfg_file);
    cfg_file_exists = fileglob(cfg, cfg_path);
}  

/* ------------ Drawing routines ------------- */

//
// Generate the drawing script          
//

void MakeInfoScript(int where) {
    real longest_string = 0.0; // Units length of longest string
    real size; // Temporary size variable
    real x_inc, y_inc; // X and Y increments for each loop pass
    real x_pos, y_pos; // Absolute positions for text command
    int i,j; // Counters
    int layer; // Current layer
    int n_strings; // Number of strings to draw
    string rotation; // Rotation parameter value for Text

    // Generate list of layer numbers used
    for (i = 0; i < out_size; i++) {
        layers_list[i] = layer_numbers[GetIndex(out_string_list[i])];
    }

    // Setup string list
    sprintf(lines[0],"Design: %s", project_string);
    sprintf(lines[1],"Designer: %s", designer_string);
    sprintf(lines[2],"Contact: %s", contact_string);
    sprintf(lines[3],"Layer: %s", layer_names[NUM_LAYERS - 1]);

    GetExtents(); // Get maximum board extents so as to draw outside of it

    // Finds which string is the longuest
    for (i = 0; i < 4; i++) {
        size = GetStringSize(lines[i], text_size, text_ratio);
        if (size > longest_string) {
            longest_string = size;
        }
    }

    // Handle the calculation of shown information
    n_strings = 4;

    if (!opt_show_designer) {
        n_strings--;
        shown_list[1] = 0;
    }

    if (!opt_show_contact) {
        n_strings--;
        shown_list[2] = 0;
    }

    output(file_name)
    {
        /* Output setup header */
        printf(";\n");
        printf("Mark;\n");
        printf("Grid Inch;\n");
        printf("Change Width 0.008;\n");
        printf("Change Size %.3f;\n", text_size);
        printf("Change Ratio %d;\n", text_ratio);

        /* Output layer display info */
        printf("Display 20 ");
        for (i = 0; i < out_size; i++) printf("%d ",layer_numbers[GetIndex(out_string_list[i])]);
        printf(";\n");

        text_spacing = text_size * 0.5;

        if (where == PlacementTOP) {
            // Placement is at Top Left, offset by *_text_offset
            // Initialize sizing variables
            x_inc = 0.0;
            y_inc = -(text_spacing + text_size);
   
            x_coord = x_text_offset; 
            m_x_coord = x_coord + longest_string;
            y_coord = y_text_offset + u2inch(top_limit) + (real(n_strings - 1) * abs(y_inc));
        } else {
            // Placement is at Bottom Right, offset by *_text_offset
            // Initialize sizing variables
            x_inc = 0.0;
            y_inc = -(text_spacing + text_size);

            x_coord = x_text_offset + u2inch(right_limit);
            m_x_coord = x_coord + longest_string;
            y_coord = y_text_offset + (real(n_strings - 1) * abs(y_inc));
        }

        /* Draw strings */

        for (i = 0; i < out_size; i++) {
            // Output current layer
            layer = layer_numbers[GetIndex(out_string_list[i])];
            printf("Layer %d;\n",layer);

            // Setup positions and mirroring for current layer
            y_pos = y_coord;
            if (opt_mirrored_text) {
                // Odd layers are non-mirrored, even layers are mirrored
                if (layer & 1) {
                    rotation = "R0";
                    x_pos = x_coord;
                } else {
                    rotation = "MR0";
                    x_pos = m_x_coord;
                }
            } else {
                rotation = "R0";
                x_pos = x_coord;
            }
            
            // Write the information strings
            sprintf(lines[3],"Layer: %s", out_string_list[i]);
            for (j = 0; j < 4; j++) {
                if (shown_list[j]) {
                    printf("Text '%s' %s (%.4f %.4f);\n",lines[j],rotation,x_pos, y_pos);
                    x_pos += x_inc;
                    y_pos += y_inc;
                }
            }
        }
            
        printf("Grid Last;\n");
        printf("Window Fit;\n");
    }
}

/* ------------ Dialog routines ------------- */

int DoDialog(void) {
    int in_selected = -1;
    int out_selected = -1;
    int sorting = 0;
    string str;

    int Result = dlgDialog("Designer Info Parameters") {

        // Trick to force width to >= 640 pixels
        dlgHBoxLayout {
            dlgSpacing(640);
        }   

        // Title string
        dlgHBoxLayout {     
            sprintf(str,"<P><H1>Designer Info v%s (inches)</H1></P><P>By Tennessee Carmel-Veilleux (veilleux@ameth.org)<P>",VERSION);
            dlgLabel(str);
        }

        dlgSpacing(10);
        
        dlgHBoxLayout {
            dlgLabel("File &name:");
            dlgStringEdit(file_name);
            dlgPushButton("Bro&wse") {
                file_name = dlgFileSave("Save Drawing Script", filesetext("DesignerInfo",".scr"), "*.scr");
            }
        }

        dlgSpacing(10);

        dlgHBoxLayout {
            dlgGridLayout {
                dlgCell(0, 0) dlgLabel("Designer info:");
                dlgCell(0, 1, 0, 4) dlgStringEdit(designer_string);

                dlgCell(1, 0) dlgLabel("Project name:");
                dlgCell(1, 1, 1, 4) dlgStringEdit(project_string);

                dlgCell(2, 0) dlgLabel("Contact information:");
                dlgCell(2, 1, 2, 4) dlgStringEdit(contact_string);

                dlgCell(3, 0) dlgLabel("X Offset:");
                dlgCell(3, 1) dlgRealEdit(x_text_offset, 0.0, 10.0);
                dlgCell(3, 2) dlgSpacing(100);
                dlgCell(3, 3) dlgLabel("Text ratio:");
                dlgCell(3, 4) dlgIntEdit(text_ratio, 0, 31);


                dlgCell(4, 0) dlgLabel("Y Offset:");
                dlgCell(4, 1) dlgRealEdit(y_text_offset, 0.0, 10.0);
                dlgCell(4, 2) dlgSpacing(100);
                dlgCell(4, 3) dlgLabel("Text size:");
                dlgCell(4, 4) dlgRealEdit(text_size, 0.01, 0.1);
            }

            dlgSpacing(20);

            dlgVBoxLayout {
                dlgGroup("Options") {
                    dlgCheckBox("&Mirrored text for bottom layers", opt_mirrored_text );
                    dlgCheckBox("Show &Designer information", opt_show_designer );
                    dlgCheckBox("Show &Contact information", opt_show_contact );
                    dlgCheckBox("Run scri&pt when done", opt_run_script );
                }

                dlgGroup("Placement") {
                    dlgRadioButton("Draw at &Top of board", opt_placement);
                    dlgRadioButton("Draw at &Right of board", opt_placement);
                }
            }
        }

        dlgVBoxLayout {
            dlgSpacing(10);   

            dlgLabel("Layers for drawing:"); 

            dlgSpacing(10);
        
            dlgHBoxLayout {
                dlgListView("Layers available",layer_names,in_selected, sorting) { AddToOut(in_selected); }
                dlgLabel(">>>");
                dlgListView("Layers for drawing",out_string_list,out_selected,sorting) { RemoveFromOut(out_selected); }
            }
        }

        dlgHBoxLayout {     
            // dlgStretch(1);
            dlgPushButton("+Generate") {
                if (file_name == "") {
                    dlgMessageBox(":Filename is empty !");
                } else if (out_size == 0) {
                    dlgMessageBox(":You must select layers to output !");
                } else
                    dlgAccept();
            }
            dlgPushButton("Cancel") dlgReject();
            dlgStretch(1);
            dlgPushButton("Load Config") {
                string f_name;
                f_name = dlgFileOpen("Load configuration file...", cfg_path, "*.cfg");
                if (f_name != "") {
                    LoadUserCfgFile(f_name);
                }   
            }
            dlgPushButton("Save Config") {
                string f_name;
                f_name = dlgFileSave("Save configuration file...", cfg_path, "*.cfg");
                if (f_name != "") {
                    SaveUserCfgFile(f_name);
                }   
            }
        }
    };

    return Result;
}

/* ------------ Main process ------------- */
if (!board) {
    dlgMessageBox(":You must run this ULP in board !");
    exit(1);
}

if (EV < 4 || (EV == 4 && ER < 13)) {
    string s;
    sprintf(s,":You need version 4.13 of eagle for this ULP to work.\nYou have %d.%02d !\nDownload the latest from http://www.cadsoft.de",EV,ER);
    dlgMessageBox(s);
    exit(1);
}

ProcessPaths();

if (DoDialog() == 1) {
    MakeInfoScript(opt_placement);
    if (opt_run_script) {
        exit("; SCR '"+file_name+";\n");
    }
}

/* Old RemoveFromOut routine */
/*    int i;
    numeric string new_list[];

    // Handle removal of last item
    if (n == out_size - 1) {
        // If not the only item, make list with beginning
        if (out_size - 1 != 0) {
            for (i = 0; i < out_size - 1; i++) {
                new_list[i] = out_string_list[i];
            }
        }
    } else {
        // Handle removal of first item
        if (n == 0) {
            for (i = 1; i < out_size; i++)  
                new_list[i-1] = out_string_list[i];
        } else {
            // Handle removal of item in between
            for (i = 0; i < n; i++) {
                new_list[i] = out_string_list[i];
            }
            for (i = n + 1; i < out_size; i++) {
                new_list[i - 1] = out_string_list[i];
            }
        }
    }
    
    // Clear output list
    for(i = 0; i < out_size; i++) {
        out_string_list[i] = "";
    }
    
    // Copy new list in output list
    for(i = 0; i < out_size - 1; i++) {
        out_string_list[i] = new_list[i];
    }
    
    out_size--;
    */