module.exports = function(ARGV) {
    var argv = ARGV == undefined ? {} : ARGV;
    var _ = require('lodash');
    var process = require('process');
    var os = require('os');
    var fs = require('fs');
    this.GEN_TYPE = argv.gen_type == undefined ? 'genesis' : argv.gen_type;
    this.ERROR_HANDLE = argv.error_handle == undefined ? 1 : argv.error_handle;
	// print('=========================this====================');
	// print(_.toString(this));

    var env = {
        GENESIS_DIR : argv.gen_dir == undefined ? process.getEnv('GENESIS_DIR') : argv.gen_dir,
        GENESIS_VER : process.getEnv('GENESIS_VER'),
        GENESIS_EDIR : argv.gen_edir == undefined ? process.getEnv('GENESIS_EDIR') : argv.gen_edir,
        GENESIS_TMP : argv.gen_dir == undefined ? process.getEnv('GENESIS_TMP') : argv.gen_dir + "/tmp",
    };
	print('===============ENV=================');
	print(_.toString(env));
	print('===============ENV=================');
    if (this.GEN_TYPE === 'genesis') {
        this.GENESIS_DIR = _.isEmpty(env.GENESIS_DIR) ? '/genesis' : env.GENESIS_DIR;
        this.GENESIS_VER = _.isEmpty(env.GENESIS_VER) ? '90' : env.GENESIS_VER;
        this.GENESIS_EDIR = _.isEmpty(env.GENESIS_EDIR) ? this.GENESIS_DIR + '/e' + this.GENESIS_VER : env.GENESIS_EDIR;
        this.GENESIS_TMP = _.isEmpty(env.GENESIS_TMP) ? this.GENESIS_DIR + '/tmp' : env.GENESIS_TMP;
    }
    else if (this.GEN_TYPE === 'incam') {
        if (os.type() === 'windows') {
            this.GENESIS_TMP = _.isEmpty(env.GENESIS_TMP) ? this.GENESIS_DIR + '/tmp' : env.GENESIS_TMP;
            this.GENESIS_EDIR = _.isEmpty(env.GENESIS_EDIR) ? 'c:/incam/release/bin' : env.GENESIS_EDIR;
        }
        else {
            this.GENESIS_TMP = env.GENESIS_TMP || '/tmp';
	        this.GENESIS_DIR = env.GENESIS_DIR || '/incam';
            this.GENESIS_EDIR = env.GENESIS_EDIR || '/incam/release/bin';
        }
    } else if (this.GEN_TYPE === 'incamp') {
        if (os.type() === 'windows') {
            this.GENESIS_TMP = _.isEmpty(env.GENESIS_TMP) ? this.GENESIS_DIR + '/tmp' : env.GENESIS_TMP;
            this.GENESIS_EDIR = _.isEmpty(env.GENESIS_EDIR) ? 'c:/incampro/release/bin' : env.GENESIS_EDIR;
        }
        else {
            this.GENESIS_TMP = env.GENESIS_TMP || '/tmp';
	        this.GENESIS_DIR = env.GENESIS_DIR || '/incampro';
            this.GENESIS_EDIR = env.GENESIS_EDIR || '/incampro/release/bin';
        }
    }

    this.COMMS = 'pipe';
    this.DIR_PREFIX = '@%#%@';
    this.STATUS = [];
    this.INFO_RESULT = {};

    this.sendCommand = function(commnadType, command) {
        this.blankStatusResults();
        this.sendCommandToPipe(commnadType, command);
    };

    this.blankStatusResults = function() {
        this._STATUS = undefined;
        this._READANS = undefined;
        this._PAUSEANS = undefined;
        this._MOUSEANS = undefined;
        this._COMANS = undefined;
    };

    this.sendCommandToPipe = function(commnadType, command) {
        process.stdout.write(this.DIR_PREFIX + commnadType + ' ' + command + "\n");
    };

    this.COM = function(command, argv) {
        if (this.hasError()) return;

        var cmdstr = command;
        if (!_.isEmpty(argv)) {
            for (i in argv) {
                cmdstr += "," + i + "=" + argv[i];
            }
        }
        this.sendCommand("COM", cmdstr);
        this._STATUS = this.getReply();
        if (this._STATUS != '0') {
            this.STATUS.push(this._STATUS + ':' + cmdstr)
        }
        this._READANS = this.getReply();
        this._COMANS = this._READANS;
        return this._COMANS;
    };

    this.getReply = function() {
        var reply = '';
        if (this.COMMS === 'pipe') {
            reply = process.stdin.readLine();

        }
        return reply.replace(/[\r\n]/g, '');
    };

    this.VON = function() {
        this.sendCommand('VON', '');
    };

    this.VOF = function() {
        this.sendCommand('VOF', '');
    };

    this.SU_ON = function() {
        this.sendCommand('SU_ON', '');
    };

    this.SU_OFF = function() {
        this.sendCommand('SU_OFF', '');
    };

    this.PAUSE = function(msg) {
        if (this.hasError()) return;

        this.sendCommand('PAUSE', msg);
        this._STATUS = this.getReply();
        this._READANS = this.getReply();
        this._PAUSEANS = this.getReply();
        if (this._STATUS != 0 || this._PAUSEANS == 0 || this._PAUSEANS == '') {
            this.STATUS.push('Pause Exit:' + msg);
        }
        return this._PAUSEANS;
    };

    this.MOUSE = function(argv) {
        if (this.hasError()) return;
        if (argv == undefined || argv == null) {
            argv = {};
        }
        var type = _.isEmpty(argv.type) ? 'p' : argv.type;
        var text = _.isEmpty(argv.text) ? '...' : argv.text;
        this.sendCommand('MOUSE', type + ' ' + text);
        this._STATUS = this.getReply();
        this._READANS = this.getReply();
        this._MOUSEANS = this.getReply();
        if (this._STATUS != 0 || this._MOUSEANS == 0 || this._MOUSEANS == '') {
            this.STATUS.push('Mouse Exit:' + msg);
        }
        var result = {};
        if (type == 'p') {
            var _tmp = this._MOUSEANS.split(' ');
            result['x'] = _tmp[0];
            result['y'] = _tmp[1];
        }
        else {
            var _tmp2 = this._MOUSEANS.split(' ');
            result['x1'] = _tmp2[0];
            result['y1'] = _tmp2[1];
            result['x2'] = _tmp2[2];
            result['y2'] = _tmp2[3];
        }
        return result;
    };

    this.AUX = function(command, argv) {
        var cmdstr = command;
        if (!_.isEmpty(argv)) {
            for (i in argv) {
                cmdstr += "," + i + "=" + argv[i];
            }
        }
        this.sendCommand("AUX", cmdstr);
        this._STATUS = this.getReply();
        this._READANS = this.getReply();
        this._COMANS = this._READANS;
    };

    this.DO_INFO = function() {
        var args = [];
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        var info_pre = 'info,out_file=$csh_file,write_mode=replace,args=';
        var info_com = info_pre + ' ' + args.join(' ') + ' -m SCRIPT';
        this.parse(info_com);
    };

    this.INFO = function(argv) {
        if (_.isEmpty(argv)) {
            argv = {};
        }
        this.clearInfoResult();

        var entity_path = '';
        var data_type = '';
        var parameters = '';
        var serial_number = '';
        var options = '';
        var help = '';
        var entity_type = '';
        var units = 'units = inch';
        var parse = 'yes';

        for (var k in argv) {
            var v = argv[k];
            if (k == 'entity_type') {
                entity_type = '-t ' + v;
            }
            else if (k == 'entity_path') {
                entity_path = '-e ' + v;
            }
            else if (k == 'data_type') {
                data_type = '-d ' + v;
            }
            else if (k == 'parameters') {
                parameters = '-p ' + v;
            }
            else if (k == 'serial_number') {
                serial_number = '-s ' + v;
            }
            else if (k == 'options') {
                options = '-o ' + v;
            }
            else if (k == 'help') {
                options = '-help';
            }
            else if (k == 'units') {
                units = 'units=' + v;
            }
            else if (k == 'parse') {
                parse = v;
            }
        }

        var info_pre = 'info,out_file=$csh_file,write_mode=replace,' + units + ',args=';
        var info_com = info_pre + ' ' + entity_type + ' ' + entity_path + ' ' + data_type + ' ' + parameters + ' ' + serial_number + ' ' + options + ' ' + help;
        if (parse == 'yes') {
            this.parse(info_com);
        }
        else {
            var csh_file = this.GENESIS_TMP + '/info_csh.' + process.pid();
            info_com = info_com.replace(/\$csh_file/, csh_file);
            this.COM(info_com);
            return csh_file;
        }

    };

    this.parse = function(info_com) {
        var request = info_com;
        var csh_file = this.GENESIS_TMP + '/info_csh.' + process.pid();
        request = request.replace(/\$csh_file/, csh_file);
        this.COM(request);
        var filelines = fs.readFile(csh_file).split(/\r?\n/);
        for (var i in filelines) {
            var line = filelines[i].trim();
            if (_.isEmpty(line)) continue;
            var re = /\s*set\s+(\S+)\s*=\s*(.*)\s*/;
            if (re.test(line)) {
                var key = RegExp.$1;
                var value = RegExp.$2;
                if (value == '()') {
                    value = [];
                }
                else if (value.match(/^\s*\(\s*'/)) {
                    value = value.replace(/^\s*\(\s*'/, '["');
                    value = value.replace(/'\s*\)\s*$/, '"]');
                    value = value.replace(/'\s+'/g, '","');
                    value = JSON.parse(value);
                }
                else if (value.match(/^\s*\'/)){
                    value = value.replace(/^'|'$/g, '');
                }
                this.INFO_RESULT[key] = value;
            }
        }
        fs.unlink(csh_file);
    };

    this.clearInfoResult = function() {
        this.INFO_RESULT = {};
    };

    this.dbutil = function(command) {
        var bin = '';
        if (this.GEN_TYPE == 'genesis') {
            if (os.type() == 'win') {
                bin = this.GENESIS_EDIR + '/misc/dbutil.exe';
            }
            else {
                bin = this.GENESIS_EDIR + '/misc/dbutil';
            }
        }
        else if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            if (os.type() == 'win') {
                bin = this.GENESIS_EDIR + '/dbutil.exe';
            }
            else {
                bin = this.GENESIS_EDIR + '/dbutil';
            }
        }
        return process.exec(bin, command);
    };

    this.hooksSkip = function() {
        this.COM('skip_current_command');
    };

    this.getJobPath = function(argv) {
        jobname = argv['job'];
        if (this.GEN_TYPE == 'genesis') {
            var dbname = '';
            var dbpath = '';
            var joblist = fs.readFile(this.GENESIS_DIR + '/share/joblist').split(/\r?\n/);
            var jobre = new RegExp('^\\s*NAME\\s*=\\s*' + _.escapeRegExp(jobname) + "\\s*$","i");
            var dbre = /^\s*DB\s*=\s*(.*)$/i;
            for (var i = 0; i < joblist.length;) {
                if (joblist[i].match(jobre)) {
                    i++;
                    if (dbre.test(joblist[i])) {
                        dbname = RegExp.$1;
                        break;
                    }
                }
                i++;
            }

            jobre = new RegExp('^\\s*NAME\\s*=\\s*' + _.escapeRegExp(dbname) + "\\s*$","i");
            dbre = /^\s*PATH\s*=\s*(.*)$/i;
            var dblist = fs.readFile(this.GENESIS_DIR + '/sys/dblist').split(/\r?\n/);
            for (var j = 0; j < dblist.length;) {
                if (dblist[j].match(jobre)) {
                    j++;
                    if (dbre.test(dblist[j])) {
                        dbpath = RegExp.$1;
                        break;
                    }
                }
                j++;
            }

            if (jobname == 'genesislib') {
                return dbpath + '/lib';
            }
            else {
                return dbpath + '/jobs/' + jobname;
            }
        }
        else if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            var path = this.dbutil(['path','jobs',jobname]);
            path = path.replace(/\s+/g, '');
            return path;
        }
        return '';
    }

    this.shellwords = function(argv) {
        var words = [];
        var word = '';
        var cflag = false;
        var escape = false;

        for (var i in argv) {
            var c = line[i];
            if (!cflag) {
                if (c == "'") {
                    cflag = true;
                }
            }
            else {
                if (c == '\\') {
                    escape = true;
                    word += c;
                }
                else {
                    if (escape) {
                        word += c;
                    }
                    else if (c == "'") {
                        words.push(word);
                        word = '';
                        cflag = false;
                    }
                    else {
                        word += c;
                    }
                    escape = false;
                }
            }
        }
        return words;
    };

    this.hasError = function() {
        return (this.ERROR_HANDLE == 1 && this.STATUS.length > 0);
    };

    /*************************************/

    this.currentJob = function() {
        return process.getEnv('JOB');
    }

    this.currentStep = function() {
        return process.getEnv('STEP');
    }

    this.getWorkLayer = function() {
        return this.COM('get_work_layer');
    }

    this.getAffectLayer = function() {
        return this.COM('get_affect_layer').trim().split(' ');
    }

    this.getDispLayers = function() {
        return this.COM('get_disp_layers').trim().split(' ');
    }

    this.getJobList = function() {
        this.INFO({entity_type:'root'});
        return this.INFO_RESULT['gJOBS_LIST'];
    }

    this.getUserName = function() {
        return this.COM('get_user_name');
    }

    this.getJobDbName = function(argv) {
        var jobname = argv['job'];
        if (this.GEN_TYPE == 'genesis') {
            var joblist = fs.readFile(this.GENESIS_DIR + '/share/joblist').split(/\r?\n/);
            var jobre = new RegExp('^\\s*NAME\\s*=\\s*' + _.escapeRegExp(jobname) + "\\s*$","i");
            var dbre = /^\s*DB\s*=\s*(.*)$/i;
            for (var i = 0; i < joblist.length;) {
                if (joblist[i].match(jobre)) {
                    i++;
                    if (dbre.test(joblist[i])) {
                        return RegExp.$1;
                    }
                }
                i++;
            }
        }
        return '';
    }

    this.isJobExists = function(argv) {
        this.INFO({entity_type:'job', entity_path:argv['job'], data_type:'exists'});
        return (this.INFO_RESULT['gEXISTS'] == 'yes');
    }

    this.isJobOpen = function(argv) {
        return (this.COM('is_job_open', {job:argv['job']}) == 'yes');
    }

    this.createJob = function(argv) {
        if (_.includes(this.getJobList(), argv['name'])) {
            return 'exists';
        }
        this.COM('create_entity', {job:'', is_fw:'no', type:'job', name:argv['name'], db:argv['db'], fw_type:'form'});
        return true;
    }

    this.openJob = function(argv) {
        if (_.isEmpty(argv['update_clipboard'])) argv['update_clipboard'] = 'view_job';
        this.VOF();
        this.COM('clipb_open_job', {job:argv['job'], update_clipboard:argv['update_clipboard']});
        this.VON();
    }

    this.closeJob = function(argv) {
        if (this.isJobOpen({job:argv['job']})) {
            this.COM('close_job', {job:argv['job']});
            return true;
        }
        return false;
    }

    this.deleteJob = function(argv) {
        if (this.isJobExists({job:argv['job']})) {
            this.closeJob({job:argv['job']});
            this.COM('delete_entity',{job:'', type:'job', name:argv['job']});
            return (this._STATUS == '0');
        }
        return false;
    }

    this.deleteStep = function(argv) {
        if (this.isStepExists({job:argv['job'], step:argv['step']})){
            this.COM('delete_entity', {job:argv['job'], type:'step', name:argv['step']});
            return (this._STATUS == '0');
        }
        return false;
    }

    this.importJob = function(argv) {
        if (_.isEmpty(argv['analyze_surfaces'])) argv['analyze_surfaces'] = 'no';
        if (_.includes(this.getJobList(), argv['name'])) return false;
        this.COM('import_job', {name:argv['name'], db:argv['db'], path:argv['path'], analyze_surfaces:argv['analyze_surfaces']});
        return true;
    }

    this.listOpenJobs = function() {
        var csh_file = this.GENESIS_TMP + '/info_csh.' + process.pid();
        this.COM('list_open_jobs', {file:csh_file});
        var list = fs.readFile(csh_file).split(/\r?\n/);
        fs.unlink(csh_file);
        return _.map(list, function(n){return n.trim()});
    }

    this.isStepExists = function(argv) {
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'exists'});
        return (this.INFO_RESULT.gEXISTS == 'yes');
    }

    this.createStep = function(argv) {
        if (!_.isEmpty(argv.step)) argv.name = argv.step;
        if (this.isStepExists({job:argv.job, step:argv.name})) return false;
        this.COM('create_entity', {job:argv.job, is_fw:'no', type:'step', name:argv.name, db:'', fw_type:'form'});
        return true;
    }

    this.setGroup = function(argv) {
        this.AUX('set_group', {group:argv.group});
    }

    this.openStep = function(argv) {
        if (_.isEmpty(argv.iconic)) argv.iconic = 'no';
        if (!_.isEmpty(argv.step)) argv.name = argv.step;
        var grp = this.COM('open_entity', {job:argv.job, type:'step', name:argv.name, iconic:argv.iconic});
        if (this._STATUS == '0') {
            this.setGroup({group: grp});
            this.COM('editor_page_close');
            grp = this.COM('open_entity', {job:argv.job, type:'step', name:argv.name, iconic:argv.iconic});
            this.setGroup({group:grp});
            this.COM('sel_options', {clear_mode:'clear_after', display_mode:'all_layers', area_inout:'inside',
                                     area_select:'select', select_mode:'standard', area_touching_mode:'exclude'});
            return grp;
        }
        return 0;
    }

    this.closeStep = function() {
        this.COM('editor_page_close');
    }

    this.getStepList = function(argv) {
        this.INFO({entity_type:'job', entity_path:argv.job, data_type:'STEPS_LIST'});
        return this.INFO_RESULT.gSTEPS_LIST;
    }

    this.getMatrix = function(argv) {
        if (_.isEmpty(argv)) argv = {};
        if (_.isEmpty(argv.type)) argv.type = 'hash';
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row'});
        var result = {};
        if (argv.type == 'hash') {
            var layer_count = 0;
            var smfrnum = 1;
            var ssfrnum = 1;
            var spfrnum = 1;
            var smbanum = 1;
            var ssbanum = 1;
            var spbanum = 1;
            var top = '';
            var bottom = '';
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (_.isEmpty(item)) continue;
                result[item] = {};
                result[item]['row'] = this.INFO_RESULT.gROWrow[n];
                result[item]['type'] = this.INFO_RESULT.gROWtype[n];
                result[item]['name'] = this.INFO_RESULT.gROWname[n];
                result[item]['context'] = this.INFO_RESULT.gROWcontext[n];
                result[item]['layer_type'] = this.INFO_RESULT.gROWlayer_type[n];
                result[item]['polarity'] = this.INFO_RESULT.gROWpolarity[n];
                result[item]['side'] = this.INFO_RESULT.gROWside[n];
                result[item]['drl_start'] = this.INFO_RESULT.gROWdrl_start[n];
                result[item]['drl_end'] = this.INFO_RESULT.gROWdrl_end[n];
                result[item]['gdrl_start'] = result[item]['drl_start'];
                result[item]['gdrl_end'] = result[item]['drl_end'];

                if (result[item]['context'] == 'board'
                    && result[item]['layer_type'].match(/^signal$|^power_ground$|^mixed$/)
                    && result[item]['side'] == 'inner')
                {
                    result[item]['tl_num'] = layer_count + 1;
                    result[item]['tl_name'] = 'l' + (layer_count + 1);
                    result[item]['tl_type'] = 'inner';
                    layer_count++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'].match(/^signal$|^power_ground$|^mixed$/)
                         && result[item]['side'] == 'top')
                {
                    result[item]['tl_num'] = layer_count + 1;
                    result[item]['tl_name'] = 'top';
                    result[item]['tl_type'] = 'outer';
                    top = item;
                    layer_count++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'].match(/^signal$|^power_ground$|^mixed$/)
                         && result[item]['side'] == 'bottom')
                {
                    result[item]['tl_num'] = layer_count + 1;
                    result[item]['tl_name'] = 'bottom';
                    result[item]['tl_type'] = 'outer';
                    bottom = item;
                    layer_count++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'] == 'solder_mask'
                         && result[item]['side'] == 'bottom')
                {
                    result[item]['tl_num'] = n + 1000;
                    if (smbanum == 1) {
                        result[item]['tl_name'] = 'sm_ba';
                    }
                    else {
                        result[item]['tl_name'] = 'sm_ba(' + smbanum + ')';
                    }
                    result[item]['tl_type'] = 'solder_mask';
                    smbanum++;
                }
                else if (result[item]['context'] == 'board'
                        && result[item]['layer_type'] == 'solder_paste'
                        && result[item]['side'] == 'bottom')
                {
                    result[item]['tl_num'] = n + 1000;
                    if (spbanum == 1) {
                        result[item]['tl_name'] = 'sp_ba';
                    }
                    else {
                        result[item]['tl_name'] = 'sp_ba(' + spbanum + ')';
                    }
                    result[item]['tl_type'] = 'solder_paste';
                    spbanum++;
                }
                else if (result[item]['context'] == 'board'
                        && result[item]['layer_type'] == 'silk_screen'
                        && result[item]['side'] == 'bottom')
                {
                    result[item]['tl_num'] = n + 1000;
                    if (ssbanum == 1) {
                        result[item]['tl_name'] = 'ss_ba';
                    }
                    else {
                        result[item]['tl_name'] = 'ss_ba(' + ssbanum + ')';
                    }
                    result[item]['tl_type'] = 'silk_screen';
                    ssbanum++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'] == 'solder_mask'
                         && result[item]['side'] == 'top')
                {
                    result[item]['tl_num'] = n - 1000;
                    if (smfrnum == 1) {
                        result[item]['tl_name'] = 'sm_fr';
                    }
                    else {
                        result[item]['tl_name'] = 'sm_fr(' + smfrnum + ')';
                    }
                    result[item]['tl_type'] = 'solder_mask';
                    smfrnum++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'] == 'solder_paste'
                         && result[item]['side'] == 'top')
                {
                    result[item]['tl_num'] = n - 1000;
                    if (spfrnum == 1) {
                        result[item]['tl_name'] = 'sp_fr';
                    }
                    else {
                        result[item]['tl_name'] = 'sp_fr(' + spfrnum + ')';
                    }
                    result[item]['tl_type'] = 'solder_paste';
                    spfrnum++;
                }
                else if (result[item]['context'] == 'board'
                         && result[item]['layer_type'] == 'silk_screen'
                         && result[item]['side'] == 'top')
                {
                    result[item]['tl_num'] = n - 1000;
                    if (ssfrnum == 1) {
                        result[item]['tl_name'] = 'ss_fr';
                    }
                    else {
                        result[item]['tl_name'] = 'ss_fr(' + ssfrnum + ')';
                    }
                    result[item]['tl_type'] = 'silk_screen';
                    ssfrnum++;
                }
                else if (result[item]['context'] == 'misc'){
                    result[item]['tl_num'] = n + 110000;
                }

            }

            var tl_name_num = {};
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (_.isEmpty(item)) continue;
                if (result[item]['context'] == 'board' && result[item]['layer_type'] == 'drill') {
                    result[item]['drl_start_num'] = result[result[item]['drl_start']]['tl_num'];
                    result[item]['drl_end_num'] = result[result[item]['drl_end']]['tl_num'];
                    if (result[item]['drl_start_num'] <= 0) {
                        result[item]['drl_start_num'] = 1;
                        result[item]['drl_start'] = top;
                    }
                    if (result[item]['drl_end_num'] > layer_count) {
                        result[item]['drl_end_num'] = layer_count;
                        result[item]['drl_end'] = bottom;
                    }
                    result[item]['tl_name'] = 'drill' + result[item]['drl_start_num'] + '-' + result[item]['drl_end_num'];
                    if (result[item]['drl_start_num'] == 1 && result[item]['drl_end_num'] == layer_count) {
                        result[item]['tl_name'] = 'drill';
                    }
                    if (_.has(tl_name_num, result[item]['tl_name'])) {
                        tl_name_num[result[item]['tl_name']] = tl_name_num[result[item]['tl_name']] + 1;
                        result[item]['tl_name'] = result[item]['tl_name'] + '(' + tl_name_num[result[item]['tl_name']] + ')';
                    }
                    else {
                        tl_name_num[result[item]['tl_name']] = 1;
                    }
                    result[item]['tl_type'] = 'drill';
                    result[item]['tl_num'] = 10000 + n;
                }
            }
            if (!_.isEmpty(this.TL_NAME)) {
                for (var layer in this.TL_NAME) {
                    if (_.has(result, layer)) {
                        result[layer] = _.merge(result[layer], this.TL_NAME[layer]);
                    }
                }
            }

            if (!_.isEmpty(argv.layer)) {
                result = result[argv.layer];
            }

        }
        else {
            result['row'] = this.INFO_RESULT.gROWrow;
            result['type'] = this.INFO_RESULT.gROWtype;
            result['name'] = this.INFO_RESULT.gROWname;
            result['context'] = this.INFO_RESULT.gROWcontext;
            result['layer_type'] = this.INFO_RESULT.gROWlayer_type;
            result['polarity'] = this.INFO_RESULT.gROWpolarity;
            result['drl_start'] = this.INFO_RESULT.gROWdrl_start;
            result['drl_end'] = this.INFO_RESULT.gROWdrl_end;

        }
        return result;
    }

    this.matrixAddLayer = function(argv) {
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        if (_.isEmpty(argv.context)) argv.context = 'misc';
        if (_.isEmpty(argv.type)) argv.type = 'signal';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        var after = argv.after;
        var before = argv.before;
        delete argv.after;
        delete argv.before;
        if (!_.isEmpty(after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == after) {
                    argv.row = row + 1;
                    break;
                }
                row++;
            }
        }
        else if (!_.isEmpty(before)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == before) {
                    argv.row = row;
                    break;
                }
                row++;
            }
            print("before row = ", argv.row);
        }


        if (argv.row > 0) {
            this.COM('matrix_insert_row', {job:argv.job, matrix:argv.matrix, row:argv.row});
        }
        else {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            var ok = false;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == '') {
                    ok = true;
                    break;
                }
                row++;
            }
            if (!ok) {
                this.COM('matrix_add_row', {job:argv.job, matrix:argv.matrix});
            }
            argv.row = row;
        }
        this.COM('matrix_add_layer', argv);
        this.COM('matrix_refresh', {job:argv.job, matrix:argv.matrix});
    }

    this.matrixMoveRow = function(argv){
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        if (!_.isEmpty(argv.layer)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == argv.layer) {
                    argv.row = row;
                    break;
                }
                row++;
            }
        }
        if (!_.isEmpty(argv.after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == argv.after) {
                    argv.ins_row = ++row;
                    break;
                }
                row++;
            }
        }
        else if (!_.isEmpty(argv.before)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == argv.before) {
                    argv.ins_row = row;
                    break;
                }
                row++;
            }
        }

        if (!_.isEmpty(argv.row) && !_.isEmpty(argv.ins_row)) {
            this.COM('matrix_move_row', {job:argv.job, row:argv.row, ins_row:argv.ins_row, matrix:argv.matrix});
        }
    }
    
    this.matrixAddStep = function(argv) {
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        var after = argv.after; delete argv.after;
        var before = argv.before; delete argv.before;
        if (this.isStepExists({job:argv.job, step:argv.step})) return;
        if (!_.isEmpty(after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == after) {
                    argv.col = ++col;
                    break;
                }
                col++;
            }
        }
        else if (!_.isEmpty(before)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == before) {
                    argv.col = col;
                    break;
                }
                col++;
            }
        }

        if (!_.isEmpty(argv.col)) {
            this.COM('matrix_insert_col', {job:argv.job, matrix:argv.matrix, col:argv.col});
        }
        else {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            var ok = false;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == '') {
                    ok = true;
                    break;
                }
                col++;
            }
            if (!ok) {
                this.COM('matrix_add_col', {job:argv.job, matrix:argv.matrix});
            }
        }

        this.COM('matrix_add_step', argv);
        this.COM('matrix_refresh', {job:argv.job, matrix:argv.matrix});
    }

    this.matrixDeleteStep = function(argv) {
        if (! this.isStepExists({job:argv.job, step:argv.step})) return;
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
        var col = 1;
        var ok = false;
        for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
            var item = this.INFO_RESULT.gCOLstep_name[n];
            if (item == argv.step) {
                ok = true;
                break;
            }
            col++;
        }
        if (ok) {
            argv.col = col;
            this.COM('matrix_delete_col', {job:argv.job, matrix:argv.matrix, col:argv.col});
            this.COM('matrix_refresh', {job:argv.job, matrix:argv.matrix});
        }
    }

    this.matrixCopyStep = function(argv) {
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        if (! this.isStepExists({job:argv.job, step:argv.step})) return;
        if (this.isStepExists({job:argv.job, step:argv.new_name})) return;
        var after = argv.after; delete argv.after;
        var before = argv.before; delete argv.before;
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
        var col = 1;
        var ok = false;
        for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
            var item = this.INFO_RESULT.gCOLstep_name[n];
            if (item == argv.step) {
                ok = true;
                break;
            }
            col++;
        }
        if (ok) {
            argv.col = col;
        }
        else {
            return;
        }

        if (!_.isEmpty(after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == after) {
                    argv.ins_col = ++col;
                    break;
                }
                col++;
            }
        }
        else if (!_.isEmpty(before)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == before) {
                    argv.ins_col = col;
                    break;
                }
                col++;
            }
        }
        if (_.isEmpty(argv.ins_col)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
            var col = 1;
            var ok = false;
            for (var n = 0; n < this.INFO_RESULT.gCOLstep_name.length; n++) {
                var item = this.INFO_RESULT.gCOLstep_name[n];
                if (item == '') {
                    ok = true;
                    break;
                }
                col++;
            }
            if (!ok) {
                this.COM('matrix_add_col', {job:argv.job, matrix:argv.matrix});
            }
            argv.ins_col = col;
        }
        this.COM('matrix_copy_col', {job:argv.job, matrix:argv.matrix, col:argv.col, ins_col:argv.ins_col});
        this.COM('matrix_refresh', {job:argv.job, matrix:argv.matrix});
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'col', parameters:'step_name'});
        var step = this.INFO_RESULT.gCOLstep_name[argv.ins_col-1];
        this.COM('matrix_rename_step', {job:argv.job, matrix:argv.matrix, step:step, new_name:argv.new_name});
    }

    this.matrixCopyLayer = function(argv) {
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        if (! this.isLayerExists({job:argv.job, layer:argv.layer})) return;
        if (this.isLayerExists({job:argv.job, layer:argv.new_name})) return;
        var after = argv.after; delete argv.after;
        var before = argv.before; delete argv.before;
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
        var row = 1;
        var ok = false;
        for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
            var item = this.INFO_RESULT.gROWname[n];
            if (item == argv.layer) {
                ok = true;
                break;
            }
            row++;
        }
        if (ok) {
            argv.row = row;
        }
        else {
            return;
        }
        if (!_.isEmpty(argv.ins_row)) {

        }
        else if (!_.isEmpty(after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == after) {
                    argv.ins_row = ++row;
                    break;
                }
                row++;
            }
        }
        else if (!_.isEmpty(before)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == before) {
                    argv.ins_row = row;
                    break;
                }
                row++;
            }
        }

        if (_.isEmpty(argv.ins_row)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            var row = 1;
            var ok = false;
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                var item = this.INFO_RESULT.gROWname[n];
                if (item == '') {
                    ok = true;
                    break;
                }
                row++;
            }
            if (!ok) {
                this.COM('matrix_add_row', {job:argv.job, matrix:argv.matrix});
            }
            argv.ins_row = row;
        }
        this.COM('matrix_copy_row', {job:argv.job, matrix:argv.matrix, row:argv.row, ins_row:argv.ins_row});
        this.COM('matrix_refresh', {job:argv.job, matrix:argv.matrix});
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
        var layer = this.INFO_RESULT.gROWname[argv.ins_row-1];
        this.COM('matrix_rename_layer', {job:argv.job, matrix:argv.matrix, layer:argv.layer, new_name:argv.new_name});
        this.matrixLayerAttr({job:argv.job, layer:argv.new_name, type:argv.type, context:argv.context, polarity:argv.polarity});
    }

    this.openMatrix = function(argv) {
        this.COM('open_entity', {job:argv.job, type:'matrix', name:'matrix', iconic:'no'});
    }

    this.matrixRefresh = function(argv) {
        this.COM('matrix_refresh', {job:argv.job, matrix:'matrix'});
    }

    this.matrixLayerAttr = function(argv) {
        argv.layer = [].concat(argv.layer);
        var self = this;
        _.forEach(argv.layer, function(layer){
            if (!_.isEmpty(argv.type)) {
                self.COM('matrix_layer_type', {job:argv.job, matrix:'matrix', layer:layer, type:argv.type});
            }
            if (!_.isEmpty(argv.context)) {
                self.COM('matrix_layer_context', {job:argv.job, matrix:'matrix', layer:layer, context:argv.context});
            }
            if (!_.isEmpty(argv.polarity)) {
                self.COM('matrix_layer_polar', {job:argv.job, matrix:'matrix', layer:layer, polarity:argv.polarity});
            }
        })
    }

    this.matrixLayerDrill = function(argv) {
        if (_.isEmpty(argv.matrix)) argv.matrix = 'matrix';
        this.COM('matrix_layer_drill', argv);
    }

    this.getLayerCount = function(argv) {
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row'});
        var ret = 0;
        for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
            if (this.INFO_RESULT.gROWcontext[n] == 'board' && /^signal$|^power_ground$|^mixed$/.test(this.INFO_RESULT.gROWlayer_type[n])) {
                ret++;
            }
        }
        return ret;
    }

    this.getTool = function(argv) {
        if (_.isEmpty(argv.type)) argv.type = 'hash';
        if (_.isEmpty(argv.hashkey)) argv.hashkey = ['num'];
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL', units:argv.units});
        var ret = {};
        if (argv.type == 'hash') {
            for (var n = 0; n < this.INFO_RESULT.gTOOLnum.length; n++) {
                var keys = [];
                var self = this;
                _.forEach(argv.hashkey, function(item){
                    keys.push(self.INFO_RESULT['gTOOL'+item][n]);
                });
                var key = keys.join(',');
                if (! ret.hasOwnProperty(key)) ret[key] = {};
                if (_.isEmpty(ret[key].count)) ret[key].count = 0;
                ret[key].num = self.INFO_RESULT.gTOOLnum[n];
                ret[key].count += self.INFO_RESULT.gTOOLcount[n];
                ret[key].type = self.INFO_RESULT.gTOOLtype[n];
                ret[key].min_tol = self.INFO_RESULT.gTOOLmin_tol[n];
                ret[key].max_tol = self.INFO_RESULT.gTOOLmax_tol[n];
                ret[key].finish_size = self.INFO_RESULT.gTOOLfinish_size[n];
                ret[key].drill_size = self.INFO_RESULT.gTOOLdrill_size[n];
                ret[key].slot_len = self.INFO_RESULT.gTOOLslot_len[n] || 0;
                ret[key].type2 = self.INFO_RESULT.gTOOLtype2[n];
                ret[key].shape = self.INFO_RESULT.gTOOLshape[n] || 'hole';
                ret[key].bit = self.INFO_RESULT.gTOOLbit[n] || 0;
            }
        }
        else {
            ret.num = self.INFO_RESULT.gTOOLnum;
            ret.count = self.INFO_RESULT.gTOOLcount;
            ret.type = self.INFO_RESULT.gTOOLtype;
            ret.min_tol = self.INFO_RESULT.gTOOLmin_tol;
            ret.max_tol = self.INFO_RESULT.gTOOLmax_tol;
            ret.finish_size = self.INFO_RESULT.gTOOLfinish_size;
            ret.drill_size = self.INFO_RESULT.gTOOLdrill_size;
            ret.slot_len = self.INFO_RESULT.gTOOLslot_len;
            ret.type2 = self.INFO_RESULT.gTOOLtype2;
            ret.shape = self.INFO_RESULT.gTOOLshape;
            ret.bit = self.INFO_RESULT.gTOOLbit;
        }
        return ret;
    }

    this.toolSet = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.openJob({job:argv.job});
        this.openStep({job:argv.job, name:argv.step});
        var old_units = this.getUnits();
        if (_.toNumber(this.GENESIS_VER) >= 91) {
            if (_.isEmpty(argv.thickness)) {
                this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL_THICK', units:argv.units});
                argv.thickness = this.INFO_RESULT.gTOOL_THICK;
            }
            if (_.isEmpty(argv.user_params)) {
                this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL_USER', units:argv.units});
                argv.user_params = this.INFO_RESULT.gTOOL_USER;
            }
            if (_.isEmpty(argv.slots)) argv.slots = 'by_length';
            this.units({type:argv.units});
            this.COM('tools_tab_reset');
            this.COM('tools_set', {layer:argv.layer, thickness:argv.thickness, user_params:argv.user_params, slots:argv.slots});
            this.units({type:old_units});
        }
    }

    this.outputPostScript = function(argv) {
        var layerPar = _.assign({
            layer: argv.layer,
            angle: 0,
            mirror: 'no',
            x_scale: '1',
            y_scale: '1',
            comp: 0,
            polarity: 'positive',
            setupfile: '',
            setupfiletmp: '',
            line_units: 'inch'
        },argv.layer_params);
        var outputPar = _.assign({
            job: argv.job,
            step: argv.step,
            format: 'PostScript',
            dir_path: argv.dir_path,
            prefix: '',
            suffix: '.ps',
            break_sr: 'yes',
            break_symbols: 'yes',
            break_arc: 'yes',
            scale_mode: 'all',
            surface_mode: 'contour',
            min_brush: 1,
            x_anchor: 0,
            y_anchor: 0,
            x_offset: 0,
            y_offset: 0,
            line_units: 'inch',
            override_online: 'yes',
            params_opt: 'yes',
            orientation: 'automatic',
            title_opt: 'fix',
            title: '',
            size_mode: 'A4',
            width: 0,
            height: 0,
            scale: 0,
            output_files: 'multiple'
        },argv.output_params);
        this.COM('output_layer_reset');
        this.COM('output_layer_set', layerPar);
        this.COM('output', outputPar);
    }

    this.outputRs274x = function(argv) {
        var layerPar = _.assign({
            layer: argv.layer,
            angle: 0,
            mirror: 'no',
            x_scale: '1',
            y_scale: '1',
            comp: 0,
            polarity: 'positive',
            setupfile: '',
            setupfiletmp: '',
            line_units: 'inch'
        },argv.layer_params);
        var outputPar = _.assign({
            job: argv.job,
            step: argv.step,
            format: 'Gerber274x',
            dir_path: argv.dir_path,
            prefix: '',
            suffix: '',
            break_sr: 'yes',
            break_symbols: 'yes',
            break_arc: 'no',
            scale_mode: 'all',
            surface_mode: 'contour',
            min_brush: 1,
            units: 'inch',
            coordinates: 'absolute',
            zeroes: 'none',
            nf1: 2,
            nf2: 6,
            x_anchor: 0,
            y_anchor: 0,
            wheel: ''
        },argv.output_params);
        this.COM('output_layer_reset');
        this.COM('output_layer_set', layerPar);
        this.COM('output', outputPar);
    }

    this.outputDxf = function(argv) {
        var layerPar = _.assign({
            layer: argv.layer,
            angle: 0,
            mirror: 'no',
            x_scale: '1',
            y_scale: '1',
            comp: 0,
            polarity: 'positive',
            setupfile: '',
            setupfiletmp: '',
            line_units: 'inch'
        },argv.layer_params);
        var outputPar = _.assign({
            job: argv.job,
            step: argv.step,
            format: 'DXF',
            dir_path: argv.dir_path,
            prefix: '',
            suffix: '.dxf',
            break_sr: 'yes',
            break_symbols: 'yes',
            break_arc: 'no',
            scale_mode: 'all',
            surface_mode: 'contour',
            min_brush: 1,
            units: 'inch',
            x_anchor: 0,
            y_anchor: 0,
            x_offset: 0,
            y_offset: 0,
            line_units: 'inch',
            override_online: 'yes',
            pads_2circles: 'yes',
            draft: 'no',
            contour_to_hatch: 'no',
            output_files: 'multiple'
        },argv.output_params);
        this.COM('output_layer_reset');
        this.COM('output_layer_set', layerPar);
        this.COM('output', outputPar);
    }

    this.isLayerExists = function(argv) {
        this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
        for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
            var item = this.INFO_RESULT.gROWname[n];
            if (item == argv.layer) return true;
        }
        return false;
    }

    this.createLayer = function(argv) {
        var self = this;
        if (_.isEmpty(argv.context)) argv.context = 'misc';
        if (_.isEmpty(argv.type)) argv.type = 'signal';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.ins_layer)) argv.ins_layer = '';
        argv.layer = [].concat(argv.layer);
        if (_.toString(argv.delete_exists) == 'yes') {
            this.deleteLayer({job:argv.job, layer:argv.layer});
        }

        if (!_.isEmpty(argv.before)) {
            argv.ins_layer = argv.before;
        }
        else if (!_.isEmpty(argv.after)) {
            this.INFO({entity_type:'matrix', entity_path:argv.job+'/matrix', data_type:'row', parameters:'name'});
            for (var n = 0; n < this.INFO_RESULT.gROWname.length; n++) {
                if (this.INFO_RESULT.gROWname[n] == argv.after) {
                    argv.ins_layer = n+1 < this.INFO_RESULT.gROWname.length ? this.INFO_RESULT.gROWname[n+1] : '';
                    break;
                }
            }
        }
        _.forEach(argv.layer, function(layer){
            self.COM('create_layer', {layer:layer, context:argv.context, type:argv.type, polarity:argv.polarity, ins_layer:argv.ins_layer});
        });
    }

    this.deleteLayer = function(argv) {
        var self = this;
        var matrix;
        var err = this.ERROR_HANDLE;
        this.ERROR_HANDLE = 0;
        if (!_.isEmpty(argv.job)) {
            matrix = this.getMatrix({job:argv.job});
        }
        argv.layer = [].concat(argv.layer);
        var layers = [];
        _.forEach(argv.layer, function(item){
            if (/^\~(.*)$/.test(item)) {
                var tmp = RegExp.$1;
                if (!_.isEmpty(argv.job)) {
                    _.forEach(_.sortBy(_.keys(matrix), function(key){return _.toNumber(matrix[key][row])}), function(layer){
                        var re = new RegExp(tmp);
                        if (re.test(layer)) layers.push(layer);
                    });
                }
            }
            else {
                layers.push(item);
            }
        });

        _.forEach(layers, function(layer){
            if (!_.isEmpty(argv.job)) {
                if (matrix.hasOwnProperty(layer)) {
                    self.COM('delete_layer', {layer:layer});
                }
            }
            else {
                self.COM('delete_layer', {layer:layer});
            }
        });
        if (!_.isEmpty(argv.job) && !_.isEmpty(argv.step)) {
            this.openStep({job:argv.job, name:argv.step});
        }
        this.ERROR_HANDLE = err;
    }

    this.getLayerLimits = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        if(argv.options) {
            this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'limits', options: argv.options});
        } else {
            this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'limits'});
        }
        return {
            xmin: this.INFO_RESULT.gLIMITSxmin,
            ymin: this.INFO_RESULT.gLIMITSymin,
            xmax: this.INFO_RESULT.gLIMITSxmax,
            ymax: this.INFO_RESULT.gLIMITSymax,
            xsize: this.INFO_RESULT.gLIMITSxmax - this.INFO_RESULT.gLIMITSxmin,
            ysize: this.INFO_RESULT.gLIMITSymax - this.INFO_RESULT.gLIMITSymin,
        };
    }

    this.getSymbolLimits = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'symbol', entity_path:argv.job+'/'+argv.symbol, data_type:'limits'});
        return {
            xmin: this.INFO_RESULT.gLIMITSxmin,
            ymin: this.INFO_RESULT.gLIMITSymin,
            xmax: this.INFO_RESULT.gLIMITSxmax,
            ymax: this.INFO_RESULT.gLIMITSymax,
            xsize: this.INFO_RESULT.gLIMITSxmax - this.INFO_RESULT.gLIMITSxmin,
            ysize: this.INFO_RESULT.gLIMITSymax - this.INFO_RESULT.gLIMITSymin,
        };
    }

    this.getProfileLimits = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'PROF_LIMITS'});
        return {
            xmin: this.INFO_RESULT.gPROF_LIMITSxmin,
            ymin: this.INFO_RESULT.gPROF_LIMITSymin,
            xmax: this.INFO_RESULT.gPROF_LIMITSxmax,
            ymax: this.INFO_RESULT.gPROF_LIMITSymax,
            xsize: this.INFO_RESULT.gPROF_LIMITSxmax - this.INFO_RESULT.gPROF_LIMITSxmin,
            ysize: this.INFO_RESULT.gPROF_LIMITSymax - this.INFO_RESULT.gPROF_LIMITSymin,
        };
    }

    this.affectedLayer = function(argv) {
        var self = this;
        if (!_.isEmpty(argv.layer)) {
            argv.mode = 'single';
        }
        else {
            argv.mode = 'all';
        }
        argv.layer = [].concat(argv.layer);
        if (!_.isEmpty(argv.clear_before) && argv.clear_before != 'no') {
            this.clearLayers();
            this.affectedLayer({model:'all', affected:'no'});
        }
        if (argv.mode == 'single') {
            _.forEach(argv.layer, function(layer){
                self.COM('affected_layer', {name:layer, mode:argv.mode, affected:argv.affected});
            });
        }
        else {
            this.COM('affected_layer', {name:'', mode:argv.mode, affected:argv.affected});
        }
    }

    this.getSelectCount = function() {
        return this.COM('get_select_count');
    }

    this.selCopyOther = function(argv) {
        if (_.isEmpty(argv.dest)) {
            if (!_.isEmpty(argv.target_layer) && !/^\s*$/.test(argv.target_layer)) {
                argv.dest = 'layer_name';
            }
            else {
                argv.dest = 'affected_layers';
            }
        }
        if (_.isEmpty(argv.invert)) argv.invert = 'no';
        if (_.isEmpty(argv.dx)) argv.dx = 0;
        if (_.isEmpty(argv.dy)) argv.dy = 0;
        if (_.isEmpty(argv.size)) argv.size = 0;
        this.COM('sel_copy_other', {dest:argv.dest, target_layer:argv.target_layer, invert:argv.invert, dx:argv.dx, dy:argv.dy, size:argv.size});
    }

    this.selMoveOther = function(argv) {
        if (_.isEmpty(argv.invert)) argv.invert = 'no';
        if (_.isEmpty(argv.dx)) argv.dx = 0;
        if (_.isEmpty(argv.dy)) argv.dy = 0;
        if (_.isEmpty(argv.size)) argv.size = 0;
        this.COM('sel_move_other', argv);
    }

    this.clearLayers = function() {
        this.COM('clear_layers');
    }

    this.workLayer = function(argv) {
        if (_.isEmpty(argv.display_number)) argv.display_number = 1;
        if (!_.isEmpty(argv.clear_before) && argv.clear_before != 'no') {
            this.clearLayers();
            this.affectedLayer({mode:'all', affected:'no'});
        }
        this.COM('display_layer', {name:argv.name, display:'yes', number:argv.display_number});
        this.COM('work_layer', {name:argv.name});
    }

    this.zoomArea = function(argv) {
        this.COM('zoom_area', {x1:argv.x1, y1:argv.y1, x2:argv.x2, y2:argv.y2});
    }

    this.units = function(argv) {
        this.COM('units', {type:argv.type});
    }

    this.selectByFilter = function(argv) {
        if (_.isEmpty(argv.operation)) argv.operation = 'select';
        if (_.isEmpty(argv.intersect_area)) argv.intersect_area = 'no';
        if (_.isEmpty(argv.area_type)) argv.area_type = 'none';
        if (_.isEmpty(argv.filter_name)) argv.filter_name = 'popup';
        if (_.isEmpty(argv.layer)) argv.layer = '';
        if (_.isEmpty(argv.lines_only)) argv.lines_only = 'no';
        if (_.isEmpty(argv.ovals_only)) argv.ovals_only = 'no';
        if (_.isEmpty(argv.min_len)) argv.min_len = '0';
        if (_.isEmpty(argv.max_len)) argv.max_len = '0';
        if (_.isEmpty(argv.min_angle)) argv.min_angle = '0';
        if (_.isEmpty(argv.max_angle)) argv.max_angle = '0';
        if (_.isEmpty(argv.profile)) argv.profile = 'all';
        this.filterSet({
            filter_name: argv.filter_name,
            feat_types: argv.feat_types,
            polarity: argv.polarity,
            dcode: argv.dcode,
            attribute: argv.attribute,
            profile: argv.profile,
            include_syms: argv.include_syms,
            exclude_syms: argv.exclude_syms,
            text: argv.text
        });

        if (!_.isEmpty(argv.area_rect)) {
            this.COM('filter_area_strt');
            this.COM('filter_area_xy', {x: argv.area_rect.x1, y: argv.area_rect.y1});
            this.COM('filter_area_xy', {x: argv.area_rect.x2, y: argv.area_rect.y2});
            argv.area_type = 'rectangle';
            if (_.isEmpty(argv.inside_area)) argv.inside_area = 'yes';
        }
        if (_.isEmpty(argv.inside_area)) argv.inside_area = 'no';
        this.COM('filter_area_end', {
            layer: argv.layer,
            filter_name: argv.filter_name,
            operation: argv.operation,
            area_type: argv.area_type,
            inside_area: argv.inside_area,
            intersect_area: argv.intersect_area,
            lines_only: argv.lines_only,
            ovals_only: argv.ovals_only,
            min_len: argv.min_len,
            max_len: argv.max_len,
            min_angle: argv.min_angle,
            max_angle: argv.max_angle
        });
        this.COM('filter_reset', {filter_name: argv.filter_name});
    }

    this.highlightByFilter = function(argv) {
        if (_.isEmpty(argv.filter_name)) argv.filter_name = 'popup';
        if (_.isEmpty(argv.layer)) argv.layer = '';
        if (_.isEmpty(argv.lines_only)) argv.lines_only = 'no';
        if (_.isEmpty(argv.ovals_only)) argv.ovals_only = 'no';
        if (_.isEmpty(argv.min_len)) argv.min_len = '0';
        if (_.isEmpty(argv.max_len)) argv.max_len = '0';
        if (_.isEmpty(argv.min_angle)) argv.min_angle = '0';
        if (_.isEmpty(argv.max_angle)) argv.max_angle = '0';
        if (_.isEmpty(argv.profile)) argv.profile = 'all';
        this.filterSet({
            filter_name: argv.filter_name,
            feat_types: argv.feat_types,
            polarity: argv.polarity,
            dcode: argv.dcode,
            attribute: argv.attribute,
            profile: argv.profile,
            include_syms: argv.include_syms,
            exclude_syms: argv.exclude_syms,
        });

        this.COM('filter_highlight', {
            layer: argv.layer,
            filter_name: argv.filter_name,
            lines_only: argv.lines_only,
            ovals_only: argv.ovals_only,
            min_len: argv.min_len,
            max_len: argv.max_len,
            min_angle: argv.min_angle,
            max_angle: argv.max_angle
        });
        this.COM('filter_reset', {filter_name: argv.filter_name});
    }

    this.selRefFeat = function(argv) {
        var filter = argv.filter;
        delete argv.filter;
        if (_.isEmpty(filter)) filter = {filter_name: 'popup'};
        if (_.isEmpty(filter.filter_name)) filter.filter_name = 'popup';
        this.filterSet(filter);
        if (_.isEmpty(argv.f_types)) argv.f_types = 'line\;pad\;surface\;arc\;text';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive\;negative';
        if (_.isEmpty(argv.use)) argv.use = 'filter';
        if (_.isEmpty(argv.mode)) argv.mode = 'touch';
        this.COM('sel_ref_feat', argv);
        this.COM('filter_reset', {filter_name: filter.filter_name});
    }

    this.filterSet = function(argv) {
        var self = this;
        if (_.isEmpty(argv.filter_name)) argv.filter_name = 'popup';
        this.COM('filter_reset', {filter_name: argv.filter_name});
        if (!_.isEmpty(argv.feat_types)) {
            this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', feat_types: argv.feat_types});
        }
        if (!_.isEmpty(argv.polarity)) {
            this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', polarity: argv.polarity});
        }
        if (!_.isEmpty(argv.dcode)) {
            this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', dcode: argv.dcode});
        }
        if (!_.isEmpty(argv.include_syms)) {
            if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
                this.COM('set_filter_symbols', {filter_name: argv.filter_name, exclude_symbols: 'no', symbols: argv.include_syms});
            }
            else {
                this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', include_syms: argv.include_syms});
            }
        }
        if (!_.isEmpty(argv.exclude_syms)) {
            if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
                this.COM('set_filter_symbols', {filter_name: argv.filter_name, exclude_symbols: 'yes', symbols: argv.exclude_syms});
            }
            else {
                this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', exclude_syms: argv.exclude_syms});
            }
        }
        if (!_.isEmpty(argv.attribute)) {
            var attr = [];
            if (typeof(argv.attribute) == 'string') {
                attr.push({attribute: argv.attribute, condition: 'no'});
            }
            else if (_.isPlainObject(argv.attribute)) {
                attr.push(_.assign(argv.attribute, {condition: 'yes'}));
            }
            else if (_.isArray(argv.attribute)) {
                _.forEach(argv.attribute, function(item){
                    if (typeof(item) == 'string') {
                        attr.push({attribute: item, condition: 'no'});
                    }
                    else if (_.isPlainObject(item)) {
                        attr.push(_.assign(item, {condition: 'yes'}));
                    }
                })
            }
            if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
                _.forEach(attr, function(item){
                    self.COM('set_filter_attributes', _.assign(item, {filter_name: argv.filter_name, exclude_attributes: 'no'}));
                });
                this.COM('set_filter_logic', {filter_name: argv.filter_name, criteria: 'inc_attr', logic: 'or'});
            }
            else {
                _.forEach(attr, function(item){
                    self.COM('filter_atr_set', _.assign(item, {filter_name: argv.filter_name}));
                });
            }
        }
        if (!_.isEmpty(argv.profile)) {
            this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', profile: argv.profile});
        }
        if (!_.isEmpty(argv.text)) {
            this.COM('filter_set', {filter_name: argv.filter_name, update_popup: 'no', text: argv.text});
        }
    }

    this.checkInout = function(argv) {
        if (_.isEmpty(argv.type)) argv.type = 'job';
        this.VOF();
        var ans = this.COM('check_inout', {mode:'test', type:argv.type, job:argv.job});
        var user = ans.split(' ')[1];
        ans = ans.split(' ')[0];
        if (argv.mode == 'test') {
            return ans == 'yes' ? user : 0;
        }
        else {
            if (ans == 'yes' && argv.mode == 'out') return ;
            if (ans == 'no' && argv.mode == 'in') return ;
            this.COM('check_inout', {mode:argv.mode, type:argv.type, job:argv.job});
        }
        this.VON();
    }

    this.saveJob = function(argv) {
        if (_.isEmpty(argv.override)) argv.override = 'no';
        this.COM('save_job', {job:argv.job, override:argv.override});
    }

    this.copyJob = function(argv) {
        if (_.isEmpty(argv.source_name)) argv.source_name = argv.source_job;
        if (_.isEmpty(argv.dest_name)) argv.dest_name = argv.dest_job;
        this.COM('copy_entity', {type:'job', source_job:argv.source_job, source_name:argv.source_name, dest_job:argv.dest_job, dest_name:argv.dest_name, dest_database:argv.dest_database});
    }

    this.copyLayer = function(argv) {
        if (_.isEmpty(argv.mode)) argv.mode = 'replace';
        if (_.isEmpty(argv.invert)) argv.invert = 'no';
        if (!_.isEmpty(argv.dest_layer)) {
            argv.dest = 'layer_name';
        }
        else {
            argv.dest = 'affected_layer';
        }
        this.COM('copy_layer', {source_job:argv.source_job, source_step:argv.source_step, source_layer:argv.source_layer, dest:argv.dest, dest_layer:argv.dest_layer, mode:argv.mode, invert:argv.invert});
    }

    this.getChecklist = function(argv) {
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'CHECKS_LIST'});
        return this.INFO_RESULT.gCHECKS_LIST;
    }

    this.chklistFromLib = function(argv) {
        this.COM('chklist_from_lib', {chklist:argv.chklist});
    }

    this.createChklist = function(argv) {
        var self = this;
        this.COM('chklist_create', {chklist:argv.chklist});
        this.COM('chklist_open', {chklist:argv.chklist});
        var items = [];
        if (_.isPlainObject(argv.items)) {
            _.forEach(_.sortBy(_.keys(argv.items), function(key){return _.toNumber(argv.items[key].nact)}), function(k) {
                argv.items[k].name = k;
                items.push(argv.items[k]);
            });
        }
        else {
            items = argv.items;
        }
        this.COM('chklist_pclear');
        var n = 0;
        _.forEach(items, function(item){
            if (!_.isPlainObject(item)) item = {name:item, action:item};
            item.nact = ++n;
            if (_.isEmpty(item.name)) item.name = item.action;
            if (_.isEmpty(item.action)) item.action = item.name;
            self.COM('chklist_single', {action:item.action, show:'no'});
            self.COM('chklist_pcopy', {chklist:item.action, nact:1});
        });
        this.COM('chklist_ppaste', {chklist:argv.chklist, row:0});

        var ret = {};
        _.forEach(items, function(item){
            if (!_.isEmpty(item.erf)) {
                self.COM('chklist_erf', {chklist:argv.chklist, nact:item.nact, erf:item.erf});
            }
            if (!_.isEmpty(item.params)) {
                self.chklistCupd({chklist:argv.chklist, nact:item.nact, params:item.params});
            }
            ret[item.name] = item;
        });
        return ret;
    }

    this.isChklistExists = function(argv) {
        var check_list = this.getChecklist({job:argv.job, step:argv.step});
        return _.some(check_list, function(item){return item == argv.chklist});
    }

    this.chklistShow = function(argv) {
        this.COM('chklist_open', {chklist:argv.chklist});
        this.COM('chklist_show', {chklist:argv.chklist});
    }

    this.chklistRun = function(argv) {
        if (_.isEmpty(argv.area)) argv.area = 'profile';
        if (_.isEmpty(argv.num)) argv.num = 'a';
        this.COM('chklist_run', {chklist:argv.chklist, nact:argv.nact, area:argv.area});
    }

    this.getCheckMeas = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        var options = [];
        if (!_.isEmpty(argv.index)) options.push('index');
        if (!_.isEmpty(argv.nact)) options.push('action='+argv.nact);
        if (!_.isEmpty(argv.category)) options.push('category='+argv.category);
        if (!_.isEmpty(argv.severity)) options.push('severity='+argv.severity);
        if (!_.isEmpty(argv.layer)) options.push('layer='+argv.layer);
        var cshfile = this.INFO({units:argv.units, entity_type:'check', entity_path:argv.job+'/'+argv.step+'/'+argv.chklist, data_type:'MEAS', options:options.join('+'), parse:'no'});
        var lines = fs.readFile(cshfile).split(/\r?\n/);
        fs.unlink(cshfile);
        return lines;
    }

    this.runSingleDfm = function(argv) {
        if (_.isEmpty(argv.show)) argv.show = 'no';
        if (_.isEmpty(argv.mode)) argv.mode = 'regular';
        if (_.isEmpty(argv.area)) argv.area = 'profile';
        this.COM('chklist_single', {action:argv.chklist, show:argv.show});
        if (!_.isEmpty(argv.erf)) {
            this.COM('chklist_erf', {chklist:argv.chklist, nact:1, erf:argv.erf});
        }
        var pars = '';
        for (var item in argv.params) {
            pars += '(' + item + '=' + argv.params[item] + ')';
        }
        argv.params = '(' + pars + ')';
        this.COM('chklist_cupd', {chklist:argv.chklist, params:argv.params, mode:argv.mode});
        this.COM('chklist_run', {chklist:argv.chklist, nact:1, area:argv.area});
        if (_.toString(argv.show_res) == 'yes') {
            this.COM('chklist_res_show', {chklist:argv.chklist, nact:1, x:0, y:0, w:0, h:0});
        }
    }

    this.copperArea = function(argv) {
        if (_.isEmpty(argv.layer2)) argv.layer2 = '';
        if (_.isEmpty(argv.drills)) argv.drills = 'no';
        if (_.isEmpty(argv.drills_source)) argv.drills_source = 'matrix';
        if (_.isEmpty(argv.thickness)) argv.thickness = 0;
        if (_.isEmpty(argv.resolution_value)) argv.resolution_value = 1;
        if (_.isEmpty(argv.x_boxes)) argv.x_boxes = 3;
        if (_.isEmpty(argv.y_boxes)) argv.y_boxes = 3;
        if (_.isEmpty(argv.area)) argv.area = 'no';
        if (_.isEmpty(argv.x1)) argv.x1 = 0;
        if (_.isEmpty(argv.y1)) argv.y1 = 0;
        if (_.isEmpty(argv.x2)) argv.x2 = 0;
        if (_.isEmpty(argv.y2)) argv.y2 = 0;
        if (_.isEmpty(argv.dist_map)) argv.dist_map = 'yes';
        if (_.isEmpty(argv.f_type)) argv.f_type = 'all';
        if (_.isEmpty(argv.out_file)) argv.out_file = '';
        if (_.isEmpty(argv.out_layer)) argv.out_layer = 'first';
        this.COM('copper_area', argv);
        return {
            area: this._COMANS.split(' ')[0],
            percent: this._COMANS.split(' ')[1]
        };
    }

    this.exposedArea = function(argv) {
        if (_.isEmpty(argv.layer2)) argv.layer2 = '';
        if (_.isEmpty(argv.mask2)) argv.mask2 = '';
        if (_.isEmpty(argv.drills)) argv.drills = 'no';
        if (_.isEmpty(argv.drills_source)) argv.drills_source = 'matrix';
        if (_.isEmpty(argv.thickness)) argv.thickness = 0;
        if (_.isEmpty(argv.resolution_value)) argv.resolution_value = 1;
        if (_.isEmpty(argv.x_boxes)) argv.x_boxes = 3;
        if (_.isEmpty(argv.y_boxes)) argv.y_boxes = 3;
        if (_.isEmpty(argv.area)) argv.area = 'no';
        if (_.isEmpty(argv.dist_map)) argv.dist_map = 'yes';
        this.COM('exposed_area', argv);
        return {
            area: this._COMANS.split(' ')[0],
            percent: this._COMANS.split(' ')[1]
        };
    }

    this.selContourize = function(argv) {
        argv = _.assign({}, argv);
        if (_.isEmpty(argv.accuracy)) argv.accuracy = 0;
        if (_.isEmpty(argv.break_to_islands)) argv.break_to_islands = 'yes';
        if (_.isEmpty(argv.clean_hole_size)) argv.clean_hole_size = 0;
        if (_.isEmpty(argv.clean_hole_mode)) argv.clean_hole_mode = 'x_and_y';
        this.COM('sel_contourize', argv);
    }

    this.zoomHome = function() {
        this.COM('zoom_home');
    }

    this.panelSize = function(argv) {
        this.COM('panel_size', {width:argv.width, height:argv.height});
    }

    this.srFill = function(argv) {
        if (_.isEmpty(argv.type)) argv.type = 'solid';
        if (_.isEmpty(argv.origin_type)) argv.origin_type = 'datum';
        if (_.isEmpty(argv.solid_type)) argv.solid_type = 'surface';
        if (_.isEmpty(argv.min_brush)) argv.min_brush = 1;
        if (_.isEmpty(argv.use_arcs)) argv.use_arcs = 'yes';
        if (_.isEmpty(argv.symbol)) argv.symbol = '';
        if (_.isEmpty(argv.dx)) argv.dx = 1;
        if (_.isEmpty(argv.dy)) argv.dy = 1;
        if (_.isEmpty(argv.break_partial)) argv.break_partial = 'yes';
        if (_.isEmpty(argv.cut_prims)) argv.cut_prims = 'no';
        if (_.isEmpty(argv.outline_draw)) argv.outline_draw = 'no';
        if (_.isEmpty(argv.outline_width)) argv.outline_width = 0;
        if (_.isEmpty(argv.outline_invert)) argv.outline_invert = 'no';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.step_margin_x)) argv.step_margin_x = 0;
        if (_.isEmpty(argv.step_margin_y)) argv.step_margin_y = 0;
        if (_.isEmpty(argv.step_max_dist_x)) argv.step_max_dist_x = 100;
        if (_.isEmpty(argv.step_max_dist_y)) argv.step_max_dist_y = 100;
        if (_.isEmpty(argv.sr_margin_x)) argv.sr_margin_x = 0;
        if (_.isEmpty(argv.sr_margin_y)) argv.sr_margin_y = 0;
        if (_.isEmpty(argv.sr_max_dist_x)) argv.sr_max_dist_x = 0;
        if (_.isEmpty(argv.sr_max_dist_y)) argv.sr_max_dist_y = 0;
        if (_.isEmpty(argv.nest_sr)) argv.nest_sr = 'no';
        if (_.isEmpty(argv.consider_feat)) argv.consider_feat = 'no';
        if (_.isEmpty(argv.feat_margin)) argv.feat_margin = 0;
        if (_.isEmpty(argv.consider_drill)) argv.consider_drill = 'no';
        if (_.isEmpty(argv.drill_margin)) argv.drill_margin = 0;
        if (_.isEmpty(argv.consider_rout)) argv.consider_rout = 'no';
        if (_.isEmpty(argv.attributes)) argv.attributes = 'no';
        if (!_.isEmpty(argv.layer)) {
            argv.dest = 'layer_name';
        }
        else {
            argv.dest = 'affected_layers';
        }
        this.COM('fill_params', _.pick(argv, ['type','origin_type','min_brush','use_arcs','symbol','dx','dy','break_partial','cut_prims','outline_draw','outline_width','outline_invert']));
        this.COM('sr_fill', _.pick(argv, ['polarity','step_margin_x','step_margin_y','step_max_dist_x','step_max_dist_y','sr_margin_x','sr_margin_y','sr_max_dist_x','sr_max_dist_y','nest_sr',
        'consider_feat','feat_margin','consider_drill','drill_margin','consider_rout','dest','layer','attributes']));
    }

    this.addPad = function(argv) {
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        delete argv.dcode;
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.angle)) argv.angle = '0';
        if (_.isEmpty(argv.mirror)) argv.mirror = 'no';
        if (_.isEmpty(argv.nx)) argv.nx = '1';
        if (_.isEmpty(argv.ny)) argv.ny = '1';
        if (_.isEmpty(argv.dx)) argv.dx = '0';
        if (_.isEmpty(argv.dy)) argv.dy = '0';
        if (_.isEmpty(argv.xscale)) argv.xscale = '1';
        if (_.isEmpty(argv.yscale)) argv.yscale = '1';
        this.COM('add_pad', argv);
    }

    this.addLine = function(argv) {
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        delete argv.dcode;
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        this.COM('add_line', argv);
    }

    this.addArc = function(argv) {
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        delete argv.dcode;
        if (_.isEmpty(argv.direction)) argv.direction = 'ccw';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        this.COM('add_arc', argv);
    }

    this.transCoordinate = function(argv) {
        if (_.isEmpty(argv.mirror)) argv.mirror = 'no';
        if (_.isEmpty(argv.shiftx)) argv.shiftx = 0;
        if (_.isEmpty(argv.shifty)) argv.shifty = 0;
        if (_.isEmpty(argv.angle)) argv.angle = 0;
        argv.cx += argv.shiftx;
        argv.cy += argv.shifty;
        argv.x += argv.shiftx;
        argv.y += argv.shifty;
        
        var ret = {};
        if (argv.angle == 90) {
            ret.x = argv.cx + argv.y - argv.cy;
            ret.y = argv.cy - (argv.x - argv.cx);
        }
        else if (argv.angle == 180) {
            ret.x = argv.cx - (argv.x - argv.cx);
            ret.y = argv.cy - (argv.y - argv.cy);
        }
        else if (argv.angle == 270) {
            ret.x = argv.cx - (argv.y - argv.cy);
            ret.y = argv.cy + (argv.x - argv.cx);
        }
        else if (argv.angle == 0) {
            ret.x = argv.x;
            ret.y = argv.y;
        }
        if (_.toLower(argv.mirror) == 'yes') {
            ret.x = argv.cx + (argv.cx - ret.x);
        }
        return ret;
    }

    this.addText = function(argv) {
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        delete argv.dcode;
        if (_.isEmpty(argv.type)) argv.type = 'string';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.angle)) argv.angle = '0';
        if (_.isEmpty(argv.mirror)) argv.mirror = 'no';
        if (_.isEmpty(argv.fontname)) argv.fontname = 'standard';
        if (_.isEmpty(argv.bar_type)) argv.bar_type = 'UPC39';
        if (_.isEmpty(argv.bar_char_set)) argv.bar_char_set = 'full_ascii';
        if (_.isEmpty(argv.bar_checksum)) argv.bar_checksum = 'no';
        if (_.isEmpty(argv.bar_background)) argv.bar_background = 'yes';
        if (_.isEmpty(argv.bar_add_string)) argv.bar_add_string = 'yes';
        if (_.isEmpty(argv.bar_add_string_pos)) argv.bar_add_string_pos = 'top';
        if (_.isEmpty(argv.bar_width)) argv.bar_width = 0.008;
        if (_.isEmpty(argv.bar_height)) argv.bar_height = 0.2;
        if (_.isEmpty(argv.ver)) argv.ver = 1;
        if (_.isEmpty(argv.anchor)) argv.anchor = 'sw';
        var job = argv.job;
        delete argv.job;
        var txt = argv.text;
        txt.replace(/\$\$JOB/gi, job);
        var text_length = argv.text_length;
        delete argv.text_length;
        if (!_.isEmpty(text_length)) {
            txt = _.repeat('8', text_length);
        }
        var units = this.COM('get_units');
        if (!_.isEmpty(argv.w_factor)) {
            argv.line_width = units == 'inch' ? argv.w_factor * 12 : argv.w_factor * 304.8;
        }
        argv.w_factor = argv.line_width;
        delete argv.line_width;
        var text_width;
        if (units == 'inch') {
            argv.w_factor = argv.w_factor / 12;
            text_width = argv.x_size * 0.702781 + (argv.w_factor - 1) * 0.004053;
        }
        else {
            argv.w_factor = argv.w_factor / 304.8;
            text_width = argv.x_size * 0.702781 + (argv.w_factor - 1) * 0.102945;
        }
        if (!_.isEmpty(argv.all_x_size)) {
            argv.x_size = (argv.all_x_size - text_width) / argv.text.length;
            delete argv.all_x_size;
        }

        var anchor = argv.anchor;
        delete argv.anchor;
        var cx;
        var cy;
        if (anchor == 'center') {
            cx = argv.x;
            cy = argv.y;
        }
        else if (anchor == 'e') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y;
        }
        else if (anchor == 's') {
            cx = argv.x;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'w') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y;
        }
        else if (anchor == 'n') {
            cx = argv.x;
            cy = argv.y - argv.y_size / 2;
        }
        else if (anchor == 'se') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'sw') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'nw') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y - argv.y_size / 2;
        }
        else if (anchor == 'ne') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y - argv.y_size / 2;
        }

        var trans_anchor = argv.trans_anchor;
        delete argv.trans_anchor;
        if (_.toString(trans_anchor) == 'yes') {
            var tmp = this.transCoordinate({cx:argv.x, cy:argv.y, x:cx, y:cy, angle:argv.angle, mirror:'no'});
            cx = tmp.x;
            cy = tmp.y;
        }
        var x = cx - (argv.x_size * (txt.length - 1) + text_width) / 2;
        var y = cy - argv.y_size / 2;
        var tmp = this.transCoordinate({cx:cx, cy:cy, x:x, y:y, angle:argv.angle, mirror:argv.mirror});
        argv.x = tmp.x;
        argv.y = tmp.y;
        this.COM('add_text', argv);
    }

    this.getTextRect = function(argv) {
        if (_.isEmpty(argv.x)) argv.x = 0;
        if (_.isEmpty(argv.y)) argv.y = 0;
        if (_.isEmpty(argv.anchor)) argv.anchor = 'sw';
        if (_.isEmpty(argv.angle)) argv.angle = 0;
        if (_.isEmpty(argv.mirror)) argv.mirror = 'no';
        var units = argv.units;
        if (_.isEmpty(units)) units = 'inch';
        if (!_.isEmpty(argv.w_factor)) {
            argv.line_width = units == 'inch' ? argv.w_factor * 12 : argv.w_factor * 304.8;
        }
        argv.w_factor = argv.line_width;
        delete argv.line_width;
        var text_width;
        if (units == 'inch') {
            argv.w_factor = argv.w_factor / 12;
            text_width = argv.x_size * 0.702781 + (argv.w_factor - 1) * 0.004053;
        }
        else {
            argv.w_factor = argv.w_factor / 304.8;
            text_width = argv.x_size * 0.702781 + (argv.w_factor - 1) * 0.102945;
        }
        if (!_.isEmpty(argv.all_x_size)) {
            argv.x_size = (argv.all_x_size - text_width) / argv.text.length;
            delete argv.all_x_size;
        }

        var anchor = argv.anchor;
        delete argv.anchor;
        var cx;
        var cy;
        if (anchor == 'center') {
            cx = argv.x;
            cy = argv.y;
        }
        else if (anchor == 'e') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y;
        }
        else if (anchor == 's') {
            cx = argv.x;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'w') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y;
        }
        else if (anchor == 'n') {
            cx = argv.x;
            cy = argv.y - argv.y_size / 2;
        }
        else if (anchor == 'se') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'sw') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y + argv.y_size / 2;
        }
        else if (anchor == 'nw') {
            cx = argv.x + (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y - argv.y_size / 2;
        }
        else if (anchor == 'ne') {
            cx = argv.x - (argv.x_size * (txt.length - 1) + text_width) / 2;
            cy = argv.y - argv.y_size / 2;
        }

        var trans_anchor = argv.trans_anchor;
        delete argv.trans_anchor;
        if (_.toString(trans_anchor) == 'yes') {
            var tmp = this.transCoordinate({cx:argv.x, cy:argv.y, x:cx, y:cy, angle:argv.angle, mirror:'no'});
            cx = tmp.x;
            cy = tmp.y;
        }
        var x1 = cx - (argv.x_size * (argv.text.length - 1) + text_width) / 2;
        var y1 = cy - argv.y_size / 2;
        var x2 = cx + (argv.x_size * (argv.text.length - 1) + text_width) / 2;
        var y2 = cy + argv.y_size / 2;
        var ret = {cx:cx, cy:cy};
        var tmp = this.transCoordinate({cx:cx, cy:cy, x:x1, y:y1, angle:argv.angle, mirror:argv.mirror});
        ret.x1 = tmp.x;
        ret.y1 = tmp.y;
        tmp = this.transCoordinate({cx:cx, cy:cy, x:x2, y:y2, angle:argv.angle, mirror:argv.mirror});
        ret.x2 = tmp.x;
        ret.y2 = tmp.y;
        tmp = [ret.x1, ret.x2];
        ret.x1 = _.min(tmp);
        ret.x2 = _.max(tmp);
        tmp = [ret.y1, ret.y2];
        ret.y1 = _.min(tmp);
        ret.y2 = _.max(tmp);
        ret.xsize = Math.abs(ret.x2 - ret.x1);
        ret.ysize = Math.abs(ret.y2 - ret.y1);
        return ret;
    }

    this.addRectangle = function(argv) {
        if (_.isEmpty(argv.type)) argv.type = 'surface';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.symbol)) argv.symbol = 'r0';
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        if (argv.type == 'surface') {
            this.COM('fill_params', {type:'solid', origin_type:'datum', solid_type:'surface', min_brush:1, use_arcs:'yes', symbol:'', dx:0.1, dy:0.1, break_partial:'no', cut_prims:'no', outline_draw:'no', outline_width:0, outline_invert:'no'});
            this.COM('add_surf_strt');
            this.COM('add_surf_poly_strt', {x:argv.x1, y:argv.y1});
            this.COM('add_surf_poly_seg', {x:argv.x1, y:argv.y2});
            this.COM('add_surf_poly_seg', {x:argv.x2, y:argv.y2});
            this.COM('add_surf_poly_seg', {x:argv.x2, y:argv.y1});
            this.COM('add_surf_poly_seg', {x:argv.x1, y:argv.y1});
            this.COM('add_surf_poly_end');
            this.COM('add_surf_end', {attributes:argv.attributes, polarity:argv.polarity});
        }
        else {
            this.COM('add_polyline_strt');
            this.COM('add_polyline_xy', {x:argv.x1, y:argv.y1});
            this.COM('add_polyline_xy', {x:argv.x1, y:argv.y2});
            this.COM('add_polyline_xy', {x:argv.x2, y:argv.y2});
            this.COM('add_polyline_xy', {x:argv.x2, y:argv.y1});
            this.COM('add_polyline_xy', {x:argv.x1, y:argv.y1});
            this.COM('add_polyline_end', {attributes:argv.attributes, symbol:argv.symbol, polarity:argv.polarity});
        }
    }

    this.addPolygon = function(argv) {
        if (_.isEmpty(argv.type)) argv.type = 'surface';
        if (_.isEmpty(argv.polarity)) argv.polarity = 'positive';
        if (_.isEmpty(argv.symbol)) argv.symbol = 'r0';
        if (_.isEmpty(argv.attributes) || argv.attributes == 'no') {
            argv.attributes = 'no';
        }
        else if (argv.attributes == 'yes') {
            argv.attributes = 'yes';
        }
        else {
            this.setCurrentAttribute({attribute: argv.attributes});
            argv.attributes = 'yes';
        }
        if (argv.type == 'surface') {
            this.COM('fill_params', {type:'solid', origin_type:'datum', solid_type:'surface', min_brush:1, use_arcs:'yes', symbol:'', dx:0.1, dy:0.1, break_partial:'no', cut_prims:'no', outline_draw:'no', outline_width:0, outline_invert:'no'});
            this.COM('add_surf_strt');
            this.COM('add_surf_poly_strt', {x:argv.items[0].x, y:argv.items[0].y});
            for (var n = 1; n < argv.items.length; n++) {
                var p = argv.items[n];
                if (!_.isEmpty(p.xc)) {
                    this.COM('add_surf_poly_crv', {xc:p.xc, yc:p.yc, xe:p.xe, ye:p.ye, cw:p.cw});
                }
                else {
                    this.COM('add_surf_poly_seg', {x:p.x, y:p.y});
                }
            }
            this.COM('add_surf_poly_end');
            this.COM('add_surf_end', {attributes:argv.attributes, polarity:argv.polarity});
        }
        else {
            this.COM('add_polyline_strt');
            this.COM('add_polyline_xy', {x:argv.items[0].x, y:argv.items[0].y});
            for (var n = 1; n < argv.items.length; n++) {
                var p = argv.items[n];
                if (!_.isEmpty(p.xc)) {
                    this.COM('add_polyline_crv', {xc:p.xc, yc:p.yc, xe:p.xe, ye:p.ye, cw:p.cw});
                }
                else {
                    this.COM('add_polyline_xy', {x:p.x, y:p.y});
                }
            }
            this.COM('add_polyline_end', {attributes:argv.attributes, symbol:argv.symbol, polarity:argv.polarity});
        }
    }

    this.selFill = function(argv) {
        argv = _.assign({},argv);
        if (_.isEmpty(argv.type)) argv.type = 'solid';
        if (_.isEmpty(argv.origin_type)) argv.origin_type = 'datum';
        if (_.isEmpty(argv.solid_type)) argv.solid_type = 'fill';
        if (_.isEmpty(argv.min_brush)) argv.min_brush = 1;
        if (_.isEmpty(argv.use_arcs)) argv.use_arcs = 'yes';
        if (_.isEmpty(argv.symbol)) argv.symbol = '';
        if (_.isEmpty(argv.dx)) argv.dx = 1;
        if (_.isEmpty(argv.dy)) argv.dy = 1;
        if (_.isEmpty(argv.break_partial)) argv.break_partial = 'yes';
        if (_.isEmpty(argv.cut_prims)) argv.cut_prims = 'no';
        if (_.isEmpty(argv.outline_draw)) argv.outline_draw = 'no';
        if (_.isEmpty(argv.outline_width)) argv.outline_width = 0;
        if (_.isEmpty(argv.outline_invert)) argv.outline_invert = 'no';
        this.COM('fill_params', argv);
        this.COM('sel_fill');
    }

    this.selReverse = function() {
        this.COM('sel_reverse');
    }

    this.selDelete = function() {
        this.COM('sel_delete');
    }

    this.panelSR = function(argv) {
        if (_.isEmpty(argv.nx)) argv.nx = 1;
        if (_.isEmpty(argv.ny)) argv.ny = 1;
        if (_.isEmpty(argv.dx)) argv.dx = 0;
        if (_.isEmpty(argv.dy)) argv.dy = 0;
        if (_.isEmpty(argv.angle)) argv.angle = 0;
        if (_.isEmpty(argv.mirror)) argv.mirror = 'no';
        this.COM('sr_tab_add', {line:argv.line, step:argv.step, x:argv.x, y:argv.y, nx:argv.nx, ny:argv.ny, dx:argv.dx, dy:argv.dy, angle:argv.angle, mirror:argv.mirror});
    }

    this.flattenLayer = function(argv) {
        var job = argv.job;
        delete argv.job;
        var step = argv.step;
        delete argv.step;
        if (_.toString(job) != '' && _.toString(step) != '') {
            var sr = this.getSR1({job:job, step:step});
            if (sr.length > 0) {
                this.COM('flatten_layer', argv);
            }
            else {
                this.copyLayer({source_job:job, source_step:step, source_layer:argv.source_layer, dest_layer:argv.target_layer});
            }
        }
        else {
            this.COM('flatten_layer', argv);
        }
    }

    this.getSymbolFeatures = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        var cshfile = this.INFO({units:argv.units, entity_type:'symbol', entity_path:argv.job+'/'+argv.symbol, data_type:'FEATURES', parse:'no'});
        var file = fs.openFile(cshfile,'r');
        var ret = [];
        while (! file.atEnd()) {
            var item = file.readLine();
            if (/^###/.test(item)) continue;
            if (/^\s+$/.test(item)) continue;
            item = _.trimEnd(item);
            if (/^#[PLATS]/.test(item)) {
                var info = item.split(';')[0];
                var attr = item.split(';')[1];
                var attributes = [];
                if (_.toString(attr) != '') attributes = attr.split(',');
                var infos = info.split(' ');
                if (infos[0] == '#P') {
                    ret.push({
                        type: 'pad',
                        x: infos[1],
                        y: infos[2],
                        symbol: infos[3],
                        polarity: infos[4] == 'P' ? 'positive' : 'negative',
                        angle: infos[6],
                        mirror: infos[7] == 'Y' ? 'yes' : 'no',
                        attributes: attributes
                    });
                }
                else if (infos[0] == '#L') {
                    ret.push({
                        type: 'line',
                        xs: infos[1],
                        ys: infos[2],
                        xe: infos[3],
                        ye: infos[4],
                        symbol: infos[5],
                        polarity: infos[6] == 'P' ? 'positive' : 'negative',
                        attributes: attributes
                    });
                }
                else if (infos[0] == '#A') {
                    ret.push({
                        type: 'arc',
                        xs: infos[1],
                        ys: infos[2],
                        xe: infos[3],
                        ye: infos[4],
                        xc: infos[5],
                        yc: infos[6],
                        symbol: infos[7],
                        polarity: infos[8] == 'P' ? 'positive' : 'negative',
                        direction: infos[10] == 'Y' ? 'cw' : 'ccw',
                        attributes: attributes
                    });
                }
                else if (infos[0] == '#T') {
                    var text = _.slice(infos, 10, infos.length-1).join(' ');
                    text = text.replace(/^'|'$/g, '');
                    ret.push({
                        type: 'text',
                        x: infos[1],
                        y: infos[2],
                        fontname: infos[3],
                        polarity: infos[4] == 'P' ? 'positive' : 'negative',
                        angle: infos[5],
                        mirror: infos[6] == 'Y' ? 'yes' : 'no',
                        x_size: infos[7],
                        y_size: infos[8],
                        w_factor: infos[9],
                        text: text,
                        attributes: attributes
                    });
                }
                else if (infos[0] == '#S') {
                    ret.push({
                        type: 'surface'
                    });
                    //currently ignore surface
                }
            }
        }
        file.close();
        fs.unlink(cshfile);
        return ret.length > 0 ? ret : undefined;
    }

    this.getFeatures = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        var cshfile;
        if (!_.isEmpty(argv.options)) {
            cshfile = this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'FEATURES', options:argv.options, parse:'no'});
        }
        else {
            cshfile = this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'FEATURES', parse:'no'});
        }
        var file = fs.openFile(cshfile,'r');
        var ret = [];
        var surface = {};
        var surf_num = 1;
        while (! file.atEnd()) {
            var item = file.readLine();
            var index;
            if (/^###/.test(item)) continue;
            if (/^\s+$/.test(item)) continue;
            item = _.trimEnd(item);
            if(/feat_index/.test(argv.options)){
                if(/(^#\d+\s+)/.test(item)){
                    var tmp = item.split(/(^#\d+\s+)/);
                    item = tmp[2];
                    index = tmp[1].match(/\d+/)[0]
                } else if (/(^#\s+)/.test(item)) {
                    var tmp = item.split(/(^#\s+)/)
                    item = tmp[2]
                }
            }
            if (/^#[PLATS]/.test(item)) {
                var info = item.split(';')[0];
                var attr = item.split(';')[1];
                var attributes = [];
                if (_.toString(attr) != '') attributes = attr.split(',');
                var infos = info.split(' ');
                if (infos[0] == '#P') {
                    ret.push({
                        type: 'pad',
                        x: infos[1],
                        y: infos[2],
                        symbol: infos[3],
                        polarity: infos[4] == 'P' ? 'positive' : 'negative',
                        angle: infos[6],
                        mirror: infos[7] == 'Y' ? 'yes' : 'no',
                        attributes: attributes,
                        index:index
                    });
                }
                else if (infos[0] == '#L') {
                    ret.push({
                        type: 'line',
                        xs: infos[1],
                        ys: infos[2],
                        xe: infos[3],
                        ye: infos[4],
                        symbol: infos[5],
                        polarity: infos[6] == 'P' ? 'positive' : 'negative',
                        attributes: attributes,
                        index:index
                    });
                }
                else if (infos[0] == '#A') {
                    ret.push({
                        type: 'arc',
                        xs: infos[1],
                        ys: infos[2],
                        xe: infos[3],
                        ye: infos[4],
                        xc: infos[5],
                        yc: infos[6],
                        symbol: infos[7],
                        polarity: infos[8] == 'P' ? 'positive' : 'negative',
                        direction: infos[10] == 'Y' ? 'cw' : 'ccw',
                        attributes: attributes,
                        index:index
                    });
                }
                else if (infos[0] == '#T') {
                    var text = _.slice(infos, 10, infos.length-1).join(' ');
                    text = text.replace(/^'|'$/g, '');
                    ret.push({
                        type: 'text',
                        x: infos[1],
                        y: infos[2],
                        fontname: infos[3],
                        polarity: infos[4] == 'P' ? 'positive' : 'negative',
                        angle: infos[5],
                        mirror: infos[6] == 'Y' ? 'yes' : 'no',
                        x_size: infos[7],
                        y_size: infos[8],
                        w_factor: infos[9],
                        text: text,
                        attributes: attributes,
                        index:index
                    });
                }
                else if (infos[0] == '#S') {
                    surf_num++;
                    if (!_.isEmpty(argv.surface)) {
                        if (!surface.hasOwnProperty(surf_num)) surface[surf_num] = [];
                        surface[surf_num].push(item);
                        ret.push({
                            type: 'surface',
                            feats: surface[surf_num],
                            index:index
                        });
                    }
                    else {
                        ret.push({
                            type: 'surface',
                            index:index
                        });
                    }
                    //currently ignore surface
                }
            }
            else if (!_.isEmpty(argv.surface)) {
                if (/^#O[BSEC]/.test(item)) {
                    if (!surface.hasOwnProperty(surf_num)) surface[surf_num] = [];
                    surface[surf_num].push(item);
                }
            }
        }
        file.close();
        fs.unlink(cshfile);
        return ret.length > 0 ? ret : undefined;
    }

    this.copyStep = function(argv) {
        this.COM('copy_entity', _.assign({type:'step'}, argv));
    }

    this.selChangeSym = function(argv) {
        if (_.isEmpty(argv.reset_angle)) argv.reset_angle = 'no';
        this.COM('sel_change_sym', argv);
    }

    this.selBoardNetFeat = function(argv) {
        if (_.isEmpty(argv.operation)) argv.operation = 'select';
        if (_.isEmpty(argv.tol)) {
            var units = this.COM('get_units');
            if (units == 'inch') {
                argv.tol = 8.947;
            }
            else {
                argv.tol = 227.26;
            }
        }
        this.COM('sel_board_net_feat', argv);
    }

    this.displayLayer = function(argv) {
        if (_.isEmpty(argv.display)) argv.display = 'yes';
        if (_.isEmpty(argv.number)) argv.number = 1;
        this.COM('display_layer', argv);
    }

    this.getNcsetsList = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'ncsets_list'});
        return this.INFO_RESULT.gNCSETS_LIST;
    }

    this.getNcsetReg = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'ncset', entity_path:argv.job+'/'+argv.step+'/'+argv.layer+'/'+argv.name, data_type:'reg'});
        return {
            gREGxoff: this.INFO_RESULT.gREGxoff,
            gREGyoff: this.INFO_RESULT.gREGyoff,
            gREGmirror: this.INFO_RESULT.gREGmirror,
            gREGangle: this.INFO_RESULT.gREGangle,
            gREGxscale: this.INFO_RESULT.gREGxscale,
            gREGyscale: this.INFO_RESULT.gREGyscale,
            gREGscale_x_orig: this.INFO_RESULT.gREGscale_x_orig,
            gREGscale_y_orig: this.INFO_RESULT.gREGscale_y_orig,
            gREGxorigin: this.INFO_RESULT.gREGxorigin,
            gREGyorigin: this.INFO_RESULT.gREGyorigin,
            gREGversion: this.INFO_RESULT.gREGversion + 1
        };
    }

    this.selCreateStep = function(argv) {
        if (_.isEmpty(argv.x_datum)) argv.x_datum = 0;
        if (_.isEmpty(argv.y_datum)) argv.y_datum = 0;
        if (_.isEmpty(argv['delete'])) argv['delete'] = 'no';
        this.COM('sel_create_step', {step:argv.step, x_datum:argv.x_datum, y_datum:argv.y_datum, 'delete':argv['delete']});
    }

    this.selMoveRepeat = function(argv) {
        if (_.isEmpty(argv.x_repeats)) argv.x_repeats = 0;
        if (_.isEmpty(argv.y_repeats)) argv.y_repeats = 0;
        if (_.isEmpty(argv.delete_orig)) argv.delete_orig = 'yes';
        this.COM('sel_move_repeat', {dest_step:argv.dest_step, x_repeats:argv.x_repeats, y_repeats:argv.y_repeats, delete_orig:argv.delete_orig});
    }

    this.createSymbol = function(argv) {
        if (this.isSymbolExists({job:argv.job, symbol:argv.name})) {
            print('Symbol ' + argv.name + ' already exists in ' + argv.job + "!\n");
            return false;
        }
        this.COM('create_entity', {job:argv.job, is_fw:'no', type:'symbol', name:argv.name, db:'', fw_type:'form'});
        return true;
    }

    this.isSymbolExists = function(argv) {
        this.INFO({entity_type:'symbol', entity_path:argv.job+'/'+argv.symbol, data_type:'exists'});
        return (this.INFO_RESULT.gEXISTS == 'yes');
    }

    this.openSymbol = function(argv) {
        if (_.isEmpty(argv.iconic)) argv.iconic = 'no';
        this.COM('open_entity', {job:argv.job, type:'symbol', name:argv.name, iconic:argv.iconic});
        var ret = this._COMANS;
        this.COM('disp_on');
        this.COM('origin_on');
        if (_.isEmpty(this._STATUS)) {
            this.setGroup({group:ret});
            return ret;
        }
        else {
            return 0;
        }
    }

    this.closeSymbol = function() {
        this.COM('editor_page_close');
    }

    this.getCheckAttr = function(argv) {
        var ret = {};
        var attrs = [].concat(argv.attr);
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'check', entity_path:argv.job+'/'+argv.step+'/'+argv.checklist, data_type:'chk_attr', options:'action='+argv.nact});
        var ret = _.pick(_.zipObject(this.INFO_RESULT.gCHK_ATTRname, this.INFO_RESULT.gCHK_ATTRval), attrs);
        return _.isArray(argv.attr) ? ret : ret[argv.attr];
    }

    this.getProfile = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        var cshfile = this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'PROF', parse:'no'});
        var content = fs.readFile(cshfile);
        fs.unlink(cshfile);
        return content;
    }

    this.clearHighlight = function() {
        this.COM('clear_highlight');
    }

    this.selBreak = function(argv) {
        argv = _.assign({},argv);
        this.COM('sel_break', argv);
    }

    this.getUnits = function() {
        return this.COM('get_units');
    }

    this.getLayerSymsHist = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        if (!_.isEmpty(argv.options)) {
            this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'SYMS_HIST', options:argv.options});
        }
        else {
            this.INFO({units:argv.units, entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'SYMS_HIST'});
        }
        var ret = {};
        for (var n = 0; n < this.INFO_RESULT.gSYMS_HISTsymbol.length; n++) {
            var symbol = this.INFO_RESULT.gSYMS_HISTsymbol[n];
            ret[symbol] = {
                symbol: symbol,
                line: this.INFO_RESULT.gSYMS_HISTline[n],
                pad: this.INFO_RESULT.gSYMS_HISTpad[n],
                arc: this.INFO_RESULT.gSYMS_HISTarc[n]
            };
            if (/^[rs]\d+(\.\d+)?$/.test(symbol)) {
                ret[symbol].size = symbol.replace(/^[rs]/, '');
            }
            else if (/^(rect|oval|oval_h)\d+(\.\d+)?x\d+(\.\d+)?(x[cr]\d+(\.\d+)?(x\d+)?)?$/.test(symbol)) {
                /(?:rect|oval|oval_h)(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)/.test(symbol);
                ret[symbol].width = RegExp.$1;
                ret[symbol].height = RegExp.$2;
            }
        }
        return ret;
    }

    this.getSymbolSymsHist = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'symbol', entity_path:argv.job+'/'+argv.symbol, data_type:'SYMS_HIST'});
        var ret = {};
        for (var n = 0; n < this.INFO_RESULT.gSYMS_HISTsymbol.length; n++) {
            var symbol = this.INFO_RESULT.gSYMS_HISTsymbol[n];
            ret[symbol] = {
                symbol: symbol,
                line: this.INFO_RESULT.gSYMS_HISTline[n],
                pad: this.INFO_RESULT.gSYMS_HISTpad[n],
                arc: this.INFO_RESULT.gSYMS_HISTarc[n]
            };
            if (/^[rs]\d+(\.\d+)?$/.test(symbol)) {
                ret[symbol].size = symbol.replace(/^[rs]/, '');
            }
            else if (/^(rect|oval|oval_h)\d+(\.\d+)?x\d+(\.\d+)?(x[cr]\d+(\.\d+)?(x\d+)?)?$/.test(symbol)) {
                /(?:rect|oval|oval_h)(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)/.test(symbol);
                ret[symbol].width = RegExp.$1;
                ret[symbol].height = RegExp.$2;
            }
        }
        return ret;
    }

    this.netlistPageOpen = function(argv) {
        argv = _.assign({},argv);
        if (_.isEmpty(argv.set)) argv.set = 'no';
        this.COM('netlist_page_open', argv);
    }

    this.netlistRecalc = function(argv) {
        this.COM('netlist_recalc', argv);
    }

    this.netlistCompare = function(argv) {
        if (_.isEmpty(argv.display)) argv.display = 'yes';
        this.COM('netlist_compare', argv);
        var csh_file = this.GENESIS_DIR + '/tmp/info_csh.' + process.pid();
        this.COM('netlist_save_compare_results', {output:'file', out_file:csh_file});
        var file = fs.openFile(csh_file, 'r');
        var ret = {};
        var mismatch_type = '';
        if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            while (! file.atEnd()) {
                var item = file.readLine();
                ret.full_report += item;
                if (/Mismatch\s*Type:\s*Possible\s*short/i.test(item)) {
                    mismatch_type = 'possible_shorted';
                }
                else if (/Mismatch\s*Type:\s*Possible\s*broken/i.test(item)) {
                    mismatch_type = 'possible_broken';
                }
                else if (/Mismatch\s*Type:\s*short/i.test(item)) {
                    mismatch_type = 'shorted';
                }
                else if (/Mismatch\s*Type:\s*broken/i.test(item)) {
                    mismatch_type = 'broken';
                }
                else if (/Mismatch\s*Type:\s*missing/i.test(item)) {
                    mismatch_type = 'missing';
                }
                else if (/Mismatch\s*Type:\s*extra/i.test(item)) {
                    mismatch_type = 'extra';
                }

                if (/Total\s*:/i.test(item) && mismatch_type != '') {
                    /(\d+)/.test(item);
                    var count = RegExp.$1;
                    ret[mismatch_type] = count;
                    mismatch_type = '';
                }
            }
        }
        else {
            while (! file.atEnd()) {
                var item = file.readLine();
                ret.full_report += item;
                if (/Mismatch\s*Type:\s*short/i.test(item)) {
                    mismatch_type = 'shorted';
                }
                else if (/Mismatch\s*Type:\s*broken/i.test(item)) {
                    mismatch_type = 'broken';
                }
                else if (/Mismatch\s*Type:\s*missing/i.test(item)) {
                    mismatch_type = 'missing';
                }
                else if (/Mismatch\s*Type:\s*extra/i.test(item)) {
                    mismatch_type = 'extra';
                }

                if (/Total\s*:/i.test(item) && mismatch_type != '') {
                    /(\d+)/.test(item);
                    var count = RegExp.$1;
                    ret[mismatch_type] = count;
                    mismatch_type = '';
                }
            }
        }
        file.close();
        fs.unlink(csh_file);
        return ret;
    }

    this.netlistPageClose = function() {
        this.COM('netlist_page_close');
    }

    this.selClearFeature = function() {
        this.COM('sel_clear_feat');
    }

    this.getSRLimits = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        var ret = {};
        if (!_.isEmpty(argv.include_step)) {
            var inc = argv.include_step;
            var repeats = this.getRepeat({job:argv.job, step:argv.step, units:argv.units});
            var x = [], y = [];
            _.forEach(repeats, function(rpt){
                if (rpt.step.indexOf(inc) == -1) return;
                x.push(rpt.xmin, rpt.xmax);
                y.push(rpt.ymin, rpt.ymax);
            });
            x = _.sortBy(x, function(item) {return _.toNumber(item)});
            y = _.sortBy(y, function(item) {return _.toNumber(item)});
            ret = {
                xmin: x[0],
                xmax: x[x.length-1],
                ymin: y[0],
                ymax: y[y.length-1]
            };
        }
        else {
            this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'SR_LIMITS'});
            ret = {
                xmin: this.INFO_RESULT.gSR_LIMITSxmin,
                ymin: this.INFO_RESULT.gSR_LIMITSymin,
                xmax: this.INFO_RESULT.gSR_LIMITSxmax,
                ymax: this.INFO_RESULT.gSR_LIMITSymax
            }
        }
        ret.xsize = ret.xmax - ret.xmin;
        ret.ysize = ret.ymax - ret.ymin;
        return ret;
    }

    this.getSR = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'SR'});
        return {
            step: this.INFO_RESULT.gSRstep,
            xa: this.INFO_RESULT.gSRxa,
            ya: this.INFO_RESULT.gSRya,
            dx: this.INFO_RESULT.gSRdx,
            dy: this.INFO_RESULT.gSRdy,
            nx: this.INFO_RESULT.gSRnx,
            ny: this.INFO_RESULT.gSRny,
            angle: this.INFO_RESULT.gSRangle,
            mirror: this.INFO_RESULT.gSRmirror,
            xmin: this.INFO_RESULT.gSRxmin,
            ymin: this.INFO_RESULT.gSRymin,
            xmax: this.INFO_RESULT.gSRxmax,
            ymax: this.INFO_RESULT.gSRymax
        }
    }

    this.getActiveArea = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'ACTIVE_AREA'});
        var ret = {
            xmin: this.INFO_RESULT.gACTIVE_AREAxmin,
            ymin: this.INFO_RESULT.gACTIVE_AREAymin,
            xmax: this.INFO_RESULT.gACTIVE_AREAxmax,
            ymax: this.INFO_RESULT.gACTIVE_AREAymax
        };
        ret.xsize = ret.xmax - ret.xmin;
        ret.ysize = ret.ymax - ret.ymin;
        return ret;
    }

    this.chklistCupd = function(argv) {
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        if (_.isEmpty(argv.mode)) argv.mode = 'regular';
        var pars = '';
        for (var item in argv.params){
            pars += '(' + item + '=' + argv.params[item] + ')';
        }
        argv.params = '(' + pars + ')';
        this.COM('chklist_cupd', argv);
    }

    this.getChklistRange = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        var cshfile = this.INFO({entity_type:'check', entity_path:argv.job+'/'+argv.step+'/'+argv.chklist, data_type:'ERF', options:'action='+argv.nact, parse:'no', units:argv.units});
        var ret = {};
        var file = fs.openFile(cshfile, 'r');
        while (! file.atEnd()) {
            var line = _.trimEnd(file.readLine());
            if (line == '') continue;
            var tmp = line.split(/\s+/);
            ret[tmp[0]] = _.tail(tmp);
        }
        file.close();
        fs.unlink(cshfile);
        return ret;
    }

    this.chklistErfRange = function(argv) {
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        if (_.isEmpty(argv.redisplay)) argv.redisplay = 'yes';
        if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            argv.redisplay = argv.redisplay == 'yes' ? 1 : 0;
        }
        if (!_.isEmpty(argv.default_range)) {
            var units = this.getUnits();
            var tmp = this.getChklistRange({job:argv.job, step:argv.step, chklist:argv.chklist, nact:argv.nact, units:units});
            for (var k in tmp) {
                tmp[k] = argv.default_range;
            }
            for (var k in argv.range) {
                tmp[k] = argv.range[k];
            }
            for (var k in tmp) {
                this.COM('chklist_erf_range', {
                    chklist: argv.chklist,
                    nact: argv.nact,
                    erf: argv.erf,
                    category: k,
                    range: (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') ? tmp[k].join('\;') : ('(' + tmp[k].join('\;') + ')'),
                    redisplay: argv.redisplay});
            }
        }
        else {
            for (var k in argv.range) {
                this.COM('chklist_erf_range', {
                    chklist: argv.chklist,
                    nact: argv.nact,
                    erf: argv.erf,
                    category: k,
                    range: (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') ? argv.range[k].join('\;') : ('(' + argv.range[k].join('\;') + ')'),
                    redisplay: argv.redisplay});
            }
        }
    }

    this.chklistErf = function(argv) {
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        this.COM('chklist_erf', {chklist:argv.chklist, nact:argv.nact, erf:argv.erf});
    }

    this.chklistRereadErf = function(argv) {
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        this.COM('chklist_reread_erf', {chklist:argv.chklist, nact:argv.nact});
    }

    this.chklistSelectAct = function(argv) {
        var self = this;
        if (_.isEmpty(argv.select)) argv.select = 'yes';
        argv.nact = [].concat(argv.nact);
        if (_.isEmpty(argv.clear_before)) argv.clear_before = 'yes';
        if (argv.clear_before == 'yes' && argv.select == 'yes') {
            this.chklistSelectAll({job:argv.job, step:argv.step, chklist:argv.chklist, select:'no', nact_count:argv.nact_count});
        }
        _.forEach(argv.nact, function(n){
            self.COM('chklist_select_act', {chklist:argv.chklist, nact:n, select:argv.select});
        })
    }

    this.chklistSelectAll = function(argv) {
        if (_.isEmpty(argv.select)) argv.select = 'yes';
        if (_.isEmpty(argv.nact_count)) argv.nact_count = this.getChklistActCount({job:argv.job, step:argv.step, chklist:argv.chklist});
        for (var n = 1; n <= argv.nact_count; n++) {
            this.COM('chklist_select_act', {chklist:argv.chklist, nact:n, select:argv.select});
        }
    }

    this.getChklistActCount = function(argv) {
        this.INFO({entity_type:'check', entity_path:argv.job+'/'+argv.step+'/'+argv.chklist, data_type:'NUM_ACT', options:'action=1'});
        return this.INFO_RESULT.gNUM_ACT;
    }

    this.chklistCreateLyrs = function(argv) {
        this.COM('chklist_create_lyrs', argv);
    }

    this.getStepLimits = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'LIMITS'});
        var ret = {
            xmin: this.INFO_RESULT.gLIMITSxmin,
            ymin: this.INFO_RESULT.gLIMITSymin,
            xmax: this.INFO_RESULT.gLIMITSxmax,
            ymax: this.INFO_RESULT.gLIMITSymax
        };
        ret.xsize = ret.xmax - ret.xmin;
        ret.ysize = ret.ymax - ret.ymin;
        return ret;
    }

    this.selMove = function(argv) {
        this.COM('sel_move', argv);
    }

    this.selSubstitute = function(argv) {
        if (_.isEmpty(argv.x_datum)) argv.x_datum = 0;
        if (_.isEmpty(argv.y_datum)) argv.y_datum = 0;
        if (_.isEmpty(argv.dcode)) argv.dcode = 0;
        return this.COM('sel_substitute', argv);
    }

    this.selTransform = function(argv) {
        if (_.isEmpty(argv.mode)) argv.mode = 'anchor';
        if (_.isEmpty(argv.duplicate)) argv.duplicate = 'no';
        if (_.isEmpty(argv.angle)) argv.angle = 0;
        if (_.isEmpty(argv.x_scale)) argv.x_scale = 1;
        if (_.isEmpty(argv.y_scale)) argv.y_scale = 1;
        if (_.isEmpty(argv.x_offset)) argv.x_offset = 0;
        if (_.isEmpty(argv.y_offset)) argv.y_offset = 0;
        if (this.GEN_TYPE == 'genesis') {
            this.COM('sel_transform', argv);
        }
        else if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            var orig = this.getOrigin();
            argv.x_offset -= orig.x;
            argv.y_offset -= orig.y;
            this.COM('sel_transform', argv);
        }
    }

    this.getSR1 = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'SR'});
        var ret = [];
        for (var n = 0; n < this.INFO_RESULT.gSRstep.length; n++) {
            var step = this.INFO_RESULT.gSRstep[n];
            ret.push({
                step: this.INFO_RESULT.gSRstep[n],
                xa: this.INFO_RESULT.gSRxa[n],
                ya: this.INFO_RESULT.gSRya[n],
                dx: this.INFO_RESULT.gSRdx[n],
                dy: this.INFO_RESULT.gSRdy[n],
                nx: this.INFO_RESULT.gSRnx[n],
                ny: this.INFO_RESULT.gSRny[n],
                angle: this.INFO_RESULT.gSRangle[n],
                mirror: this.INFO_RESULT.gSRmirror[n],
                xmin: this.INFO_RESULT.gSRxmin[n],
                ymin: this.INFO_RESULT.gSRymin[n],
                xmax: this.INFO_RESULT.gSRxmax[n],
                ymax: this.INFO_RESULT.gSRymax[n],
                xsize: this.INFO_RESULT.gSRxmax[n] - this.INFO_RESULT.gSRxmin[n],
                ysize: this.INFO_RESULT.gSRymax[n] - this.INFO_RESULT.gSRymin[n]
            });
        }
        return ret;
    }

    this.getRepeat = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'REPEAT'});
        var ret = [];
        for (var n = 0; n < this.INFO_RESULT.gREPEATstep.length; n++) {
            var step = this.INFO_RESULT.gREPEATstep[n];
            ret.push({
                step: this.INFO_RESULT.gREPEATstep[n],
                xa: this.INFO_RESULT.gREPEATxa[n],
                ya: this.INFO_RESULT.gREPEATya[n],
                angle: this.INFO_RESULT.gREPEATangle[n],
                mirror: this.INFO_RESULT.gREPEATmirror[n],
                xmin: this.INFO_RESULT.gREPEATxmin[n],
                ymin: this.INFO_RESULT.gREPEATymin[n],
                xmax: this.INFO_RESULT.gREPEATxmax[n],
                ymax: this.INFO_RESULT.gREPEATymax[n],
                xsize: this.INFO_RESULT.gREPEATxmax[n] - this.INFO_RESULT.gREPEATxmin[n],
                ysize: this.INFO_RESULT.gREPEATymax[n] - this.INFO_RESULT.gREPEATymin[n]
            });
        }
        return ret;
    }

    this.getSubSteps = function(argv) {
        var self = this;
        var all_steps = [].concat(argv.all_steps);
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'SR'});
        _.forEach(this.INFO_RESULT.gSRstep, function(step){
            if (all_steps.indexOf(step) == -1) {
                all_steps.push(step);
                all_steps = self.getSubSteps({job:argv.job, step:argv.step, all_steps:all_steps});
            }
        });
        return all_steps;
    }

    this.getDatum = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        this.INFO({units:argv.units, entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'DATUM'});
        return {x: this.INFO_RESULT.gDATUMx, y: this.INFO_RESULT.gDATUMy};
    }

    this.getStepAttr = function(argv) {
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'ATTR'});
        var attr = _.zipObject(this.INFO_RESULT.gATTRname, this.INFO_RESULT.gATTRval);
        if (_.isEmpty(argv.attr)) {
            return attr;
        }
        else if (_.isArray(argv.attr)) {
            return _.pick(attr, argv.attr);
        }
        else {
            return attr[argv.attr];
        }
    }

    this.getJobAttr = function(argv) {
        this.INFO({entity_type:'job', entity_path:argv.job, data_type:'ATTR'});
        var attr = _.zipObject(this.INFO_RESULT.gATTRname, this.INFO_RESULT.gATTRval);
        if (_.isEmpty(argv.attr)) {
            return attr;
        }
        else if (_.isArray(argv.attr)) {
            return _.pick(attr, argv.attr);
        }
        else {
            return attr[argv.attr];
        }
    }

    this.setJobAttr = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        for (var item in argv.attr) {
            this.COM('set_attribute', {type:'job', job:argv.job, name1:'', name2:'', name3:'', attribute:item, value:argv.attr[item], units:argv.units});
        }
    }

    this.getLPD = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'LPD'});
        return {
            polarity: this.INFO_RESULT.gLPDpolarity,
            stretch_x: this.INFO_RESULT.gLPDxstretch,
            stretch_y: this.INFO_RESULT.gLPDystretch,
            shift_x: this.INFO_RESULT.gLPDxshift,
            shift_y: this.INFO_RESULT.gLPDyshift,
            mirror_x: this.INFO_RESULT.gLPDxmirror,
            mirror_y: this.INFO_RESULT.gLPDymirror,
            resolution: this.INFO_RESULT.gLPDres_value,
            swap: this.INFO_RESULT.gLPDswap_axes
        };
    }

    this.getLPM = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'LPM'});
        var ret = {};
        for (var n = 0; n < this.INFO_RESULT.gLPMdevice_type.length; n++) {
            var device = this.INFO_RESULT.gLPMdevice_type[n];
            ret[device] = {
                polarity: this.INFO_RESULT.gLPMpolarity[n],
                stretch_x: this.INFO_RESULT.gLPMxstretch[n],
                stretch_y: this.INFO_RESULT.gLPMystretch[n],
                shift_x: this.INFO_RESULT.gLPMxshift[n],
                shift_y: this.INFO_RESULT.gLPMyshift[n],
                mirror_x: this.INFO_RESULT.gLPMxmirror[n],
                mirror_y: this.INFO_RESULT.gLPMymirror[n],
                resolution: this.INFO_RESULT.gLPMres_value[n],
                swap: this.INFO_RESULT.gLPMswap_axes[n]
            };
        }
        if (!_.isEmpty(argv.device)) {
            return ret[argv.device];
        }
        else {
            return ret;
        }
    }

    this.setCurrentAttribute = function(argv) {
        var self = this;
        this.COM('cur_atr_reset');
        if (!_.isEmpty(argv.attribute)) {
            if (_.isArray(argv.attribute)) {
                _.forEach(argv.attribute, function(item){
                    if (_.isPlainObject(item)) {
                        self.COM('cur_atr_set', item);
                    }
                    else {
                        self.COM('cur_atr_set', {attribute: item});
                    }
                });
            }
            else if (_.isPlainObject(argv.attribute)) {
                this.COM('cur_atr_set', argv.attribute);
            }
            else {
                this.COM('cur_atr_set', {attribute: argv.attribute});
            }
        }
    }

    this.attributeReset = function() {
        this.COM('cur_atr_reset');
    }

    this.selAddAttr = function(argv) {
        this.setCurrentAttribute(argv);
        this.COM('sel_change_atr', {mode:'add'});
    }

    this.setOrigin = function(argv) {
        this.COM('origin', {x:argv.x, y:argv.y});
    }

    this.setDatum = function(argv) {
        this.COM('datum', {x:argv.x, y:argv.y});
    }

    this.selNetFeat = function(argv) {
        if (_.isEmpty(argv.operation)) argv.operation = 'select';
        this.COM('sel_net_feat', argv);
    }

    this.getOrigin = function() {
        var tmp = this.COM('get_origin').split(' ');
        return {x: tmp[0], y: tmp[1]};
    }

    this.origin = function(argv) {
        if (_.isEmpty(argv.push_in_stack)) argv.push_in_stack = 0;
        this.COM('origin', argv);
    }

    this.chklistResShow = function(argv) {
        if (_.isEmpty(argv.nact)) argv.nact = 1;
        if (_.isEmpty(argv.x)) argv.x = 0;
        if (_.isEmpty(argv.y)) argv.y = 0;
        if (_.isEmpty(argv.w)) argv.w = 0;
        if (_.isEmpty(argv.h)) argv.h = 0;
        this.COM('chklist_res_show', argv);
    }

    this.chklistClose = function(argv) {
        if (_.isEmpty(argv.mode)) argv.mode = 'hide';
        this.COM('chklist_close', argv);
    }

    this.getDbList = function(argv) {
        argv = _.assign({},argv);
        var ret = {};
        if (this.GEN_TYPE == 'genesis') {
            var file = fs.openFile(this.GENESIS_DIR + '/sys/dblist', 'r');
            while (! file.atEnd()) {
                var item = file.readLine();
                if (/^\s*NAME\s*=\s*(.*)$/i.test(item)) {
                    var dbname = RegExp.$1;
                    if (/^\s*PATH=(.*)$/i.test(file.readLine())) ret[dbname] = RegExp.$1;
                }
            }
            file.close();
        }
        else if (this.GEN_TYPE == 'incam' || this.GEN_TYPE == 'incamp') {
            var dbs = this.dbutil('list dbs');
            _.forEach(dbs.split("\n"), function(line){
                if (/^\s*([0-9a-z_\-]+)\s*\((.*)\)\s*$/i.test(line)) ret[RegExp.$1] = RegExp.$2;
            })
        }
        return ret;
    }

    this.getLayerAttr = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'ATTR'});
        var attr = _.zipObject(this.INFO_RESULT.gATTRname, this.INFO_RESULT.gATTRval);
        if (_.isEmpty(argv.attr)) {
            return attr;
        }
        else if (_.isArray(argv.attr)) {
            return _.pick(attr, argv.attr);
        }
        else {
            return attr[argv.attr];
        }
    }

    this.isJobChanged = function(argv) {
        this.INFO({entity_type:'job', entity_path:argv.job, data_type:'is_changed'});
        return this.INFO_RESULT.gIS_CHANGED == 'yes';
    }

    this.isStepChanged = function(argv) {
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'is_changed'});
        return this.INFO_RESULT.gIS_CHANGED == 'yes';
    }

    this.isLayerChanged = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'is_changed'});
        return this.INFO_RESULT.gIS_CHANGED == 'yes';
    }

    this.getSymbolHistory = function(argv) {
        if (!_.isEmpty(argv.options)) {
            this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'SYMS_HIST', options:argv.options});
        }
        else {
            this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'SYMS_HIST'});
        }
        return {
            symbol: this.INFO_RESULT.gSYMS_HISTsymbol,
            pad: this.INFO_RESULT.gSYMS_HISTpad,
            line: this.INFO_RESULT.gSYMS_HISTline,
            arc: this.INFO_RESULT.gSYMS_HISTarc
        };
    }

    this.selectRectangle = function(argv) {
        if (_.isEmpty(argv.inside_area)) argv.inside_area = 'yes';
        if (_.isEmpty(argv.intersect_area)) argv.intersect_area = 'no';
        if (_.isEmpty(argv.lines_only)) argv.lines_only = 'no';
        if (_.isEmpty(argv.ovals_only)) argv.ovals_only = 'no';
        if (_.isEmpty(argv.min_len)) argv.min_len = 0;
        if (_.isEmpty(argv.max_len)) argv.max_len = 0;
        if (_.isEmpty(argv.min_angle)) argv.min_angle = 0;
        if (_.isEmpty(argv.max_angle)) argv.max_angle = 0;
        var x1 = argv.x1; delete argv.x1;
        var x2 = argv.x2; delete argv.x2;
        var y1 = argv.y1; delete argv.y1;
        var y2 = argv.y2; delete argv.y2;
        this.COM('filter_area_strt');
        this.COM('filter_area_xy', {x:x1, y:y1});
        this.COM('filter_area_xy', {x:x2, y:y2});
        this.COM('filter_area_end', _.assign({layer:'', filter_name:'popup', operation:'select', area_type:'rectangle'}, argv));
    }

    this.renameLayer = function(argv) {
        this.COM('matrix_rename_layer', {job:argv.job, matrix:'matrix', layer:argv.layer, new_name:argv.new_name});
    }

    this.renameStep = function(argv) {
        this.COM('rename_entity', {job:argv.job, is_fw:'no', type:'step', fw_type:'form', name:argv.name, new_name:argv.new_name});
    }

    this.setLayerAttr = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        for (var item in argv.attr) {
            this.COM('set_attribute', {type:'layer', job:argv.job, name1:argv.step, name2:argv.layer, name3:'', attribute:item, value:argv.attr[item], units:argv.units});
        }
    }

    this.setStepAttr = function(argv) {
        if (_.isEmpty(argv.units)) argv.units = 'inch';
        for (var item in argv.attr) {
            this.COM('set_attribute', {type:'step', job:argv.job, name1:argv.step, name2:'', name3:'', attribute:item, value:argv.attr[item], units:argv.units});
        }
    }

    this.getToolUser = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL_USER'});
        return this.INFO_RESULT.gTOOL_USER;
    }

    this.getToolThick = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL_THICK'});
        return this.INFO_RESULT.gTOOL_THICK;
    }

    this.getToolSlots = function(argv) {
        this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'TOOL_SLOTS'});
        return this.INFO_RESULT.gTOOL_SLOTS;
    }

    this.cadNetlistExists = function(argv) {
        return this.netlistExists({job:argv.job, step:argv.step, type:'cadnet'});
    }

    this.netlistExists = function(argv) {
        this.INFO({entity_type:'step', entity_path:argv.job+'/'+argv.step, data_type:'NETS_LIST'});
        return this.INFO_RESULT.gNETS_LIST.indexOf(argv.type) != -1;
    }

    this.isLayerEmpty = function(argv) {
        var feat_hist = this.getLayerFeatHist(argv);
        return _.isEmpty(feat_hist.total);
    }

    this.changeText = function(argv) {
        if (_.toNumber(this.GENESIS_VER) >= 91) {
            this.COM('sel_change_txt', {text:argv.text, x_size:-1, y_size:-1, w_factor:-1, polarity:'no_change', mirror:'no_change', fontname:'standard'});
        }
        else {
            this.COM('sel_change_txt', {text:argv.text, x_size:-1, y_size:-1, w_factor:-1});
        }
    }

    this.getGenVersion = function(argv) {
        argv = _.assign({},argv);
        if (_.isEmpty(argv.type)) argv.type = 'number';
        var ver_str = this.COM('get_version');
        if (argv.type == 'number') {
            if (/V(\d+(?:\.\d*)?)/i.test(ver_str)) {
                var ver = RegExp.$1;
                return ver * 10;
            }
        }
        else {
            return ver_str;
        }
    }

    this.getLayerFeatHist = function(argv) {
        if (_.isEmpty(argv.options)) {
            this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'FEAT_HIST', options:argv.options});
        }
        else {
            this.INFO({entity_type:'layer', entity_path:argv.job+'/'+argv.step+'/'+argv.layer, data_type:'FEAT_HIST'});
        }
        return {
            line: this.INFO_RESULT.gFEAT_HISTline,
            pad: this.INFO_RESULT.gFEAT_HISTpad,
            surf: this.INFO_RESULT.gFEAT_HISTsurf,
            arc: this.INFO_RESULT.gFEAT_HISTarc,
            text: this.INFO_RESULT.gFEAT_HISTtext,
            total: this.INFO_RESULT.gFEAT_HISTtotal
        };
    }

    this.getSymbolList = function(argv) {
        this.INFO({entity_type:'job', entity_path:argv.job, data_type:'SYMBOLS_LIST'});
        return this.INFO_RESULT.gSYMBOLS_LIST;
    }

    this.copySymbol = function(argv) {
        this.COM('copy_entity', {type:'symbol', source_job:argv.source_job, source_name:argv.source_name, dest_job:argv.dest_job, dest_name:argv.dest_name, dest_database:''});
    }

    this.clipArea = function(argv) {
        if (_.toString(argv.layer) != '') {
            if (_.isEmpty(argv.layers_mode)) argv.layers_mode = 'layer_name';
        }
        else {
            argv.layers_mode = 'affected_layers';
        }
        if (_.isEmpty(argv.inout)) argv.inout = 'inside';
        if (_.isEmpty(argv.contour_cut)) argv.contour_cut = 'no';
        if (_.isEmpty(argv.margin)) argv.margin = 0;
        if (_.isEmpty(argv.feat_types)) argv.feat_types = 'line\;pad\;surface\;arc\;text';
        this.COM('clip_area_strt');
        if (!_.isEmpty(argv.area_rect)) {
            this.COM('clip_area_xy', {x:argv.area_rect.x1, y:argv.area_rect.y1});
            this.COM('clip_area_xy', {x:argv.area_rect.x2, y:argv.area_rect.y2});
            argv.area = 'manual';
            argv.area_type = 'rectangle';
        }
        else if (!_.isEmpty(argv.area_polygon)) {
            var self = this;
            _.forEach(argv.area_polygon, function(it){
                self.COM('clip_area_xy', {x:it.x, y:it.y});
            });
            argv.area = 'manual';
            argv.area_type = 'polygon';
        }
        else if (_.toString(argv.ref_layer) != '') {
            argv.area = 'reference';
            argv.area_type = 'rectangle';
        }
        else {
            argv.area = 'profile';
            argv.area_type = 'rectangle';
        }
        this.COM('clip_area_end', {
            layers_mode: argv.layers_mode,
            layer: argv.layer,
            area: argv.area,
            area_type: argv.area_type,
            inout: argv.inout,
            contour_cut: argv.contour_cut,
            margin: argv.margin,
            feat_types: argv.feat_types,
            ref_layer: argv.ref_layer
        });
    }

    this.compareLayers = function(argv) {
        argv = _.assign({},argv);
        if (_.isEmpty(argv.area)) argv.area = 'global';
        this.COM('compare_layers', argv);
        return this._COMANS > 0;
    }

    this.von = function() {
        this.VON();
    }

    this.vof = function() {
        this.VOF();
    }

    this.dispOn = function() {
        var err = this.ERROR_HANDLE;
        this.ERROR_HANDLE = 0;
        this.COM('disp_on');
        this.ERROR_HANDLE = err;
    }

    this.dispOff = function() {
        var err = this.ERROR_HANDLE;
        this.ERROR_HANDLE = 0;
        this.COM('disp_off');
        this.ERROR_HANDLE = err;
    }

    this.errOn = function() {
        this.ERROR_HANDLE = 1;
    }

    this.errOff = function() {
        this.ERROR_HANDLE = 0;
    }

    this.selCreateProfile = function(argv) {
        this.COM('sel_create_profile');
    }

    this.selAllFeat = function(argv) {
        this.COM('sel_all_feat');
    }

    this.selResize = function(argv) {
        if (_.isEmpty(argv.corner_ctl)) argv.corner_ctl = 'no';
        this.COM('sel_resize', {size:argv.size, corner_ctl:argv.corner_ctl});
    }

    this.breakStep = function(){ // 打散step    2020-03-06 @Scott
        GEN.COM('sredit_reduce_nesting,mode=one_highest')
    }
    
    this.delStepType = function(name) { // 删除当前Step排版 2020-03-06 @Scott
        GEN.COM('sredit_sel_steps_name,name='+name)
        GEN.COM('sredit_del_steps')
    }
  
    this.selLayerFeat = function(argv) {
        argv.operation = argv.operation || "select";
        this.COM("sel_layer_feat", argv)
    }

    

    

    
}

