/*
    NAME: 
    DESCRIPTION: 客户数据分析
    PARAMETER:
	[
        {
            name : 'pcs_step',
			title : 'pcsStep名',
			type : 'LineEdit',
			property : {tool_tip : 'pcs step名,默认是cad'}
        },
        {
            name : 'send_email',
			title : '是否发送邮件',
			type : 'RadioBox',
            property : {
				item_list:[
					{name:'yes',text:'YES'},
					{name:'no',text:'NO'},
				],
				tool_tip:'是否自动保存料号开关'
			}
        },
        {
            name : 'cam_type',
			title : 'cam_type',
			type : 'LineEdit',
			property : {tool_tip : 'cam_type'}
        },
        {
            name : 'vc_src_01005_pad_result',
			title : 'vc_src_01005_pad_result',
			type : 'LineEdit',
			property : {tool_tip : 'vc_src_01005_pad_result'}
        },
        {
            name : 'array_step',
			title : 'arrayStep名',
			type : 'LineEdit',
			property : {tool_tip : 'array step名,默认是stp'}
        },
        {
            name : 'erf',
			title : 'erf名称',
			type : 'LineEdit',
			property : {tool_tip : 'erf名称'}
        },
        {
            name : 'auto_save',
			title : '自动保存',
            type : 'RadioBox',
            property : {
				item_list:[
					{name:'yes',text:'YES'},
					{name:'no',text:'NO'},
				],
				tool_tip:'是否自动保存料号开关'
			}
        },
        {
            name : 'export_path',
			title : '导出路径',
			type : 'LineEdit',
			property : {tool_tip : '导出路径'}
        },
        {
            name : 'export_mode',
			title : '导出模式',
            type : 'RadioBox',
            property : {
				item_list:[
					{name:'tar_gzip',text:'tar_gzip'},
					{name:'tar',text:'tar'},
					{name:'xml',text:'xml'},
					{name:'directory',text:'directory'},
				],
				tool_tip:'导出模式'
			}
        },
        {
            name : 'export_submode',
			title : '导出方式',
            type : 'RadioBox',
            property : {
				item_list:[
					{name:'full',text:'full'},
					{name:'partial',text:'partial'},
				],
				tool_tip:'导出方式'
			}
        }
    ]

    VERSION_HISTORY:
	V1.00 2020 3-30 Scott
	
    HELP:
	<html><body bgcolor="#DDECFE">
		<font size="3" color="#003DB2"><p>功能简介</p></font>
		  <p> 客户数据分析</p>
		  <br>
		<font size="3" color="#003DB2"><p>参数配置</p></font>
		 <p> 客户参数 </p>
		<font size="3" color="#003DB2"><p>注意事项</p></font>
		  <p> ● 无 </p>
		  <br>
	</body></html>	
*/
// 引入模块 包
var $ = require('topcam.scriptfunc').argv();
var fs = require('fs');
var _ = require('lodash');
var mail = require('topsin.mail');
var error = require('topsin.error');
var mode = $.ikm ? "topcam" : "aimdfm";
var IKM = $.ikm; 
if (IKM==undefined ) { IKM = require('topcam.ikm6')($) }
var GEN = $.gen;
var GUI = $.gui || {};
var Job = $.job || $.job_name;
var JobId = $.job_id;
var process = require('process');
var db = $.db || IKM.db
var PAR = {}
if ($.par) {
	PAR = $.par
} else if ($.hasOwnProperty('script_parameter')){
	PAR = JSON.parse($.script_parameter);
}

if (mode === "aimdfm") {
	var database = require("topsin.database");
	database.addConnection($.conf.database_conf, "DFM");
	var QDfm = database.query("DFM");
	$.QDfm = QDfm;
	if ($.conf.product_type == "aimdfm") {
		QDfm.updateRow({
			table: "pdm_aimdfm_task",
			data: {
				current_process_title: $.process_title
			},
			where: { id: $.task_id }
		});
	}
}
var Status = 'ok';
var resultData = [];

var ALL = {}
var Omatrix = {};

var db_customer = db.query("",function(q){
    return q.selectValue({
        table:'pdm_job',
        field:'customer_code',
        where:{id : JobId}
    })
});
var mailUserList = db.query("",function(q){
    return q.selectArrayMap({
        table:'sys_commu_grp_detail AS DETAIL LEFT JOIN sys_commu_grp AS GRP ON GRP.id = DETAIL.commu_grp_id',
        field:['DETAIL.mode, DETAIL.name, DETAIL.address'],
        where:{"GRP.sys_name":"quotation_fee"},
        order: "DETAIL.mode ASC"
    })
});
var mailUserTo=[], mailUserCc=[], mailUserBcc = [];
if(mailUserList && mailUserList.length) {
    mailUserList.map(function(item){
        if(item.mode == "mail_cc" && item.address){
            mailUserCc.push(item.address)
        } else if (item.mode == "mail_to" && item.address ) {
            mailUserTo.push(item.address)
        }else if (item.mode == "mail_bcc" && item.address ) {
            mailUserBcc.push(item.address)
        }
    })
}
var cam_workflow_info = IKM.get_jobinfo({jobid:JobId, jobinfo:"cam_workflow_info"});
try {
    if(cam_workflow_info == "err" && global._STATUS == "err"){  
        throw cam_workflow_info
    }
    if(global._STATUS != "err") {
        // 清空报错信息
        var job_attrs = db.query("",function(q){
            return q.selectValue({
                table:'pdm_job',
                field_format:{job_attrs:'json'},
                field:'job_attrs',
                where:{id : JobId}
            })
        });
        if (!job_attrs ) { job_attrs = {}} 
        job_attrs.readin_result = "Readin"
        db.query("",function(q){
            return q.updateRow({
                table:'pdm_job',
                data:{job_attrs:job_attrs},
                update_policy:{attr_data:'json_merge'},
                where:{id : JobId}
            })
        });
    }
    cam_workflow_info = "sendmail"
    console.log("============== ===============================>anaysis_start");
    var par = PAR;
    var vc_src_01005_pad_result = par.vc_src_01005_pad_result
    if(vc_src_01005_pad_result == ""){
        vc_src_01005_pad_result = ".pth_pad"
    }
    if(par.erf == ""){delete PAR.erf}
	if(_.isEmpty(Job)) throw "没有传入料号名!";
    var job = Job.toLowerCase();

    if(!GEN.isJobExists({job:job})){ throw "job "+ job+ " is not exist" }
    // 检查料号是否能够check out
	if(GEN.checkInout({job:job,mode:"test"}) != 0 && mode == "aimdfm"){ throw "the job check" }
    GEN.checkInout({job:job,mode:"out"});
    script_info({ msg: "Data Analysis" ,result_severity:"info",progress: 65})
	global._ERRORMAG = "Failed to Data Analysis"; 
    var pcs_step = par.pcs_step == "" ? "cad" : par.pcs_step;
    var array_step = par.array_step == "" ? "stp" : par.array_step;
    var step_list = GEN.getStepList({job:job})
    if(step_list.indexOf(array_step)<0){
        array_step = false
    }
    if(step_list.indexOf(pcs_step)<0){
        pcs_step = step_list[0]
    }
    script_info({ msg: "Upload matrix",result_severity:"info" })
	global._ERRORMAG = "Error-Upload matrix"; 
    var matrix = UPLOAD_LAYER_MATRIX({job:job})  // 分析matrix 获得分析后的matrix信息
    GEN.openStep({job:job, name:pcs_step})
    
    

    _.values(matrix).forEach(function(v){
        if(v.context == "board" && v.layer_type == "drill"){
            if(v.type == "laser_drill"){
                GEN.affectedLayer({affected:'no',mode:'all'})
                GEN.workLayer({name:v.odb_name,display_number:2,clear_before:'yes'})
                GEN.selClearFeature()
                // GEN.selAllFeat()
                var syms = GEN.getLayerSymsHist({job:job,step:pcs_step,layer:v.odb_name});
                // var getLayerSymsHist = $.gen.getLayerSymsHist({job:'demo',step:'panel',layer:'l2',options:'break_sr',units:'mm'});

                var include_syms = [];
                for (var key in syms) {
                    var info = syms[key];
                    if(info.size > 5.9055) {
                        include_syms.push(key)
                    }
                }

                GEN.selectByFilter({include_syms:include_syms.join("\\;")})
                if(GEN.getSelectCount() > 0){
                    GEN.COM("cur_atr_reset")
                    GEN.COM("cur_atr_set,attribute=.drill,option=plated")
                    GEN.COM("sel_change_atr,mode=add")
                }

                GEN.selectByFilter({include_syms:include_syms.join("\\;")})
                GEN.selReverse();
                if(GEN.getSelectCount() > 0){
                    GEN.COM("cur_atr_reset")
                    GEN.COM("cur_atr_set,attribute=.drill,option=via")
                    GEN.COM("sel_change_atr,mode=add")
                }

                GEN.clearLayers()
                GEN.selClearFeature()
            } else if(v.type == "main_drill" || v.type == "blind_drill" || v.type == "bury_drill") {
                GEN.affectedLayer({affected:'no',mode:'all'})
                // GEN.workLayer({name:v.odb_name,display_number:2,clear_before:'yes'})
                GEN.selClearFeature()
                var tmp_layer = v.drl_start + "tmp"
                GEN.workLayer({name:v.drl_start,display_number:2,clear_before:'yes'})
                GEN.selCopyOther({dest:'layer_name',target_layer:tmp_layer,invert:'no',dx:0,dy:0,size:0})
                GEN.workLayer({name:tmp_layer,display_number:2,clear_before:'yes'})
                GEN.selRefFeat({layers:v.odb_name,use:'filter',mode:'cover'})
                if(GEN.getSelectCount()> 0){GEN.selDelete()}
                GEN.workLayer({name:v.odb_name,display_number:2,clear_before:'yes'})
                GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'touch'})
                if(GEN.getSelectCount()>0){
                    // unSelect()
                    // if(GEN.getSelectCount() > 0){
                        // GEN.COM("cur_atr_reset")
                    //     GEN.COM("cur_atr_set,attribute=.drill,option=plated")
                    //     GEN.COM("sel_change_atr,mode=add")
                    // }
                }
                GEN.selReverse()
                if(GEN.getSelectCount() > 0){
                    // unSelect()
                    // if(GEN.getSelectCount() > 0){
                        // GEN.COM("cur_atr_reset")
                    //     GEN.COM("cur_atr_set,attribute=.drill,option=non_plated")
                    //     GEN.COM("sel_change_atr,mode=add")
                    // }
                }
                GEN.deleteLayer({job:job,layer:[tmp_layer]})
            }
        }
    })
    GEN.clearLayers()
   
    script_info({ msg: "Drill Stackup analysis" ,progress: 70,result_severity:"info"})
	global._ERRORMAG = "Error-Drill Stackup analysis"; 

    // * new 钻孔叠构
    var cam_drill_structure = []
    var stack_drills = _.values(matrix).filter(function(item){
        return item.layer_type == "drill" && item.context == "board"
    })
    var layerCount = GEN.getLayerCount({job:job})
    stack_drills.forEach(function(drill){
        GEN.clearLayers()
        GEN.selClearFeature()
        var drill_info = {}
        var drl_start_num = drill.drl_start_num -0
        var drl_end_num = drill.drl_end_num-0
        drill_info.drl_start_num = drl_start_num
        drill_info.drl_end_num = drl_end_num
        var drl_side = "None"
        if(layerCount/2 >= drl_start_num){
            drl_side = "Top"
        } else {
            drl_side = "Bot"
        }
        drill_info.drl_side = drl_side
        var drl_filler = "None"
        if(drill.type == "laser_drill"){
            drill_info.drl_type = "Laser"
            drill_info.drl_name = "Laser" + drl_start_num + "-" + drl_end_num
            drill_info.drl_tech = "LDD"
            // 计算此层的理论 top  bot
            if(drl_side == "Top"){
                if((drl_start_num-1)==0){ drl_filler = "None" }
                else {
                    var top_l = "ftdrill" + (drl_start_num-1) + "-" + (drl_end_num-1) + "l"
                    if(GEN.isLayerExists({job:job, layer:top_l})){
                        GEN.clearLayers()
                        GEN.selClearFeature()
                        GEN.workLayer({name:top_l,display_number:2,clear_before:'yes'})
                        GEN.selRefFeat({layers:drill.odb_name,use:'filter',mode:'touch'})
                        if(GEN.getSelectCount()>0){
                            GEN.selClearFeature()
                            drl_filler="Via Filling"
                        }
                    }
                }
                var bot_l = "ftdrill" + (drl_start_num+1) + "-" + (drl_end_num+1) + "l"
                if(GEN.isLayerExists({job:job, layer:bot_l})){
                    GEN.clearLayers()
                    GEN.selClearFeature()
                    GEN.workLayer({name:drill.odb_name,display_number:2,clear_before:'yes'})
                    GEN.selRefFeat({layers:bot_l,use:'filter',mode:'touch'})
                    if(GEN.getSelectCount()>0){
                        GEN.selClearFeature()
                        drill_info.drl_overlap = "Yes"
                    } else {
                        drill_info.drl_overlap = "No"
                    }
                }else{ drill_info.drl_overlap = "No" }
            }else if (drl_side == "Bot"){
                if((drl_end_num-layerCount)==0){ drl_filler = "None" }
                else {
                    var top_l = "ftdrill" + (drl_start_num+1) + "-" + (drl_end_num+1) + "l"
                    if(GEN.isLayerExists({job:job, layer:top_l})){
                        GEN.clearLayers()
                        GEN.selClearFeature()
                        GEN.workLayer({name:top_l,display_number:2,clear_before:'yes'})
                        GEN.selRefFeat({layers:drill.odb_name,use:'filter',mode:'touch'})
                        if(GEN.getSelectCount()>0){
                            GEN.selClearFeature()
                            drl_filler="Via Filling"
                        }
                    }
                }
                var bot_l = "ftdrill" + (drl_start_num-1) + "-" + (drl_end_num-1) + "l"
                if(GEN.isLayerExists({job:job, layer:bot_l})){
                    GEN.clearLayers()
                    GEN.selClearFeature()
                    GEN.workLayer({name:drill.odb_name,display_number:2,clear_before:'yes'})
                    GEN.selRefFeat({layers:bot_l,use:'filter',mode:'touch'})
                    if(GEN.getSelectCount()>0){
                        GEN.selClearFeature()
                        drill_info.drl_overlap = "Yes"
                    }else {
                        drill_info.drl_overlap = "No"
                    }
                }else{ drill_info.drl_overlap = "No" }
            }
        } else {
            GEN.clearLayers()
            GEN.workLayer({name:drill.odb_name,display_number:2,clear_before:'yes'})
            GEN.selectByFilter({attribute:[{attribute:".drill",option:"plated"}]})
            if(GEN.getSelectCount()>0){
                GEN.selClearFeature()
                drill_info.drl_type = "PTH"
                if(drl_end_num - drl_start_num == 1){drl_filler = "Resin Plugin"}
                drill_info.drl_name = "PTH" + drl_start_num + "-" + drl_end_num
            }else{
                drill_info.drl_type = "NPTH"
                drill_info.drl_name = "NPTH" + drl_start_num + "-" + drl_end_num
            }
        }
        drill_info.drl_filler = drl_filler
        drill_info.drl_zones = "{A}"
        cam_drill_structure.push(drill_info)
    })
    var is_via_filling = cam_drill_structure.filter(function(v){return v.drl_filler == "Via Filling"})
    if(is_via_filling.length>0){
        cam_drill_structure = cam_drill_structure.map(function(item){
            if(item.drl_type=="Laser" && item.drl_filler=="None"){
                item.drl_filler = "Via Filling"
            }
            return item
        })
    }
    save_job_info({
        jobid: JobId,
        jobinfohash: {cam_drill_structure: JSON.stringify(cam_drill_structure)}
    })
    GEN.closeStep()
    if(step_list.indexOf(pcs_step)<0){throw "can not find pcsstep"}
    if(step_list.indexOf(array_step)<0){array_step = false}
    console.log("=============== ====================> 1matrix")
    var analysis_obj = analysis({job:job,jobId:JobId,pcs_step:pcs_step,array_step:array_step,matrix:matrix})
    // 创建profile
    var tmp_matrix = GEN.getMatrix({job:job})
    script_info({ msg: "Profile analysis",progress: 70 ,result_severity:"info"})
	global._ERRORMAG = "Error-Profile analysis"; 

    // * profile 和 拼版
    var pandle_step = step_list.map(function(v){
        var type = "unknow";
        if(/^cad|^pcs/ig.test(v)){
            type = "pcs"
        } else if (/^arr|^stp/ig.test(v)) {
            type = "array"
        }
        var panel_info = {job_id:JobId,step_name:v,step_type:type}
        GEN.openStep({job:job,name:v})
        GEN.clearLayers()
        GEN.selClearFeature()
        var profileLimits = GEN.getProfileLimits({job:job,step:v,units:"mm"})
        if(profileLimits){
            panel_info.size_x = Number(profileLimits.xsize).toFixed(3) 
            panel_info.size_y = Number(profileLimits.ysize).toFixed(3) 
        }
        panel_info.datum_x = 0
        panel_info.datum_y = 0
        panel_info.margin = {"margin_top": 0, "margin_left": 0, "margin_right": 0, "margin_bottom": 0, "allow_margin_top": 0, "allow_margin_left": 0, "allow_margin_right": 0, "allow_margin_bottom": 0}
        var profile = GEN.getProfile({job:job,step:v,units:"mm"}).split("\n")
        if(profile.length>=1){
            var tmp = profile.slice(1)
            tmp = tmp.map(function(v){
                if(v[0]==="#"){v = v.slice(1)}
                return v
            })
            panel_info.profile = tmp.join("\n")
        } else {
            profile = profile.map(function(v){
                if(v[0]==="#"){v = v.slice(1)}
                return v
            })
            panel_info.profile = profile.join("\n")
        }
        for(var key in profileLimits){
            profileLimits[key] = Number(profileLimits[key])
        }
        panel_info.profile_limits  =  profileLimits
        panel_info.attr_data= {}
        panel_info.extra_data= {}
        var repeat = GEN.getSR1({job:job,step:v,units:"mm"})
        var step_repeat = {}
        repeat.forEach(function(item, i){
            step_repeat[String(i)] = {
                x:item.xa,
                y:item.ya,
                dx: item.dx,dy: item.dy,
                nx:item.nx,ny:item.ny,
                // gapX:item.xsize,gapY:item.ysize,
                step:item.step,
                angle:item.angle,
                mirror:item.mirror == "no" ? 0 : 1,
                step_name:item.step,
                step_type:/cad|pcs|card/ig.test(item.step)? "pcs" : "array",
            }
        })
        panel_info.step_repeat = step_repeat
        save_panel_info({info:panel_info, table:"pdm_job_panelizer_step"})
        GEN.closeStep()
        return {name:v, type:type}
    })





    // glod_finger
    var gold_fingers = Object.keys(tmp_matrix).filter(function(v){return /^enig_top$|^enig_bot$/ig.test(v)})
    if(gold_fingers.length){
        gold_fingers = gold_fingers.map(function(v){
            var type = (v=="enig_top") ? "top" : "bot"
            return {name:v, type:type}
        })
    } else {
        gold_fingers = Object.keys(tmp_matrix).filter(function(v){return /^past_top$|^past_botm$/ig.test(v)})
        if(gold_fingers.length){
            gold_fingers = gold_fingers.map(function(v){
                var type = (v=="past_top") ? "top" : "bot"
                return {name:v, type:type}
            })
        }
    }
    ALL.gold_fingers = gold_fingers
    GEN.closeStep()
    script_info({ msg: "PN analysis",result_severity:"info"})
	global._ERRORMAG = "Error-PN analysis"; 

  
    var config = {
        jobInfo: {
            layer_count: ["signal","power_ground"],  // Board属性的signal或者power_ground层
            pcs_size_x: {api:"vc_card_size", props:"xsize"},       // card短边尺寸   // ? 保留小数
            pcs_size_y: {api:"vc_card_size", props:"ysize"},       // card长边尺寸     // ? 保留小数
            array_size_x: {api:"vc_array_size", props:"xsize"},       // ! array短边尺寸 需要arr_step   // ? 保留小数
            array_size_y: {api:"vc_array_size", props:"ysize"},       // ! array长边尺寸 需要arr_step   // ? 保留小数
            vc_pcs_count_on_panel: true,  // array中pcs的数量 ! 需要arr_step
            pcs_count_on_array: true,
            stack_vias_number: true,    // via孔连续叠加的层数
            stack_vias_more: {only_value:true},   // yes|no : 14层板以上时,Stack Vias >=12时,存yes   // ! 需要分析完via连续叠加层数
            depth_drilling: {api:"layer_exist", props:"depth_drill"},  // yes|no :存在 depth_drill 层时 ,存yes
            depth_routing: {api:"layer_exist", props:"depth_routing"},  // tmp yes|no :存在 depth_routing 层时,,存yes
            laser_via_on_buried_hole: true,  // via孔在埋孔上时,存yes
            milling_bit_size: false,  // todo 检查array中pcs的最小间距值
            milling_length_card: true,
            milling_length_array: true,
            ATS_01005pad: {api:"board_has_attr", props:vc_src_01005_pad_result},  // 本地使用 .pth_pad ats 01005_pad
            ATS_technology_25dc: {api:"layer_exist", props:["bot_coverlay","top_coverlay"]},  // yes|no:存在cavity层别时存yes
            ATS_technology_25dr: {api:"layer_exist", props:["bot_coverlay","top_coverlay"]},  // yes|no:存在cavity层别时存yes
            // vc_src_EDGE_PLATING: true,  // yes|no:检查线路外形是否存在物件,存在则存yes  // !料号需要有rout层
            vc_src_EDGE_PLATING: {api:"layer_exist2", props:["fab_page2"]},  // fab_page2
            edge_plating_length: true,  // todo 检查线路外形处物件的长度
            gold_finger: false,  // 
            glod_finger_area: false,  // 
            ATS_sm_side: true, // top|bot|both:检查防焊层所在面次    ATS_sm_side
            vc_id_print_side: true, // top|bot|both:检查文字层所在面次  vc_id_print_side
            min_drl_size: true, // todo  0.5mm 最小圆孔
            max_pth_drl_size: true, // todo  3.5mm 最大pth圆孔
            max_npth_drl_size: true, // todo  6.0mm 最大npth圆孔
            min_slot_drl_size: true, // todo  0.8mm 最小槽孔
            max_slot_drl_size: true, // todo  1.2mm 最大槽孔
            max_slot_drl_length: true, // todo  2.0mm 最大槽孔
            max_spec_slot_drl_size: true, // todo  2.1mm 最大异形槽孔
            max_spec_slot_drl_length: true, // todo  3.0mm 最大异形槽孔
        }
    }
    console.log("===============================> 2analysis_obj")
    
 
   
    // 分析料号info
    var jobInfo = {}
    Object.keys(config.jobInfo).forEach(function(key){
        var props = config.jobInfo[key];
        if(props){
            try {
                console.log("-------------"+key+"-----------");
                if(props.hasOwnProperty("api") && props.hasOwnProperty("props")){
                    jobInfo[key] = analysis_obj["analysis_"+props.api](props.props)
                } else if(props.only_value){
                    jobInfo[key] = analysis_obj.jobInfo[key]
                } else {
                    jobInfo[key] = analysis_obj["analysis_"+key](props)
                }
            } catch (e) {
                IKM.msg(e)
                console.log("========================================error:");
                jobInfo[key] = "_error"
            }
        }
    });
    console.log("===================================> 3 jobinfo")
    
    // 过滤掉料号信息的 _todo 和 _error
    for (var key in jobInfo) {
        if(jobInfo[key]==="yes"){jobInfo[key] = "Yes"}
        if(jobInfo[key]==="no"){jobInfo[key] = "No"}
        if(jobInfo[key] === "_todo" || jobInfo[key] === "_error"){
            delete jobInfo[key]
        }
    }
    console.log("=================================> 4 save job info")
    if(db_customer == "1352"){
        jobInfo.array_size_x = jobInfo.pcs_size_x
        jobInfo.array_size_y = jobInfo.pcs_size_y
        delete jobInfo.pcs_size_x
        delete jobInfo.pcs_size_y
    }
    save_job_info({
        jobid: JobId,
        jobinfohash: jobInfo
    })
    console.log("=================================> 5 save drill info")

 

    script_info({ msg: "Drill layer analysis",result_severity:"info",progress: 75 })
	global._ERRORMAG = "Error-Drill layer analysis"; 

    // * 获取pcs_step 和 array_step的钻孔信息
    var drill_tool_info = {};
    var drillLayers = analysis_obj.matrixInfo.mDrills;
    var steps_drillinfo = [
        {
            type:"pcs",
            name:par.pcs_step
        },
        {
            type: "array",
            name:par.array_step
        }
    ]

    steps_drillinfo.forEach(function(item){
        var type = item.type;
        var step = item.name;
        if(GEN.isStepExists({job:job, step:step})){
            GEN.openStep({job:job, name:step})
            drillLayers.forEach(function(layer){
                var layer = layer.name;
                if(!GEN.isLayerEmpty({job:job,step:step,layer:layer})){
                    var mv_layer = moveSlotDrls({job:job, step:step, layer:layer})

                    if (!global._extra) {
                        GEN.COM("tools_set,layer="+layer+",slots=by_length")
                    }
                    
                    GEN.COM("tools_merge_ex,layer="+layer+",mode=merge")
                    var tool = GEN.getTool({job:job,step:step,layer:layer,units:"mm"}); // {"1":{"count":"01156","num":"1","type":"plated","min_tol":"0","max_tol":"0","finish_size":"100.31","drill_size":"100.31","slot_len":"0","type2":"standard","shape":"hole","bit":0}}
                    if(!drill_tool_info[layer]){
                        drill_tool_info[layer] = tool;
                        for (var key in tool) {
                        var info = tool[key];
                        drill_tool_info[layer][key][type+"_count"] = info.count - 0;
                        drill_tool_info[layer][key].id = JSON.stringify({type:info.type,min_tol:info.min_tol,max_tol:info.max_tol,finish_size:info.finish_size,drill_size:info.drill_size,slot_len:info.slot_len,type2:info.type2,shape:info.shape})
                        }
                    } else {
                        for (var key in tool) {
                            var info = tool[key];
                            var id = JSON.stringify({type:info.type,min_tol:info.min_tol,max_tol:info.max_tol,finish_size:info.finish_size,drill_size:info.drill_size,slot_len:info.slot_len,type2:info.type2,shape:info.shape})
                            var ifNew = true;
                            for (var key2 in drill_tool_info[layer]) {
                                var info2 = drill_tool_info[layer][key2]
                                if(info2.id == id){
                                    var ifNew = false;
                                    if(!info2[type+"_count"]) {info2[type+"_count"] = 0}
                                    info2[type+"_count"] += info.count;
                                }
                            }
                            if(ifNew){
                                var num = Object.keys(drill_tool_info[layer]).length + 1;
                                info.num = num;
                                info[type+"_count"] = info.count - 0;
                                info.id = id;
                                drill_tool_info[layer][String(num)] = info
                            }
                        }
                    }
                    if(mv_layer) {
                        GEN.workLayer({name:mv_layer,display_number:2,clear_before:'yes'})
                        GEN.selCopyOther({dest:'layer_name',target_layer:layer,invert:'no',dx:0,dy:0,size:0})
                    }
                }
            })
            GEN.closeStep()
        }
    })

    // 删除 
    db.query("",function(q){
        return q.deleteRow({
            table:'pdm_job_cam_drill',
            where:{job_id:JobId}
        })
    });
    var seq_index = 1 ;
    // 钻孔管理器信息处理
	Object.keys(drill_tool_info).forEach(function(layer){
		var datalist = []
        var tool_num = 1 ;
		_.values(drill_tool_info[layer]).sort(function(a,b){return a.drill_size - b.drill_size}).forEach(function(item){
            var layer_name = layer
            var drill_type = item.type
            if(matrix[layer].type == "laser_drill" && /^[^\d]+(\d+)-(\d+)[^\d]?$/.test(layer_name)){ 
                drill_type = "laser"
                if(Number(item.drill_size) >= 150){
                    drill_type = "plated"
                }
                var info_drilll = /^[^\d]+(\d+)-(\d+)[^\d]?$/.exec(layer_name);
                layer_name = "l" + info_drilll[1] + "-" + info_drilll[2];
            } else if(layer_name === "ftdrill"){
                layer_name = "drill"
            } else if(/^[^\d]+(\d+)-(\d+)[^\d]?$/.test(layer_name)) {
                var info_drilll = /^[^\d]+(\d+)-(\d+)[^\d]?$/.exec(layer_name);
                layer_name = "d" + info_drilll[1] + "-" + info_drilll[2];
            }
            if(matrix[layer].type == "bury_drill"){
                drill_type = "via"
            }
            var slot_length_tmp = 0;
            if(item.slot_len && Number(item.slot_len)>0){
                slot_length_tmp = Number(item.slot_len) + Number(item.drill_size);
                slot_length_tmp = slot_length_tmp.toFixed(3)
            }
            var data = {
				job_id: JobId,
                layer_name: layer_name,
                tool_num: "T" + tool_num,
                tool_flag: tool_num,
                tool_type: item.shape,
                drill_type:drill_type,
                // drill_size : item.drill_size,
                drill_slot_length: slot_length_tmp,
                finish_size : Number(item.drill_size).toFixed(3),
                finish_slot_length: slot_length_tmp,
                // size_tol_lower: item.min_tol,
                // size_tol_upper: item.max_tol,
                pcs_count: item.pcs_count || 0,
                array_count: item.array_count || 0,
                seq: seq_index + tool_num/100,
                attr_data: {"org_layer_name":layer}
			}
            datalist.push(data)
            tool_num++
		})
        seq_index++
        // datalist存入
        datalist.forEach(function(data){
            db.query("",function(q){
                return q.insertRow({
                    table:'pdm_job_cam_drill',  
                    data:data
                })
            });
        })
	})
    
    console.log("=============================> 6 set smd bga");
    script_info({ msg: "SMD&BGA create" ,progress: 80,result_severity:"info" });
	global._ERRORMAG = "Error-SMD&BGA create"; 

    step_list.forEach(function(step){
        GEN.openStep({job:job,name:step})
        GEN.affectedLayer({affected:'no',mode:'all'})
        GEN.clearLayers()
        if(GEN.GEN_TYPE == "genesis"){
            GEN.COM("chklist_single,action=valor_cleanup_set_smd,show=yes")
            GEN.COM("chklist_cupd,chklist=valor_cleanup_set_smd,nact=1,params=((pp_layer=.type=signal|mixed&side=top|bottom)(pp_drill=No)(pp_rotate=No)(pp_ignore_covered=Yes)(pp_sm=No)(pp_types=Square\;Rect\;Oval)(pp_other=)(pp_delete=No)),mode=regular")
            GEN.COM("chklist_run,chklist=valor_cleanup_set_smd,nact=1,area=profile")
            GEN.COM("chklist_close,chklist=valor_cleanup_set_smd,mode=hide")
        }else{
            GEN.COM("chklist_from_lib,chklist=quotation_check,profile=none,customer=");
            GEN.chklistRun({
                chklist: "quotation_check",
                nact: 4,
                area: 'profile'
            })
            // GEN.COM("chklist_single,show=yes,action=valor_cleanup_set_smd")
            // GEN.COM("chklist_cupd,chklist=valor_cleanup_set_smd,nact=1,params=((pp_layer=.type=signal|mixed&side=top|bottom)(pp_work_on=SMD\;BGA)(pp_delete=No)(pp_types=Square\;Rect\;Oval)(pp_other_smd=)(pp_sm=No)(pp_drill=)(pp_rotate=No)(pp_ignore_covered=Yes)(pp_bga_types=Round)(pp_other_bga=)(pp_sm_bga=No)(pp_bga_max_pitch=70)(pp_bga_actions=Set attribute)(pp_bga_suffix=_bga)(pp_identify_gf=)),mode=regular")
            // GEN.COM("get_user_name")
            // GEN.COM("get_job_path,job="+job)
            // GEN.COM("disp_on")
            // GEN.COM("origin_on")
            // GEN.COM("chklist_cnf_act,chklist=valor_cleanup_set_smd,nact=1,cnf=no")
            // GEN.COM("chklist_run,chklist=valor_cleanup_set_smd,nact=1,area=profile")
            // GEN.COM("skip_next_pre_hook")
            // GEN.COM("chklist_run,chklist=valor_cleanup_set_smd,nact=1,area=profile")
            // GEN.COM("get_user_name")
            // GEN.COM("skip_current_command")
            // GEN.COM("disp_on")
            // GEN.COM("origin_on")
            // GEN.COM("show_tab,tab=Checklists,show=no")
        }
        // top bottom 层smd排除掉 r\d开头的圆形smd   // 与开窗touch 碰不到的删除属性
        analysis_obj.matrixInfo.mOuters.forEach(function(item){
            var layer = item.signalL
            var oping_layer = layer.solderL
            GEN.workLayer({name:layer,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            // var symbols_test = GEN.getLayerSymsHist({job:job,step:step,layer:layer},true);
            // GEN.INFO({units:"mm", entity_type:'layer', entity_path:job+'/'+step+'/'+layer, data_type:'SYMS_HIST'});
            // GEN.INFO_RESULT
            
            // var symbols_tmp = GEN.getLayerSymsHist({job:job,step:step,layer:layer})
            // var syms = Object.keys(symbols_tmp)
            // var filter_syms = syms.filter(function(item){
            //     if(/^rect(\d+\.\d+|\d+)x(\d+\.\d+|\d+)/ig.test(item)){
            //         var res = /^rect(\d+\.\d+|\d+)x(\d+\.\d+|\d+)/ig.exec(item).slice(1)
            //         var num1 = res[0], num2 = res[1]
            //         if(num1<3 || num2<3){return true}
            //     }
            //     return /^r\d+/.test(item)}
            // )
            GEN.selectByFilter({attribute:".smd", include_syms:"r*", exclude_syms:"rect*", profile:"in"})
            if(GEN.getSelectCount()>0){
                GEN.COM("sel_delete_atr,attributes=.smd")
            }
            if(oping_layer){
                GEN.COM("filter_atr_set,filter_name=popup,condition=yes,attribute=.smd")
                GEN.COM("sel_ref_feat,layers="+oping_layer+",use=filter,mode=disjoint,pads_as=shape,f_types=line\;pad\;surface\;arc\;text,polarity=positive\;negative,include_syms=,exclude_syms=")
                if(GEN.getSelectCount() > 0){
                    GEN.COM("sel_delete_atr,attributes=.smd")
                }
            }
        })
        GEN.closeStep()
    })
    console.log("============= ===============> 6 analysis smd")
    script_info({ msg: "SMD&BGA analysis",result_severity:"info"  })
    global._ERRORMAG = "Error-SMD&BGA analysis";
    // smd
    var smdInfo;
    try {
        if(!analysis_obj.matrixInfo.mOuters[0].solderL){throw "no mask"}
        smdInfo = smdAnalysis({job:job,steplist:[pcs_step],layers:analysis_obj.matrixInfo.mOuters,attr:".smd"})
    } catch (msg) {
        console.log("smdInfo:error:"+msg)
    }
    // bga
    console.log("===================================> 7 bga info")
    var bgaInfo;
    bgaInfo = bgaAnalysis({job:job,steplist:[pcs_step],layers:analysis_obj.matrixInfo.mOuters,attr:".bga"})
    // 保存
    console.log("============== =====================> 8 save bga smd info")
    var save_info = [smdInfo, analysis_obj.jobInfo.laser_info,bgaInfo];  // 保存 smd  和  bga数据
    save_info.forEach(function(item){
        if(JSON.stringify(item)!= "{}" && item){
            Object.keys(item).forEach(function(key){
                var val = item[key]
                save_layerinfo({
                    jobid: JobId,
                    layer: key,
                    layerinfohash: val
                })
            })
        }
    })
    console.log("================================> 9 copper_percent signal drill")
    // 分析layer info
    var stepList = [pcs_step];
    var oChecklistName = "mychecklist";
    var signalLayers = analysis_obj.matrixInfo.mSignals.map(function(v){return v.name});
    var drillLayers = analysis_obj.matrixInfo.mDrills.map(function(v){return v.name});

    console.log("=============================> 13 save copperArea copper_distribution")
    var copper_distribution_steplist= [pcs_step, array_step];
    copper_distribution_steplist.forEach(function(step){
        if(step){
            GEN.openStep({ job: job, name: step })
            // 铜面积 分step
            var copper_percent_tmp = signalLayers.map(function(v){
                var tmp = {layer:v}
                tmp.copper_percent = GEN.copperArea({layer1:v}).percent + "%"
                return tmp
            })
            copper_percent_tmp.forEach(function(item){
                var save_info2 = {
                    jobid: JobId,
                    layer: item.layer,
                }
                if(/^cad/ig.test(step)){
                    save_info2.layerinfohash = {
                        card_copper_distribution: item.copper_percent
                    }
                } else if(/^stp/ig.test(step)){
                    save_info2.layerinfohash = {
                        array_copper_distribution: item.copper_percent
                    }
                }
                save_layerinfo(save_info2)
            })
            GEN.closeStep()
        }
    })
    // var info = {
    //     min_line_width: ["line","user_nor_line"],
    //     min_line_spacing: ["c2c","l2l","user_nor_line2nor_line"],
    //     min_line2pad: ["p2line","user_010052nor_line","user_bga2nor_line-1","user_smd2nor_line","user_self_spacing_smd2l"],
    //     min_pad2pad: ["p2p", "smd2smd", "smd2pad","via2via","bga2pad"],
    //     min_ar_laser:["laser_via_ar"],
    //     min_ar_via:["via_ar"],    
    //     min_ar_pth:["pth_ar"],
    //     min_ar_npth:["npth_ar"]
    // }
    var info = {
        min_line_width: ["line","user_nor_line"],
        min_line_spacing: ["c2c","l2l","user_nor_line2nor_line","user_nor_line2surface"],
        min_line2pad: ["p2line","p2c"],
        min_pad2pad: ["p2p", "smd2smd", "smd2pad","via2via","bga2pad"],
        min_ar_laser:["laser_via_ar"],
        min_ar_via:["via_ar"],    
        min_ar_pth:["pth_ar","ar"],
        min_ar_npth:["npth_ar"]
    }
    save_job_info({
        jobid: JobId,
        jobinfohash: {ATS_surface_area_base_on:"Card"}
    })

    var jobpath = GEN.getJobPath({job:job})
    // if(GEN.GEN_TYPE == "genesis"){
    //     jobpath = GEN.getJobPath({job:job})
    // } else {
    //     jobpath = "/home/local_db/server_db/jobs/"+job
    // }
    script_info({ msg: "Signal layer analysis",result_severity:"info" ,progress: 85  })
	global._ERRORMAG = "Error-Signal layer analysis"; 
    stepList.forEach(function(step){
        GEN.openStep({ job: job, name: step })
        // 曝光
        analysis_obj.matrixInfo.mOuters.forEach(function(item){
            if(ALL.gold_fingers.length>0){
                var tmp_gold_info = {}
                ALL.gold_fingers.forEach(function(item2){
                    tmp_gold_info[item2.type] = item2.name
                })
                var layer_gold_type = /top/ig.test(item.signalL) ? "top" : "bot"
                if(tmp_gold_info[layer_gold_type]){
                    var tmp_area_layer = "gold_area_tmp"
                    if(GEN.isLayerExists({job:job, layer:tmp_area_layer})){
                        GEN.deleteLayer({job:job, layer:[tmp_area_layer]})
                    }

					GEN.copyLayer({source_job:job,source_step:step,source_layer:tmp_gold_info[layer_gold_type],dest_layer:tmp_area_layer,mode:'replace',invert:'no'});
                    GEN.COM("clip_area_strt")
                    GEN.COM("clip_area_end,layers_mode=layer_name,layer="+tmp_area_layer+",area=profile,area_type=rectangle,inout=outside,contour_cut=yes,margin=0,feat_types=line\;pad\;surface\;arc\;text")
                    // var tmp_info = GEN.copperArea({layer1:tmp_gold_info[layer_gold_type],resolution_value:1})
                    // var tmp_info = GEN.exposedArea({layer1:item.signalL,mask1:tmp_gold_info[layer_gold_type],resolution_value:1})
                    var tmp_info = GEN.exposedArea({layer1:item.signalL,mask1:tmp_gold_info[layer_gold_type],resolution_value:1})
                    var tmp_data = {};
                    if(layer_gold_type == "top") {
                        tmp_data.sf_area_ref_layer_front = tmp_gold_info[layer_gold_type]
                        tmp_data.sf_area_gold_area_front = tmp_info.area
                    }
                    if(layer_gold_type == "bot"){
                        tmp_data.sf_area_ref_layer_back = tmp_gold_info[layer_gold_type]
                        tmp_data.sf_area_gold_area_back = tmp_info.area
                    }
                    save_job_info({
                        jobid: JobId,
                        jobinfohash: tmp_data
                    })
                    save_layerinfo({
                        jobid: JobId,
                        layer: item.signalL,
                        layerinfohash: {
                            sf_area_ref_layer: tmp_gold_info[layer_gold_type],
                            sf_area_gold_area: (tmp_info.area *1.1* 645.16 / 10000).toFixed(4)
                        }
                    })
                }
            }
            if(item.solderL){
                // var tmp_info =  GEN.copperArea({layer1:item.solderL,resolution_value:1})
                var tmp_info = GEN.exposedArea({layer1:item.signalL,mask1:item.solderL,resolution_value:1})
                // var tmp_info = GEN.exposedArea({layer1:item.signalL,mask1:item.solderL,resolution_value:1})
                // {"area":"0.73817","percent":"8.986"}
                var tmp_area =  tmp_info.area
                var tmp_percent =  tmp_info.percent;
                // todo carbon var tmp_data = /top/ig.test(item.signalL) ? {carbon_Area_front:tmp_area} : {carbon_Area_back:tmp_area}
                // save_job_info({
                //     jobid: JobId,
                //     jobinfohash: tmp_data
                // })
                save_layerinfo({
                    jobid: JobId,
                    layer: item.signalL,
                    layerinfohash: {
                        ref_layer: item.solderL,
                        exposed_area: (tmp_area * 645.16 / 10000).toFixed(4) 
                    }   
                })
            }
            // 新增最小Pad/SM开窗
            GEN.affectedLayer({affected:'no',mode:'all'})
            GEN.selClearFeature()
            GEN.workLayer({name:item.signalL,display_number:2,clear_before:'yes'})
            GEN.INFO({units:"mm", entity_type:'layer', entity_path:job+'/'+step+'/'+item.signalL, data_type:'SYMS_HIST'});
            if(GEN.INFO_RESULT.gSYMS_HISTsymbol){
                GEN.selectByFilter({feat_types:"pad", profile:"in"})
                if(GEN.getSelectCount() > 0){
                    selCopyLayer({job:job, layer:item.signalL + "_pad"})
                    var syms = GEN.getLayerSymsHist({job:job,step:step,layer:item.signalL + "_pad"})
                    save_layerinfo({
                        jobid: JobId,
                        layer: item.signalL,
                        layerinfohash: {
                            org_min_pad_size: getMinSym(syms,1)
                        }
                    })
                    if(item.solderL) {
                        GEN.workLayer({name:item.solderL,display_number:2,clear_before:'yes'})
                        GEN.selRefFeat({layers:item.signalL + "_pad", use:"filter", mode:"touch"})
                        if(GEN.getSelectCount() >0) {
                            selCopyLayer({job:job, layer:item.signalL + "_sm"})
                            var syms2 = GEN.getLayerSymsHist({job:job,step:step,layer:item.signalL + "_sm"})
                            save_layerinfo({
                                jobid: JobId,
                                layer: item.signalL,
                                layerinfohash: {
                                    org_min_pad_sm_opening_size: getMinSym(syms2,1)
                                }
                            })
                            GEN.deleteLayer({job:job, layer:item.signalL + "_sm"})
                        }
                    }
                    GEN.deleteLayer({job:job, layer:item.signalL + "_pad"})
                }
            }
        })
        GEN.clearLayers()
        GEN.affectedLayer({ affected: 'no', mode: 'all' });
        
        
        console.log("==================================> Drill analysis")
        // 钻孔
        var drillToSignals = drillLayers.map(function(v){  // 获取到钻孔层对应的顶层和底层
            var simbols = GEN.getLayerSymsHist({job:job,step:step,layer:v})
            var symbol = _.values(simbols).sort(function(a,b){return Number(a.size)-Number(b.size)})
            .reduce(function(a,b){
                if(b.pad!="0"){
                    a.push(b.symbol)
                }
                return a
            },[])[0]
            return {layer:v,symbol:symbol,start:tmp_matrix[v]["drl_start"],end:tmp_matrix[v]["drl_end"]}
        })
        

        var drillRes = analysisDrill(drillToSignals,step) // 钻孔分析结果
        drillRes.forEach(function(item){
            save_layerinfo({
                jobid: JobId,
                layer: item.layer,
                layerinfohash: {
                    drl_pad_top: item.drl_pad_top,
                    drl_pad_bot: item.drl_pad_bot,
                }
            })
        })

        console.log("==================================> chk signals analysis")

        // 删除线路层的外形线
        if(GEN.isLayerExists({job:job, layer:"out_rout"})){GEN.deleteLayer({job:job, layer:"out_rout"})}
        GEN.units({type:"mm"})
        GEN.COM("profile_to_rout,layer=out_rout,width=1") 
        GEN.units({type:"inch"})
        GEN.affectedLayer({affected:'no',mode:'all'})
        signalLayers.forEach(function(signal){
            GEN.workLayer({name:signal,display_number:2,clear_before:'yes'})

            GEN.COM("filter_set,filter_name=popup,update_popup=no,feat_types=line")
            
            GEN.selRefFeat({layers:'out_rout',use:'filter',mode:'touch'})
            if(GEN.getSelectCount() > 0) {GEN.selDelete()}
            GEN.COM("filter_reset,filter_name=popup")
        })

        // 创建chklist并运行 如果chklist存在先删除
        
        // var tmpitem = {
        //     name: "signal_layer_checks",
        //     nact: 1,
        //     action: "valor_analysis_signal",
        //     params: { 
        //         pp_layer: ".affected",
        //         pp_spacing: 20,
        //         pp_selected: "All",
        //         pp_r2c: 10,
        //         pp_d2c: 15,
        //         pp_sliver: 8,
        //         pp_min_pad_overlap: 5,
        //         pp_check_missing_pads_for_drills: "Yes",
        //         pp_use_compensated_rout: "Skeleton",
        //         pp_sm_spacing: "Yes"
        //     }
        // }
        // if(par.erf){
        //     tmpitem.erf = par.erf
        // }
        // createChklistAndRun({   // 创建checklist并运行
        //     layers: signalLayers,
        //     items: [tmpitem]
        // })
        script_info({ msg: "Run Checklist",result_severity:"ok" })
	    global._ERRORMAG = "Error-Run Checklist"; 

        // if (GEN.isChklistExists({ job: job, step: step, chklist: oChecklistName })) {
        //     GEN.COM("chklist_delete", { chklist: oChecklistName })
        // }
        GEN.COM("chklist_from_lib,chklist=quotation_check,profile=none,customer=");
        GEN.chklistRun({
            chklist: "quotation_check",
            nact: 1,
            area: 'profile'
        });
        GEN.chklistRun({
            chklist: "quotation_check",
            nact: 2,
            area: 'profile'
        });
        GEN.chklistRun({
            chklist: "quotation_check",
            nact: 3,
            area: 'profile'
        });
        GEN.chklistRun({
            chklist: "quotation_check",
            nact: 5,
            area: 'profile'
        });
        // signal层分析结果
        script_info({ msg: "Error-get ChkRes",result_severity:"ok" })
        var res = analysisChkAttr({layers:signalLayers, info:info, step:step, job:job, oChecklistName:"quotation_check",jobpath:jobpath})
        exportInfo(res);
        //  数据入库
        Object.keys(res).forEach(function(key){
            var val = res[key]
            for(var key2 in val){
                if(key2 == "min_ar_npth"){
                    if(Number(val[key2]) == 0 || Number(val[key2]) == 999){
                        delete val.min_ar_npth
                    }
                }
            }
            save_layerinfo({
                jobid: JobId,
                layer: key,
                layerinfohash: val
            })
        })
        GEN.closeStep()
    })
    //  !! mvOutProfile({job:job, step:pcs_step})
    global._ERRORMAG = false; 
    saveMeans({job:job,step:pcs_step})

    // zip压缩 jobpath/user/opcam
    // var zip = require('topsin.zip');
    // var iSrcDir = jobpath + "/user/opencam";
    // var iDestZipFile = jobpath + "/user/opencam.zip";
    // zip.zipDir(iSrcDir,iDestZipFile);
    // fs.rmdir(jobpath + "/user/opencam")


    // jobpath
    // process.exec("cp",["-r",jobpath,"/home/toplinker/samba/export/"+job])
    if(db_customer == "2171"){
		var matrix_2171 = GEN.getMatrix({job:job});
		var comp = Object.keys(matrix_2171).filter(function(v){  // 如果有comp层 删除
			return /comp/ig.test(v)
		})
		if (comp.length > 0){
			var tmp = GEN.getStepList({job:job})
			GEN.openStep({job:job,name:tmp[0]})
			GEN.COM("delete_comp")
			GEN.closeStep()
        }
    }
    // 保存 
	if(/yes/ig.test(par.auto_save)){
		GEN.checkInout({job:job,mode:"out"})  // 结束保存料号 关闭料号
		GEN.saveJob({ job: job });
		GEN.checkInout({job:job,mode:"in"})
		GEN.closeJob({job:job})
	} else {
		GEN.checkInout({job:job,mode:"in"})
	}

    var job_attrs = db.query("",function(q){
		return q.selectValue({
			table:'pdm_job',
			field_format:{job_attrs:'json'},
			field:'job_attrs',
			where:{id : JobId}
		})
	});
	if (!job_attrs ) { job_attrs = {}} 
	job_attrs.readin_result = "Readin"
	db.query("",function(q){
		return q.updateRow({
			table:'pdm_job',
			data:{job_attrs:job_attrs},
			update_policy:{attr_data:'json_merge'},
			where:{id : JobId}
		})
	});
    
    script_info({ msg: "analysis done" ,result_severity:"info"})
    
    //! means信息无法导出
    if(par.export_path != "" && par.export_mode != "" && par.export_submode != "" ){
		if(fs.exists(par.export_path)){
            if(GEN.GEN_TYPE == "genesis"){
                GEN.COM("export_job,job="+job+",path="+par.export_path+",mode="+par.export_mode+",submode="+par.export_submode+",overwrite=yes")
            } else {
                GEN.COM("export_job",{job:job,path:par.export_path,mode:par.export_mode,submode:par.export_submode,overwrite:"yes",format:"genesis",output_name:job})
                GEN.COM("disp_on")
                GEN.COM("origin_on")
            }
            // jobpath + "/user/opencam";   par.export_path + "/" + job + ".tgz"
            // 到导出路径下解压 tgz 
            process.exec("/bin/tar",["-zxvf",par.export_path + "/" + job + ".tgz","-C",par.export_path])
            // jobpath + "/user/opencam"
            process.exec("/bin/cp",["-r",jobpath + "/user/opencam",par.export_path + "/" + job+"/user/"])
            // 将导出路径下的  压缩成 tgz
            process.exec("cd" ,[par.export_path,"&&","/bin/tar","-zcvf", "/tmp/tmp/" + job + ".tgz", job])
            // fs.rmdir(par.export_path + "/" + job)
        } else {
			resultData.push({ type: "error", title: "导出路径不存在!", detail: [{ desc: par.export_path }] });
		}
    }
    
    if(/yes/.test(par.send_email) ){
        // 邮件触发
        sendEmail({
            subject:db_customer + ' / '+job+' reading is ready!',
            content: db_customer + ' / '+job+' reading is ready!'
        })
    }
    
 
	if (mode === "aimdfm") {
		$.QDfm.updateRow({
			table: "pdm_aimdfm_task",
			data: {
                result_severity:"ok",
				progress: 100
			},
			where: { id: $.task_id }
		});
		if (GEN.hasError()) {
			Status = 'error';
			resultData.push({ type: "error", title: "GEN错误!", detail: [{ desc: _.join(GEN.STATUS, "\n") }] });
			return {
				status: Status,
				result_data: resultData
			};
		} else {
			resultData.push({ type: "info", title: "操作完成, 请注意检查!" });
			return {
				status: Status,
				result_data: resultData
			};
		}
	}else {
		return "Done"
	}		
}
catch (e) {
    script_info({ result_severity: "error" })
    GEN.COM("open_job,job="+Job+",open_win=no,disk_map=,job_map=")
    GEN.AUX("set_group,group=99")
    GEN.COM("close_job,job="+Job+"")
    GEN.COM("disp_on")
    GEN.COM("origin_on")
    GEN.COM("disp_on")
    GEN.COM("origin_on")
    GEN.COM("checkin_closed_job,job="+Job)
    if( cam_workflow_info== "sendmail"){
	    IKM.crud("deleteRow", {
            table: "pdm_job_jobattr",
            where:{job_id:JobId, attr_name:"cam_workflow_info"},
        })
        IKM.save_job_info({jobid:JobId, jobinfohash:{cam_workflow_info:"err"}})
        // sendEmail({
        //     subject:db_customer + ' / '+Job+ " analysis error",
        //     content:_.toString(e)
        // })
    }
    if(global._ERRORMAG){
        script_info({ msg: global._ERRORMAG });
        var job_attrs = db.query("",function(q){
			return q.selectValue({
				table:'pdm_job',
				field_format:{job_attrs:'json'},
				field:'job_attrs',
				where:{id : JobId}
			})
		});
		if (!job_attrs ) { job_attrs = {}}
		job_attrs.readin_result = global._ERRORMAG
		db.query("",function(q){
			return q.updateRow({
				table:'pdm_job',
				data:{job_attrs:job_attrs},
				update_policy:{attr_data:'json_merge'},
				where:{id : JobId}
			})
		});
	}
    IKM.msg(_.join(GEN.STATUS, "\n"))
	IKM.msg(e)
    Status = 'error';
    resultData.push({type: "error", title: "脚本执行出错!", detail: [{desc: _.toString(e)}]});
	return (mode === "aimdfm") ? {status: Status, result_data: resultData} : "Error";
}

function UPLOAD_LAYER_MATRIX(props){
    var job = props.job
    props.jobcategory = props.jobcategory || "work"
    var matrix = ANALYSIS_STACKUP({job:job, jobcategory:props.jobcategory});
    Omatrix = matrix
    var layers = Object.keys(matrix).sort(function(a,b){return matrix[a]["row"] - matrix[b]["row"]})
    var tableName = "pdm_job_" + props.jobcategory + "_layer";
    console.log("===========>matrix upload");
    var oLayers = db.query("",function(q){
        return q.selectMapMap({
            table:tableName,
            field: ["odb_name", "release_status"],
            where:{job_id:JobId},
            uniquefield: "odb_name"
        })
    });
    db.query("",function(q){
        return q.deleteRow({
            table:tableName,
            where:{job_id:JobId},
        })
    });
    var fieldLst = ["name", "odb_context", "odb_type", "odb_polarity", "odb_side", "drl_start", "drl_end",
        "row_num", "type", "drl_start_num", "drl_end_num", "drl_from_num", "drl_to_num", "drl_connect_to",
        "odb_name", "side", "stackup_num", "customer_field", "input_file_name", "odb_row_num"
    ];
    layers.forEach(function(layer){
        var layerInfo = matrix[layer];
        var tmpData = {
            "job_id": JobId,
            "name": layerInfo["name"]
        };
        tmpData["release_status"] = (oLayers&&oLayers.hasOwnProperty(layerInfo["odb_name"])) ? oLayers[layerInfo["odb_name"]]["release_status"] : null;
        for (n = 0; n < fieldLst.length; n++) {
            tmpData[fieldLst[n]] = layerInfo[fieldLst[n]];
        }
        db.query("",function(q){
            return q.insertRow({
                table:tableName,
                data: tmpData
            })
        });
    })
    console.log("===========>matrix upload end");
    
    return matrix
}
function ANALYSIS_STACKUP(props){
    var job = props.job
    if(!props.hasOwnProperty("jobcategory")){ props.jobcategory = 'work' }
    var matrix = GEN.getMatrix({job:job})
    var layer_count = GEN.getLayerCount({job:job}) // !
    save_job_info({jobid:JobId,jobcategory:props.jobcategory,jobinfohash:{TL_layer_count:layer_count}});
    
    var job_attrs = db.query("",function(q){
		return q.selectValue({
			table:'pdm_job',
			field_format:{job_attrs:'json'},
			field:'job_attrs',
			where:{id : JobId}
		})
	});
	if (!job_attrs ) { job_attrs = {}} 
	job_attrs.layer_count = layer_count
	db.query("",function(q){
		return q.updateRow({
			table:'pdm_job',
			data:{job_attrs:job_attrs},
			update_policy:{attr_data:'json_merge'},
			where:{id : JobId}
		})
	});

	_.values(matrix).sort(function(a,b){return a.row-b.row}).forEach(function(layer){
        layer.odb_name = layer.name;
		layer.name = layer.tl_name;
		layer.odb_context = layer.context;
		layer.odb_type = layer.layer_type;
		layer.odb_polarity = layer.polarity;
		layer.odb_side = layer.side;
		layer.row_num = layer.tl_num;
		layer.type = layer.tl_type;
		layer.side = layer.enum_tl_side;
		layer.input_file_name = layer.file_name;
		layer.odb_row_num = layer.row;
		if (layer.odb_name == 'ftdrill'){
			layer.name = 'drill';
			layer.drl_start_num = 1;
			layer.drl_end_num = layer_count;
			layer.drl_from_num = 1;
			layer.drl_to_num = layer_count;
			layer.type = 'main_drill';
        }
		else if (/^ftdrill(\d+)-(\d+)/.test(layer.odb_name)){  // /^d(\d+)\-(\d+)$/
            var tmp = /^ftdrill(\d+)-(\d+)/.exec(layer.odb_name)
			var drl_star = tmp[1];
			var drl_end = tmp[2];
			layer.drl_start_num = drl_star;
			layer.drl_end_num = drl_end;
			layer.drl_from_num = drl_star;
            layer.drl_to_num = drl_end;
            if(drl_end - drl_star == 1){
                layer.name = 'laser' + drl_star + '-' + drl_end;
				layer.type = 'laser_drill';              // 镭射
            } else if( drl_star == 1 || drl_end == layer_count ){
                layer.name = 'drill' + drl_star + '-' + drl_end;
				layer.type = 'blind_drill';             // 埋孔
			} else{
                layer.name = 'drill' + drl_star + '-' + drl_end;
				layer.type = 'bury_drill';              // 盲孔
			}
		}
		else if (/\-a$/.test(layer.odb_name)){
            layer.side = 'top';
            if(!layer.name){
                layer.name = '__'+layer.odb_name+'__'
            }
            if(!layer.type){
                layer.type = 'other'
            }
		}
		else if (/\-b$/.test(layer.odb_name)){
            layer.side = 'bottom';
            if(!layer.name){
                layer.name = '__'+layer.odb_name+'__'
            }
            if(!layer.type){
                layer.type = 'other'
            }
		}
		else{
            if(!layer.name){
                layer.name = '__'+layer.odb_name+'__'
            }
			if(!layer.type){
                layer.type = 'other'
            }
		}
    })
    return matrix;
}
function getMatrixInfo(props){  // 获取matrix各种信息
    var job = props.job
    var matrix = GEN.getMatrix({job:job})
    var res = {
        matrix: matrix,   // matrix
        matrixVal: [],   // matrix 的 value数组
        mSignal: {},   // board的signal层 {}
        mSignals: [],   // board的signal层  []
        mDrill: {},  // board的drill层
        mDrills: [],  // board的drill层
        mSolderMask: {},    // 防焊层
        mSolderMasks: [],   // 防焊层
        mOuters: [],    // 外层和对于的防旱
        mBoardLayer: []   // board层
    }
    for (var key in matrix) {
        var value = matrix[key]
        res.matrixVal.push(value)
        if(value.context == "board"){
            res.mBoardLayer.push(key)
            switch (value.layer_type) {
                case "signal":
                    res.mSignal[key] = value
                    res.mSignals.push(value)            
                    break;
                case "drill":
                    res.mDrill[key] = value
                    res.mDrills.push(value)        
                    break;
                case "solder_mask":
                    res.mSolderMask[key] = value
                    res.mSolderMasks.push(value)        
                    break;
            }
        }
    }
    res.matrixVal = res.matrixVal.sort(function(a,b){return Number(a.row) - Number(b.row)})
    res.mSignals = res.mSignals.sort(function(a,b){return Number(a.row) - Number(b.row)})
    res.mDrills = res.mDrills.sort(function(a,b){return Number(a.row) - Number(b.row)})
    // 找出外层  以及外层对应的防焊层 [{signalL:"top", solderL:"smt"}]
    res.mOuters = res.mSignals.reduce(function(a,b){
        if(b.tl_type == "outer"){
			var solderLayers = res.mSolderMasks.filter(function(v){
				return v.side == b.side
			})
           	a.push({
                signalL: b.name,
                solderL: solderLayers.length >0 ? solderLayers[0]["name"] : null
           	})
        }
        return a
    },[])
    return res
}
function selCopyLayer(props){  // 拷贝选择的到辅助层
    var layer = props.layer
    var job = props.job
    if(GEN.isLayerExists({job:job,layer:layer})){
        GEN.deleteLayer({job:job,layer:layer})
    }
    GEN.selCopyOther({dest:'layer_name',target_layer:layer})
}
function save_job_info(props){  //  保存料号信息
    var jobid = props.jobid;
    var jobinfohash = props.jobinfohash;

    Object.keys(jobinfohash).forEach(function(key){
        var val = jobinfohash[key];
        var value = db.query("",function(q){
            return q.selectValue({
                table:'pdm_job_jobattr',  
                field:"value", 
                where:{job_id:jobid, attr_name: key}
            })
        });
        if(/done/ig.test(value) || !value){
            db.query("",function(q){
                return q.insertRow({
                    table:'pdm_job_jobattr',  // todo 
                    data:{job_id:jobid, attr_name:key, value:val}, // todo
                    return_field:'job_id',
                })
            });
        } else if (value != val) {
            db.query("",function(q){
                return q.updateRow({
                    table:'pdm_job_jobattr',
                    where:{job_id:jobid, attr_name: key},
                    data:{value:val},
                })
            });
        }
    })
}
function save_layerinfo(props){   //  保存层信息
    var jobid = props.jobid;
    var layer = props.layer;
    // if(Omatrix[layer] && Omatrix[layer].tl_name){
    //     if(!(/^drill/ig.test(Omatrix[layer].tl_name))){
    //         layer = Omatrix[layer].tl_name
    //     }
    // }
    if(Omatrix[layer] && Omatrix[layer].name){
        // if(!(/^drill/ig.test(Omatrix[layer].tl_name))){
            layer = Omatrix[layer].name
        // }
    }
    var layerinfohash = props.layerinfohash;
    Object.keys(layerinfohash).forEach(function(key){
        var val = layerinfohash[key];
        if(val && val != ""){
            var value = db.query("",function(q){
                return q.selectValue({
                    table:'pdm_job_layerattr',  
                    field:"value", 
                    where:{job_id:jobid, attr_name: key, layer:layer}
                })
            });
            if(/done/ig.test(value) || !value){
                db.query("",function(q){
                    return q.insertRow({
                        table:'pdm_job_layerattr',  // todo 
                        data:{job_id:jobid, attr_name:key, value:val, layer:layer}, // todo
                        return_field:'job_id',
                    })
                });
            } else if (value !== val) {
                db.query("",function(q){
                    return q.updateRow({
                        table:'pdm_job_layerattr',
                        where:{job_id:jobid, attr_name: key, layer:layer},
                        data:{value:val},
                    })
                });
            }
        }
    })
}
function save_stack_info(props){
    var info = props.info
    var table = "pdm_job_stack_drills"
    info.job_id = JobId
    var drl_name = info.drl_name
    db.query("",function(q){
        return q.deleteRow({
            table:table,  
            where:{job_id:JobId, drl_name: drl_name}
        })
    });
    var Re = db.query("",function(q){
        return q.insertRow({
            table:table,  
            data:info, 
            return_field:'job_id',
        })
    });
}
function save_panel_info(props){
    var info = props.info
    var table = props.table
    info.job_id = JobId
    var job_id = info.job_id || JobId
    var step_name = info.step_name
    db.query("",function(q){
        return q.deleteRow({
            table:table,  
            where:{job_id:job_id, step_name: step_name}
        })
    });
    db.query("",function(q){
        return q.insertRow({
            table:table,  
            data:info
        })
    });
}
function analysis(props){
    var job = props.job || Job;
    var jobId = props.jobId || JobId;
    var pcs_step = props.pcs_step || false;
    var array_step = props.array_step || false;
    var matrix = props.matrix;
    function T_m_p(props){
        this.job = props.job;
        this.jobInfo = {};
        this.jobId = props.jobId;
        if(props.pcs_step){ this.pcs_step = props.pcs_step; }
        if(props.matrix){ this.matrix = props.matrix; }
        if(props.array_step){ 
            
            this.array_step = props.array_step; 
        }
        this.matrixInfo = getMatrixInfo({job:this.job})
        this.init();
    }
    T_m_p.prototype.init = function(){
        var init_checking = ["job", "jobId", "pcs_step", "matrix"];
        var t = this;
        init_checking.forEach(function(v){ if(!t[v]){t.e(v+" is undefined")} })
    }
    T_m_p.prototype.e = function(e){  // 处理error
        throw e
    }
    T_m_p.prototype.analysis_layer_count = function(props){   // 分析board属性的层数量 []string
        var matrix = this.matrixInfo.matrixVal;
        var res = matrix.filter(function(v){return v.context == "board" && props.indexOf(v.layer_type) >= 0 })
        return res.length
    }
    T_m_p.prototype.analysis_vc_card_size = function(props){   // card短边尺寸 string
        if(db_customer == "1352"){
            var signals = this.matrixInfo.mSignals.map(function(v){return v.name})
            GEN.openStep({job:this.job,name:this.pcs_step})
            GEN.selClearFeature()
            GEN.affectedLayer({affected:'yes',layer:signals,clear_before:'yes'})
            GEN.selectByFilter({profile:"in"});
            if(GEN.getSelectCount() > 0){
                if(GEN.isLayerExists({job:this.job,layer:"_signals"})){GEN.deleteLayer({job:this.job,layer:["_signals"]})}
                GEN.selCopyOther({dest:'layer_name',target_layer:'_signals',invert:'no',dx:0,dy:0,size:0})
                var tmp = GEN.getLayerLimits({job:this.job,step:this.pcs_step,layer:'_signals',units:'mm'})
                var size1 = Number(tmp.xsize);
                var size2 = Number(tmp.ysize);
                if(size1 > size2){
                    var tmpsize = size2
                    size2 = size1
                    size1 = tmpsize
                }
                var tmp_width = 0;
                // 获取边线宽度 
                if(GEN.isLayerExists({job:this.job,layer:"tmp2"})){GEN.deleteLayer({job:this.job,layer:["tmp2"]})}
                GEN.COM("profile_to_rout,layer=tmp2,width=1")
                // 看_signals是否有跟tmp2碰到 
                GEN.workLayer({name:"_signals",display_number:2,clear_before:'yes'})
                GEN.COM("filter_reset,filter_name=popup")
                GEN.COM("filter_set,filter_name=popup,update_popup=no,feat_types=line")
                GEN.COM("sel_ref_feat,layers=tmp2,use=filter,mode=touch,pads_as=shape,f_types=line\;pad\;surface\;arc\;text,polarity=positive\;negative,include_syms=,exclude_syms=")
                if(GEN.getSelectCount() > 0) {
                    // 获取选择东西的 symbol
                    var feats = GEN.getFeatures({job:this.job,step:this.pcs_step,layer:"_signals",options:"select",units:"mm"});  
                    GEN.selClearFeature()
                    var minSyms = feats.map(function(v){return (v.symbol.slice(1) - 0)})
                    minSyms.sort();
                    tmp_width = minSyms[0]/1000
                }
                if(props == "xsize"){
                    return (size1-tmp_width).toFixed(2) //
                }
                if(props == "ysize"){
                    return (size2-tmp_width).toFixed(2) //
                }
            }
            GEN.closeStep()
        }
        return GEN.getProfileLimits({job:this.job,step:this.pcs_step,units:"mm"})[props].toFixed(2)
    }
    T_m_p.prototype.analysis_vc_array_size = function(props){   // array长边尺寸 string
        if(!this.array_step){return "_error"}
        var tmp = GEN.getProfileLimits({job:this.job,step:this.array_step,units:"mm"})
        var size1 = Number(tmp.xsize)
        var size2 = Number(tmp.ysize)
        if(size1 > size2){
            var tmpsize = size2
            size2 = size1
            size1 = tmpsize
        }
        if(props == "xsize"){
            return size1.toFixed(2)
        }
        if(props == "ysize"){
            return size2.toFixed(2)
        }
    }
    T_m_p.prototype.analysis_vc_pcs_count_on_panel = function(props){  // 
        if(!this.array_step){return "_error"}
        var has_step = has_steps({job:this.job, pcs_step:this.pcs_step ,array_step:this.array_step})  // 有无拼版关系
        if(has_step){
            // arr 中 pcs数量
            var tmp = GEN.getRepeat({job:job, step:array_step})
            return tmp.length
        }
        return "_todo"
    }
    T_m_p.prototype.analysis_pcs_count_on_array = function(props){  // 
        if(!this.array_step){return "_error"}
        var has_step = has_steps({job:this.job, pcs_step:this.pcs_step ,array_step:this.array_step})  // 有无拼版关系
        if(has_step){
            // arr 中 pcs数量
            var tmp = GEN.getRepeat({job:job, step:array_step})
            return tmp.length
        }
        return "_todo"
    }
    T_m_p.prototype.analysis_stack_vias_number = function(props){        // via孔连续叠加的层数 
        var t = this;
        var res;
        // 找出 镭射孔
        var laser_layers = []
        for (var key in t.matrix) {
            var val = t.matrix[key]
            if(val.type == "laser_drill"){
                laser_layers.push(val)
            }
        }
        if(laser_layers.length == 0){return "_error"}
        GEN.openStep({job:t.job,name:t.pcs_step})
        laser_layers = laser_layers.sort(function(a,b){return Number(a.row)-Number(b.row)})
        var laser_info = {}
        for(var i = 0; i < laser_layers.length-1; i++){
            var start_layer = laser_layers[i].odb_name
            var cover_layer = laser_layers[i+1].odb_name
            GEN.workLayer({name:start_layer,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            GEN.selRefFeat({layers:cover_layer,use:'filter',mode:'touch'})
            var count = GEN.getSelectCount()
            if(count>0){
                if(!laser_info[start_layer]){
                    laser_info[start_layer] = {}
                }
                laser_info[start_layer].drill_connect_layer = cover_layer
                laser_info[start_layer].drill_connect_point = count
            }
        }
        // via孔连续叠加的层数
        var via_vias_info = []
        function analysis_via_number(layers){
            if(layers.length < 2){return}
            var startlayer = layers[0].odb_name + "_cover" 
            GEN.selClearFeature()
            GEN.workLayer({name:layers[0].odb_name,display_number:2,clear_before:'yes'})
            GEN.selAllFeat()
            selCopyLayer({job:t.job,layer:startlayer})
            var end_index = start_cover_next(startlayer,layers,0)
            via_vias_info.push(end_index+1)
            analysis_via_number(layers.slice(end_index))
        }
        function start_cover_next(start, layers, end_index){
            if(layers.length < 2){
                GEN.deleteLayer({job:t.job,layer:start})
                return end_index
            }
            layers = layers.slice(1)
            GEN.workLayer({name:start,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            GEN.selRefFeat({layers:layers[0].odb_name,use:'filter',mode:'cover'})
            var count = GEN.getSelectCount()
            if (count < 1) {
                GEN.deleteLayer({job:t.job,layer:start})
                return ++end_index
            }
            var nextstartlayer = layers[0].odb_name + "_cover" 
            selCopyLayer({job:t.job,layer:nextstartlayer})
            GEN.deleteLayer({job:t.job,layer:start})
            return start_cover_next(nextstartlayer,layers,++end_index)
        }
        analysis_via_number(laser_layers.slice())
        if (via_vias_info.length == 1){
            res = via_vias_info[0]
        }else{
            res = via_vias_info.reduce(function(a,b){return a-b>0?a :b})
        }
        t.jobInfo.stack_vias_more = "no"
        if(GEN.getLayerCount({job:t.job}) > 14 && res >= 12){
            t.jobInfo.stack_vias_more = "yes"
        }
        t.jobInfo.laser_info = laser_info;
        return res
    }
    T_m_p.prototype.analysis_layer_exist2 = function(props){        // 存在某某层? string 
        var layers = Object.keys(this.matrixInfo.matrix)
        if(/string/ig.test(typeof(props))){
            return layers.indexOf(props) >= 0 ? "yes" : "no"
        } else {
            var res = "no"
            props.forEach(function(v){
                if(layers.indexOf(v)>=0){
                    res = "yes"
                }
            })
            return res
        }
    }
    T_m_p.prototype.analysis_layer_exist = function(props){        // 存在某某层? string 
        var layers = Object.keys(this.matrixInfo.matrix)
        if(/string/ig.test(typeof(props))){
            return layers.indexOf(props) >= 0 ? "yes" : "no"
        } else {
            var res = "no"
            props.forEach(function(v){
                if(layers.indexOf(v)>=0){
                    res = "yes"
                }
            })
            return res
        }
    }
    T_m_p.prototype.analysis_laser_via_on_buried_hole = function(){        // via孔在埋孔上
        var t = this
        // 找出 镭射孔 机械孔
        var laser_layers = []
        var buried_hole = []
        for (var key in t.matrix) {
            var val = t.matrix[key]
            if(val.type == "laser_drill"){
                laser_layers.push(key)
            } else if(val.type == "blind_drill" || val.type == "bury_drill"){
                buried_hole.push(key)
            }
        }
        var res = "no"
        if(buried_hole.length>0 && laser_layers.length){  // 有机械孔 和镭射
            GEN.openStep({job:t.job,name:t.pcs_step})
            GEN.affectedLayer({affected:'no',mode:'all'})
            try {
                laser_layers.forEach(function(laser){
                    GEN.workLayer({name:laser,display_number:2,clear_before:'yes'})
                    GEN.selClearFeature()
                    buried_hole.forEach(function(buried){
                        GEN.selRefFeat({layers:buried,use:'filter',mode:'touch'})
                        if(GEN.getSelectCount()>0){
                            res = "yes"
                            throw "yes"
                        }
                    })
                })
            } catch (msg) { }
            GEN.closeStep()
        }
        return res
    }
    T_m_p.prototype.analysis_milling_bit_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_milling_length_card = function(){        // todo 
        var allStep = [this.pcs_step, this.array_step].filter(function(v){return v})
        var res = 0
        var that = this;
        console.log("============analysis_milling_length_card=============");
        allStep.forEach(function(step){
            if(GEN.isStepExists({job:that.job,step:step})){
                GEN.openStep({job:that.job,name:step})
                var tmp = "length_tmp"
                if(GEN.isLayerExists({job:that.job,layer:tmp})){GEN.deleteLayer({job:that.job,layer:[tmp]})}
                GEN.createLayer({job:that.job,layer:tmp,conext:'misc',type:'document'})
                GEN.COM("profile_to_rout,layer="+tmp+",width=1")
                var feautres = GEN.getFeatures({job:that.job,step:step,layer:tmp,units:"mm"})
                // 根据feautres 算出周长
                var milling = Object.keys(feautres).reduce(function(a,b){
                    var value = feautres[b]
                    if (value.type=="line"){
                        length = Math.sqrt((Number(value.xe)-Number(value.xs))*(Number(value.xe)-Number(value.xs)) + (Number(value.ye)-Number(value.ys))*(Number(value.ye)-Number(value.ys)))
                        a += length
                    } else if (value.type=="arc") {
                        a += arcLength(Number(value.xs),Number(value.ys),Number(value.xe),Number(value.ye),Number(value.xc),Number(value.yc))
                    }
                    return a 
                }, 0) 
                res += milling
                GEN.deleteLayer({job:that.job,layer:[tmp]})
            }
        })
        res = res/1000
        return res.toFixed(4)
    }
    T_m_p.prototype.analysis_milling_length_array = function(){        // todo 
        if(!this.array_step){return "_error"}
        GEN.openStep({job:this.job,name:this.array_step})
        var tmp = "length_tmp"
        GEN.COM("profile_to_rout,layer="+tmp+",width=1")
        var feautres = GEN.getFeatures({job:this.job,step:this.array_step,layer:tmp,units:"mm"})
        // 根据feautres 算出周长
        var milling = Object.keys(feautres).reduce(function(a,b){
            var value = feautres[b]
            if (value.type=="line"){
                length = Math.sqrt((value.xe-value.xs)*(value.xe-value.xs) + (value.ye-value.ys)*(value.ye-value.ys))
                a += length
            } else if (value.type=="arc") {
                a += arcLength(Number(value.xs),Number(value.ys),Number(value.xe),Number(value.ye),Number(value.xc),Number(value.yc))
            }
            return a 
        }, 0)
        GEN.closeStep()
        milling = milling/1000
        return milling.toFixed(4)
    }
    T_m_p.prototype.analysis_board_has_attr = function(props){        //  board层中检查存在01005属性物件时
        if(db_customer == "2171"){
            var step = this.pcs_step
            var job_path = GEN.getJobPath({job:job});
            // fs.writeFile("/home/toplinker/samba/scott_test/tmp", job_path)
            var step_paths = fs.listDir(job_path + "/steps", 1).filter(function(item){return item.isDir}).map(function(item){return item.name})
            var ret = "no";
            var hasComp = false;
            if(step_paths.indexOf(step) >= 0) {
                var layers = fs.listDir(job_path + "/steps/" + step +"/layers", 1).filter(function(item){return item.isDir})
                layers = layers.filter(function(v){return v.name === "comp_+_bot" || v.name === "comp_+_top"})
                if(layers.length > 0) {hasComp = true}
                layers.forEach(function(item){
                    autoUn( item.path + "/components.Z", item.path + "/components")
                    if(fs.fileExists(item.path + "/components/components")){
                        var datas = fs.readFile(item.path + "/components/components")
                        datas.match(/PRP ALT_SYMBOLS '\([^)]*\)'/ig).forEach(function(v) {
                            if(/01005/.test(v)) {
                                ret = "yes"
                            }
                        })
                    }
                })
            }
            if(ret == "no" && hasComp == false) {
                var mOuters = this.matrixInfo.mOuters   // signalL   solderL(null)
                var job_tmp = this.job
                GEN.openStep({job:job_tmp,name:step})
                GEN.clearLayers()
                mOuters.forEach(function(outer_layer){
                    if(outer_layer.solderL){
                        GEN.workLayer({name:outer_layer.solderL,display_number:2,clear_before:'yes'})
                        GEN.selectByFilter({include_syms:"rect213x250xr50\;rect250x213xr50"})
                        if(GEN.getSelectCount() > 0) {
                            GEN.selCopyOther({dest:'layer_name',target_layer:outer_layer.solderL+'temp',invert:'no',dx:0,dy:0,size:0})
                            GEN.workLayer({name:outer_layer.signalL,display_number:2,clear_before:'yes'})
                            GEN.COM("filter_reset,filter_name=popup")
                            GEN.COM("filter_set,filter_name=popup,update_popup=no,feat_types=pad")
                            GEN.selRefFeat({layers:outer_layer.solderL+'temp',use:"filter",mode:"touch"})
                            if(GEN.getSelectCount() > 0) {
                                GEN.selClearFeature()
                                ret = "yes"
                            }
                            GEN.COM("filter_reset,filter_name=popup");
                            GEN.deleteLayer({job:job_tmp,layer:[outer_layer.solderL+'temp']})
                        }
                    }
                })
                GEN.closeStep()
            }
            return ret
        } else {
            return "no"
        }
    }
    T_m_p.prototype.analysis_vc_src_EDGE_PLATING = function(props){        
        var res = "no"
        var rout = this.matrixInfo.matrixVal.filter(function(v){return v.layer_type == "rout"})   // 找rout层
        if(rout.length == 0){
            return "error:cant find rout layer"
        }
        GEN.openStep({job:this.job, name:this.pcs_step})
        GEN.affectedLayer({affected:'no',mode:'all'})
        try {
            this.matrixInfo.mSignals.forEach(function(v){   // 用线路层逐层与rout层touch 找得到说明存在
                GEN.workLayer({name:v.name,display_number:2,clear_before:'yes'})
                GEN.selClearFeature()
                GEN.selRefFeat({layers:rout[0].name,mode:"touch",use:"filter"})
                if( GEN.getSelectCount()>0){
                    throw "yes"
                }
                GEN.clearLayers()
            })
        } catch (msg) {
            res = msg
            GEN.affectedLayer({affected:'no',mode:'all'})
            GEN.selClearFeature()
        }
        GEN.closeStep()
        return res
    }
    T_m_p.prototype.analysis_edge_plating_length = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_gold_finger = function(){  //
        return "no"
    }
    T_m_p.prototype.analysis_glod_finger_area = function(){        // 
        // if(ALL.gold_fingers.length > 0){
        //     GEN.openStep({job:this.job, name:this.pcs_step})
        //     var gold_finger_area = {}
        //     var res = 0;
        //     ALL.gold_fingers.forEach(function(item){
        //         var tmp = GEN.copperArea({layer1:item.name,resolution_value:1}).percent
        //         res += Number(tmp)
        //         // var tmp = GEN.copperArea({layer1:item.name}).percent + "%"
        //         // var key = item.type == "top" ?  "sf_area_gold_area_front" : "sf_area_gold_area_back"
        //         // gold_finger_area[key] = tmp
        //     })
        //     GEN.closeStep()
        //     if(res){
        //         return String(res.toFixed(4)) + "%"
        //     }

        // }
        return "_todo"
    }
    T_m_p.prototype.analysis_ATS_sm_side = function(){        //  检查防焊层所在面次 
        var solder_paste_layers = this.matrixInfo.matrixVal.filter(function(v){return v.layer_type == "solder_mask"})
        var solder_paste_info= solder_paste_layers.map(function(v){return v.side}).reduce(function(a,b){
            if(a.indexOf(b)<0){ a.push(b) }
            return a
        },[])
        var res = "NONE"
        if(solder_paste_info.length == 0){
            res = "NONE"
        } else if (solder_paste_info.length == 1) {
            res = /top/ig.test(solder_paste_info[0]) ?  "FRONT1" : "BACK1"
        } else {
            res = "FRONT1;BACK1"
        }
        return res
    }
    T_m_p.prototype.analysis_vc_id_print_side = function(){        // 检查文字层所在面次 
        var solder_mask_layers = this.matrixInfo.matrixVal.filter(function(v){return v.layer_type == "silk_screen"})
        var solder_mask_info= solder_mask_layers.map(function(v){return v.side}).reduce(function(a,b){
            if(a.indexOf(b)<0){ a.push(b) }
            return a
        },[])
        var res = "NA"
        if(solder_mask_info.length == 0){
            res = "NA"
        } else if (solder_mask_info.length == 1) {
            res = /top/ig.test(solder_mask_info[0]) ?  "FRONT" : "BACK"
        } else {
            res = "FRONT / BACK"
        }
        return res
    }
    T_m_p.prototype.analysis_min_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_pth_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_npth_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_min_slot_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_slot_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_slot_drl_length = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_spec_slot_drl_size = function(){        // todo 
        return "_todo"
    }
    T_m_p.prototype.analysis_max_spec_slot_drl_length = function(){        // todo 
        return "_todo"
    }
    return new T_m_p({job:job, jobId:jobId, pcs_step:pcs_step, array_step:array_step,matrix:matrix})
}
function smdAnalysis(props){
    var job = props.job
    var steplist = props.steplist
    var layers = props.layers
    var res = {}
    steplist.forEach(function(step){
        GEN.openStep({job:job,name:step})
        layers.forEach(function(layer){
            // 计算开窗 数量 
            if(layer.solderL){
                GEN.workLayer({name:layer.solderL,display_number:2,clear_before:'yes'})
                if (!res.hasOwnProperty(layer.solderL)){
                    res[layer.solderL] = {}
                }
                if(GEN.getProfile({job:job, step:step}).match(/\n/ig).length > 1){
                    var tmp_solder = layer.solderL + "_tmp";
                    if(GEN.isLayerExists({job:job,layer:tmp_solder})){GEN.deleteLayer({job:job,layer:[tmp_solder]})}
                    // 拷贝到辅助层
                    GEN.selCopyOther({dest:'layer_name',target_layer:tmp_solder,invert:'no',dx:0,dy:0,size:0})
                    // 转铜皮
                    GEN.workLayer({name:tmp_solder,display_number:2,clear_before:'yes'})
                    GEN.selContourize()
                    GEN.selectByFilter({profile:'in'})
                } else {
                    GEN.selAllFeat()
                } 
                res[layer.solderL].sm_opening_count = GEN.getSelectCount()
                GEN.selClearFeature()
            }
            // 分析最小smd宽高
            GEN.workLayer({name:layer.signalL,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            GEN.selectByFilter({attribute:props.attr})
            var selCount = GEN.getSelectCount()
            if(selCount>0){
                var tmp_layer = layer.signalL + "_tmp"
                selCopyLayer({job:job,layer:tmp_layer})
                var symbols = GEN.getLayerSymsHist({job:job,layer:tmp_layer,step:step})
                GEN.workLayer({name:tmp_layer,display_number:2,clear_before:'yes'})
                var symbolInfo = symbolAnalysis({symbols:symbols,filterReg:/^(rect|oval)/})
                if (!res.hasOwnProperty(layer.signalL)){
                    res[layer.signalL] = {}
                }
                if(symbolInfo["min_width"]){ res[layer.signalL]["min_smd_width"] = symbolInfo["min_width"] }
                if(symbolInfo["min_length"]){  res[layer.signalL]["min_smd_length"] = symbolInfo["min_length"] }
                // 分析最小smd间距
                // 创建一个checklist并且分析
                var min_smd_pitch = smdPitch({job:job,step:step,layer:tmp_layer})  // min_smd_pitch
                // console.log('=================  =======min_smd_pitch:' + min_smd_pitch);
                
                res[layer.signalL]["min_smd_c2c"] = min_smd_pitch
                // 分析开窗宽高
                if(layer.solderL){
                    GEN.workLayer({name:layer.solderL,display_number:2,clear_before:'yes'})
                    GEN.selClearFeature()
                    GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'include'})
                    GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'cover'})
                    var solderL_tmp = layer.solderL + "_tmp"
                    selCopyLayer({job:job,layer:solderL_tmp})
                    GEN.selClearFeature()
                    GEN.workLayer({name:solderL_tmp,display_number:2,clear_before:"yes"})
                    var symbols_solder = GEN.getLayerSymsHist({job:job,layer:solderL_tmp,step:step})
                    var symbolInfo_solder = symbolAnalysis({symbols:symbols_solder,filterReg:/^(rect|oval)/})
                    if(symbolInfo_solder["min_width"]){res[layer.signalL].min_smd_sm_width = symbolInfo_solder["min_width"]}
                    if(symbolInfo_solder["min_length"]){res[layer.signalL].min_smd_sm_length = symbolInfo_solder["min_length"]}
                    GEN.deleteLayer({job:job,layer:solderL_tmp})
                }
                GEN.deleteLayer({job:job,layer:tmp_layer})
            }
        })
        GEN.closeStep()
    })
    return res
}

function symbolAnalysis(props){
    var symbols = Object.keys(props.symbols).reduce(function(a,b){
        if(props.filterReg.test(b)){
            var res = /^(?:rect|oval)(\d+\.\d+|\d+)x(\d+\.\d+|\d+)/ig.exec(b).slice(1)
            var num1 = Number(res[0]), num2 = Number(res[1])
            if(num1 > num2){
                var tmp = num2
                num2 = num1
                num1 = tmp
            }
            if(a.width == "N/A"){
                a.width = num1
                a.length = num2
                return a
            }
            if(num2 < a.length){
                a.width = num1
                a.length = num2
            }else if(num2 == a.length && a.width > num1){
                a.width = num1
                a.length = num2
            }
        } else if (/^r\d/.test(b)){
            var num1 = b.slice(1) - 0;
            var num2 = num1;
            if(num1 > num2){
                var tmp = num2
                num2 = num1
                num1 = tmp
            }
            if(a.width == "N/A"){
                a.width = num1
                a.length = num2
                return a
            }
            if(num2 < a.length){
                a.width = num1
                a.length = num2
            }else if(num2 == a.length && a.width > num1){
                a.width = num1
                a.length = num2
            }
        }
        return a
    },{width:"N/A",length:"N/A"})
    return {
        min_width:symbols.width,
        min_length:symbols.length
    }
}
function smdPitch(props){
    var job = props.job
    var step = props.step
    var layer = props.layer
    var ck = "tmp_chk"
    if(GEN.isChklistExists({job:job,step:step,chklist:ck})){
        GEN.COM("chklist_delete", { chklist: ck })
    }
    // 创建并运行
    var tmpItem = {
        name: "smdPitch",
        nact: 1,
        action: "valor_analysis_signal",
        params: {   
            pp_layer: ".affected",
            pp_spacing: 20,
            pp_selected: "All",
            pp_r2c: 10,
            pp_d2c: 15,
            pp_sliver: 8,
            pp_min_pad_overlap: 5,
            pp_check_missing_pads_for_drills: "Yes",
            pp_use_compensated_rout: "Skeleton",
            pp_sm_spacing: "Yes",
            pp_tests: "Spacing\\;SMD",
            pp_check_missing_pads_for_drills:"Yes"
        }
    }
    if(PAR.erf && PAR.erf != ""){
        tmpItem.erf = PAR.erf
    }
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [tmpItem]
    })
    GEN.chklistShow({ chklist: ck })
    GEN.affectedLayer({ affected: "yes", layer: layer, clear_before: "yes" })
    GEN.chklistRun({
        chklist: ck,
        nact: 1,
        area: 'profile'
    })
    
    var tmp_layer1 = "mk_1_"+layer+"_pitch"
    var tmp_layer2 = "ms_1_"+layer+"_pitch"

    if(GEN.isLayerExists({job:job,layer:tmp_layer1})){
        GEN.deleteLayer({job:job,layer:tmp_layer1})
    }
    if(GEN.isLayerExists({job:job,layer:tmp_layer2})){
        GEN.deleteLayer({job:job,layer:tmp_layer2})
    }
    GEN.COM("chklist_create_lyrs,chklist="+ck+",severity=3,suffix=pitch");
    GEN.workLayer({name:tmp_layer2,display_number:1,clear_before:'yes'});

    GEN.selectByFilter({attribute:[{attribute:".string",text:"smd_pitch"}]})
    var tmp_layer = tmp_layer2 + "_tmp"
    selCopyLayer({job:job,layer:tmp_layer})
    GEN.workLayer({name:tmp_layer,display_number:1,clear_before:'yes'});
    var features = GEN.getFeatures({job:job,step:step,layer:tmp_layer})
    // 获取最小smd间距
    var tmp = features.map(function(v){
        var num = Math.sqrt((v.xe-v.xs)*(v.xe-v.xs) + (v.ye-v.ys)*(v.ye-v.ys))*1000
        return num.toFixed(4)
    })
    var res = tmp.sort(function(a,b){return a-b})[0]
    GEN.deleteLayer({job:job,layer:tmp_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer1})
    GEN.deleteLayer({job:job,layer:tmp_layer2})
    return res
}
function createChklistAndRun(par) {
    var checkLayers = par.layers
    var items = par.items
    GEN.createChklist({ // 创建checklist
        chklist: oChecklistName,
        items: items
    })
    GEN.chklistShow({
        chklist: oChecklistName
    })
    GEN.affectedLayer({
        affected: "yes",
        layer: checkLayers,
        clear_before: "yes"
    })
    items.forEach(function(v){
        GEN.chklistRun({
            chklist: oChecklistName,
            nact: v.nact,
            area: 'profile'
        })
    })
}
function analysisChkAttr(par) {
    var layers = par.layers;
    var hash = {};
    var info = par.info;
    var job = par.job;
    var oChecklistName = par.oChecklistName
    layers.forEach(function (v) {
        var nact = /top|bot/.test(v) ? 1 : 2;
        hash[v] = {}
        Object.keys(info).forEach(function (key) {
            var val = info[key]
            hash[v][key] = val.reduce(function (a, type) {
                var tmp = GEN.getCheckAttr({
                    job: job,
                    step: par.step,
                    checklist: oChecklistName,
                    nact: nact,
                    attr: v + "_min_" + type
                })
                if (a === "N/A" && /^\d+\.?\d*$/.test(tmp)) {
                    tmp = Number(tmp)
                    a = tmp.toFixed(2)
                }
                if (/^\d+\.?\d*$/.test(a) && /^\d+\.?\d*$/.test(tmp) && Number(tmp) < Number(a)) {
                    tmp = Number(tmp)
                    a = tmp.toFixed(2)
                }
                return a
            }, "N/A")
            if(hash[v][key]=="N/A"){
                // min_ar_laser
                if(/^min_ar/ig.test(key)){
                    hash[v][key] = 150
                } else {
                    hash[v][key] = 999
                }
            }
        })
    })
    
    return hash
}
function analysisDrill(par, step){

     // [{  
        //     "layer": " ftdrill2-3l",
        //     "symbol": "r7.874",
        //     "start": "l2",
        //     "end": "l3"
        // }, {
        //     "layer": "ftdrill",
        //     "symbol": "r7.874",
        //     "start": "top",
        //     "end": "bottom"
        // }]
    var job = Job.toLowerCase();
    GEN.affectedLayer({affected:'no',mode:'all'})
    var res = par.map(function(drill){
        GEN.workLayer({name:drill.layer,display_number:2,clear_before:'yes'})
        GEN.selClearFeature()
        GEN.selectByFilter({feat_types:'pad'})
		if(GEN.getSelectCount()>0){
            console.log("==========================>jinru 111111111")
			// 拷贝到_tmp
			var tmplayer = drill.layer + "_tmp"
			if(GEN.isLayerExists({job:job,layer:tmplayer})){GEN.deleteLayer({job:job,step:step,layer:tmplayer})}
			GEN.selCopyOther({dest:"layer_name",target_layer:tmplayer})
			GEN.affectedLayer({affected:"no",mode:"all"})
            GEN.selClearFeature();
			[drill.start,drill.end].forEach(function(item, i){
                if(!/^comp_\+_/.test(item)){
                    GEN.workLayer({name:item,display_number:1,clear_before:"yes"})
                    GEN.selClearFeature();
                    GEN.COM("filter_set,filter_name=popup,update_popup=no,feat_types=pad");
                    // GEN.COM("sel_ref_feat",{layers:tmplayer,use:"filter",mode:"touch",pads_as:"shape",f_types:"pad",polarity:"positive\;negative",include_syms:drill.symbol})
                    GEN.COM("sel_ref_feat,layers="+tmplayer+",use=filter,mode=touch,pads_as=shape,f_types=pad,polarity=positive\;negative,include_syms=,exclude_syms=") 
                    
                    // GEN.selRefFeat({layers:tmplayer, use:"filter",mode:"touch"})
                    GEN.COM("filter_reset,filter_name=popup")
                    if(GEN.getSelectCount()>0){
                        var res = 999;
                        if(GEN.isLayerExists({job:job,layer:"temp"})){GEN.deleteLayer({job:job,layer:["temp"]})}
                        if(GEN.isLayerExists({job:job,layer:"temp+++"})){GEN.deleteLayer({job:job,layer:["temp+++"]})}
                        GEN.selCopyOther({dest:'layer_name',target_layer:'temp',invert:'no',dx:0,dy:0,size:0})
                        GEN.workLayer({name:"temp",display_number:1,clear_before:"yes"})
                        // GEN.COM("sel_ref_feat,layers="+item+",use=filter,mode=touch,pads_as=shape,f_types=surface,polarity=positive\;negative,include_syms=,exclude_syms=")
                        // if(GEN.getSelectCount()>0){GEN.selDelete()}
                        GEN.COM("chklist_single,action=valor_dfm_nfpr,show=yes")
                        GEN.COM("chklist_cupd,chklist=valor_dfm_nfpr,nact=1,params=((pp_layer=temp)(pp_delete=Duplicate)(pp_work=Features)(pp_drill=PTH\;NPTH\;Via)(pp_non_drilled=Yes)(pp_in_selected=All)(pp_remove_mark=Remove)),mode=regular")
                        GEN.COM("chklist_run,chklist=valor_dfm_nfpr,nact=1,area=profile")
                        
                        var layer = "temp";
                        var drl_layer = layer + "_drl"
                        if(GEN.isLayerExists({job:job, layer:drl_layer})){GEN.deleteLayer({job:job, layer:drl_layer})}
                        GEN.createLayer({job:job,layer:drl_layer,type:'drill'})
                        GEN.selCopyOther({dest:'layer_name',target_layer:drl_layer,invert:'no',dx:0,dy:0,size:0})
                        if(GEN.isChklistExists({job:job,step:step,chklist:'tmp_chk'})){
                            GEN.COM("chklist_delete,chklist=tmp_chk")
                        }
                        GEN.COM("chklist_create,chklist=tmp_chk")
                        GEN.COM("chklist_show,chklist=tmp_chk")
                        GEN.COM("chklist_single,action=valor_analysis_drill,show=yes")
                        GEN.COM("chklist_pclear")
                        GEN.COM("chklist_cupd,chklist=valor_analysis_drill,nact=1,params=((pp_drill_layer="+drl_layer+
                        ")(pp_rout_distance=200)(pp_tests=Hole Separation)(pp_extra_hole_type=Pth\;Via)(pp_use_compensated_rout=Skeleton)),mode=regular")
                        GEN.COM("chklist_pcopy,chklist=valor_analysis_drill,nact=1")
                        GEN.COM("chklist_ppaste,chklist=tmp_chk,row=0")
                        GEN.COM("chklist_run,chklist=tmp_chk,nact=1,area=profile")
                        GEN.COM("chklist_close,chklist=valor_analysis_drill,mode=hide")
                        GEN.COM("chklist_close,chklist=tmp_chk,mode=hide")
                        GEN.deleteLayer({job:job, layer:drl_layer})
                        // GEN.deleteLayer({job:job, layer:[layer, layer+"+++"]})
                        var meas = GEN.getCheckMeas({job:job,step:step,chklist:'tmp_chk',nact:1})
                        
                        meas = meas.filter(function(v){return /^touch/.test(v)})
                        if(meas.length){
                            meas.forEach(function(v){
                                var tmparr = v.split(" ")
                                GEN.COM("sel_net_feat,operation=select,x="+tmparr[7]+",y="+tmparr[8]+",use_ffilter=no")
                                if(GEN.getSelectCount() > 0){GEN.selDelete()}
                            })
                        }
                        var pads = GEN.getFeatures({job:job,step:step,layer:"temp"}) || [];
                        pads = pads.filter(function(v){v.size=v.symbol.slice(1)-0 ;return /^r\d/.test(v.symbol)})
                        
                        if (pads && pads.length) {
                            var tmphash = {};
                            pads.forEach(function(item2){
                                var key = item2.x+"x"+item2.y
                                if(!tmphash[key]){tmphash[key] = item2}else {
                                    tmphash[key] = tmphash[key].size > item2.size? tmphash[key]:item2
                                }
                            })
                            for (var key in tmphash) {
                                var size = tmphash[key].size;
                                res = size < res? size: res;
                            }
                            if(res == 999) {res = ""}
                        }
                        if ( i == 0) {
                            drill.drl_pad_top = res
                        } else if (i==1){
                            drill.drl_pad_bot = res
                        }
                    }
                }
			})
			GEN.deleteLayer({job:job,step:step,layer:tmplayer})
		}
        return drill
    })
    return res
}
function bgaAnalysis(props){
    var job = props.job
    var steplist = props.steplist
    var layers = props.layers;
    var res = {}
    steplist.forEach(function(step){
        GEN.openStep({job:job,name:step})
        layers.forEach(function(layer){
            // 找出bga 拷贝到辅助层
            GEN.workLayer({name:layer.signalL,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            GEN.selectByFilter({attribute:props.attr})
            var selCount = GEN.getSelectCount()
            if(selCount>0){
                var tmp_layer = layer.signalL + "_tmp"
                selCopyLayer({job:job,layer:tmp_layer})
                var symbols = GEN.getLayerSymsHist({job:job,layer:tmp_layer,step:step})
                GEN.workLayer({name:tmp_layer,display_number:2,clear_before:'yes'})
                // 分析数据
                var syblist = _.values(symbols).reduce(function(a,b){
                    if (b.pad > 0 && b.line) {
                        a.push({symbol:b.symbol, size:Number(b.size)})
                    }
                    return a
                },[])
                // 找到最小的bga分析
                var min_symbols = syblist.sort(function(a,b){return a.size-b.size})[0]
                var min_symbols_info = min_symbols_anal({symbols:min_symbols, job:job, step:step, layer:layer})
                if (!res.hasOwnProperty(layer.signalL)){
                    res[layer.signalL] = {}
                }
                res[layer.signalL].min1_bga_dia = myFixed(min_symbols_info["min_bga_size"],2) 
                res[layer.signalL].min1_bga_sm_dia = myFixed(min_symbols_info["min_bga_openging_size"],2) 
                res[layer.signalL].min1_bga_grid = myFixed(min_symbols_info["min_bga_pitch"],2) 
                // 分析所有BGA
                var all_bga_min_pitch_info = all_bga_min_pitch_anal({job:job, step:step, layer:tmp_layer,solderLayer:layer.solderL})
                res[layer.signalL].min2_bga_grid = myFixed(all_bga_min_pitch_info["bga_min_pitch"],2) 
                res[layer.signalL].min2_bga_dia = myFixed(all_bga_min_pitch_info["bga_min_pitch_pad_size"],2) 
                res[layer.signalL].min2_bga_sm_dia = myFixed(all_bga_min_pitch_info["bga_min_pitch_openging_size"],2) 
                GEN.deleteLayer({job:job,layer:tmp_layer})
            } 
        })
        GEN.closeStep()
    })
    return res
}
function min_symbols_anal(props){  // 分析最 symbols
    var job = props.job
    var step = props.step
    var layer = props.layer
    var symbols = props.symbols
    var res = {}
    GEN.selClearFeature()
    GEN.selectByFilter({include_syms:symbols.symbol})
    var tmp_layer = layer.signalL + "_tmp_1"
    selCopyLayer({job:job,layer:tmp_layer})
    // 留下pitch最小的bga 如果pitch都一样大 就跳过
    var min_pitch = bgaPitch({job:job,step:step,layer:tmp_layer})
    if(min_pitch){
        res.min_bga_pitch = (min_pitch-0) + (symbols.size-0)
    }
    // 分析数据
    GEN.clearLayers()
    // 尺寸
    res.min_bga_size = symbols.size
    // 开窗大小
    if(layer.solderL){
        GEN.workLayer({name:layer.solderL,display_number:2,clear_before:'yes'})
        GEN.selClearFeature()
        GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'include'})
        GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'cover'})
        var solderL_tmp = layer.solderL + "_tmp"
        selCopyLayer({job:job,layer:solderL_tmp})
        GEN.selClearFeature()
        GEN.workLayer({name:solderL_tmp,display_number:2,clear_before:"yes"})
        GEN.selContourize()
        GEN.COM("sel_cont2pad,match_tol=0,restriction=,min_size=5,max_size=100,suffix=+++")
        GEN.COM("cur_atr_reset")
        GEN.COM("cur_atr_set,attribute=.smd")
        GEN.COM("sel_change_atr,mode=add")
        var ck = "tmp_chk"
        if(GEN.isChklistExists({job:job,step:step,chklist:ck})){ GEN.COM("chklist_delete", { chklist: ck })}
        var tmpItem = {
            name: "opingsize", nact: 1, action: "valor_analysis_signal", erf: PAR.erf,
            params: {   
                pp_layer: ".affected",
                pp_spacing: 20,
                pp_selected: "All",
                pp_r2c: 10,
                pp_d2c: 15,
                pp_sliver: 8,
                pp_min_pad_overlap: 5,
                pp_check_missing_pads_for_drills: "Yes",
                pp_use_compensated_rout: "Skeleton",
                pp_sm_spacing: "Yes",
                pp_tests: "SMD",
                pp_check_missing_pads_for_drills:"Yes"
            }
        }
        if(PAR.erf){ tmpItem.erf = PAR.erf }
        GEN.createChklist({  chklist: ck, items: [tmpItem] })
        GEN.chklistShow({ chklist: ck })
        GEN.chklistRun({ chklist: ck, nact: 1, area: 'profile' })
        var min_smd = ["min_smd_pad"]
        min_smd = min_smd.map(function(v){
            return GEN.getCheckAttr({job:job,step:step,checklist:ck,attr:v,nact:1})
        })
        min_smd = min_smd.filter(function(v){return v!="N/A"})
        if(min_smd.length == 0){
            res.min_bga_openging_size = "todo"  // todo
        } else {
            res.min_bga_openging_size = min_smd.sort(function(a,b){return a-b})[0]
        }
        GEN.deleteLayer({job:job,layer:[solderL_tmp,solderL_tmp+"+++"]})
    }
    GEN.deleteLayer({job:job,layer:tmp_layer})
    return res
}
function all_bga_min_pitch_anal(props){  // 分析所有bga中间距最小的
    var job = props.job
    var step = props.step
    var layer = props.layer
    var solder_layer = props.solderLayer
    var res = {}
    var ck = "tmp_chk"
    if(GEN.isChklistExists({job:job,step:step,chklist:ck})){
        GEN.COM("chklist_delete", { chklist: ck })
    }
    // 创建并运行
    var tmpItem = {
        name: "bgaPitch",
        nact: 1,
        action: "valor_analysis_signal",
        erf: PAR.erf,
        params: {   
            pp_layer: ".affected",
            pp_spacing: 20,
            pp_selected: "All",
            pp_r2c: 10,
            pp_d2c: 15,
            pp_sliver: 8,
            pp_min_pad_overlap: 5,
            pp_check_missing_pads_for_drills: "Yes",
            pp_use_compensated_rout: "Skeleton",
            pp_sm_spacing: "Yes",
            pp_tests: "Spacing"
        }
    }
    if(PAR.erf){
        tmpItem.erf = PAR.erf
    }
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [tmpItem]
    })
    GEN.chklistShow({ chklist: ck })
    GEN.affectedLayer({ affected: "yes", layer: layer, clear_before: "yes" })
    GEN.chklistRun({
        chklist: ck,
        nact: 1,
        area: 'profile'
    })
    
    var tmp_layer1 = "mk_1_"+layer+"_pitch"
    var tmp_layer2 = "ms_1_"+layer+"_pitch"

    if(GEN.isLayerExists({job:job,layer:tmp_layer1})){
        GEN.deleteLayer({job:job,layer:tmp_layer1})
    }
    if(GEN.isLayerExists({job:job,layer:tmp_layer2})){
        GEN.deleteLayer({job:job,layer:tmp_layer2})
    }
    GEN.COM("chklist_create_lyrs,chklist="+ck+",severity=3,suffix=pitch");
    GEN.workLayer({name:tmp_layer2,display_number:1,clear_before:'yes'});
    GEN.selectByFilter({attribute:[{attribute:".string",text:"p2p"}]})
    var tmp_layer = tmp_layer2 + "_tmp"
    selCopyLayer({job:job,layer:tmp_layer})
    GEN.workLayer({name:tmp_layer,display_number:1,clear_before:'yes'});
    // 获取最小bga间距
    var features = GEN.getFeatures({job:job,step:step,layer:tmp_layer})
    if(features && features.length){
        var tmp = features.map(function(v){
            var num = Math.sqrt((v.xe-v.xs)*(v.xe-v.xs) + (v.ye-v.ys)*(v.ye-v.ys))*1000
            return {num:num.toFixed(4),symbol:v.symbol}
        })
        var bga_min_pitch = tmp.sort(function(a,b){return a-b})[0]
        res.bga_min_pitch = bga_min_pitch.num
        // pitch 中选最小的
        GEN.selClearFeature()
        GEN.COM("filter_set,filter_name=popup,update_popup=no,profile=in")
        GEN.COM("filter_set,filter_name=popup,update_popup=yes,slot=line,slot_by=length,min_len=0,max_len="+(bga_min_pitch.num/1000)+0.0002)
        GEN.COM("filter_area_strt") 
        GEN.COM("filter_area_end,layer=,filter_name=popup,operation=select,area_type=none,inside_area=no,intersect_area=no")
        GEN.COM("filter_reset,filter_name=popup")
    }
    // 所有BGA中间距最小的PAD大小
    var min_pitch_layer = tmp_layer + "_min"
    selCopyLayer({job:job,layer:min_pitch_layer})
    GEN.workLayer({name:layer,display_number:1,clear_before:'yes'});
    GEN.selClearFeature()
    GEN.selRefFeat({layers:min_pitch_layer,use:'filter',mode:'touch'})
    var min_pad_layer = layer + "_min_pad"
    selCopyLayer({job:job,layer:min_pad_layer})
    GEN.workLayer({name:min_pad_layer,display_number:1,clear_before:'yes'});
    var min_pad_symbols = GEN.getLayerSymsHist({job:job,layer:min_pad_layer,step:step})
    var min_pad_symbol = _.values(min_pad_symbols).sort(function(a,b){return Number(a.size)-Number(b.size)})[0]
    // 删除非最小的
    GEN.selectByFilter({include_syms:min_pad_symbol.symbol})
    GEN.selReverse()
    if(GEN.getSelectCount()>0){GEN.selDelete()}
    res.bga_min_pitch_pad_size = min_pad_symbol.size
    res.bga_min_pitch = (res.bga_min_pitch || 20) - 0 + (min_pad_symbol.size-0)
    if(solder_layer){
        // 最小pitch开窗大小
        GEN.workLayer({name:solder_layer,display_number:1,clear_before:'yes'});
        GEN.selClearFeature()
        GEN.selRefFeat({layers:min_pitch_layer,use:'filter',mode:'cover'})
        GEN.selRefFeat({layers:min_pitch_layer,use:'filter',mode:'touch'})
        var solder_layer_tmp = solder_layer + "_tmp"
        selCopyLayer({job:job,layer:solder_layer_tmp})
        GEN.selClearFeature()
        GEN.workLayer({name:solder_layer_tmp,display_number:2,clear_before:"yes"})
        GEN.selContourize()
        GEN.COM("sel_cont2pad,match_tol=0,restriction=,min_size=5,max_size=100,suffix=+++")
        GEN.COM("cur_atr_reset")
        GEN.COM("cur_atr_set,attribute=.smd")
        GEN.COM("sel_change_atr,mode=add")
        var ck = "tmp_chk"
        if(GEN.isChklistExists({job:job,step:step,chklist:ck})){ GEN.COM("chklist_delete", { chklist: ck })}
        var tmpItem = {
            name: "opingsize", nact: 1, action: "valor_analysis_signal", erf: PAR.erf,
            params: {   
                pp_layer: ".affected",
                pp_spacing: 20,
                pp_selected: "All",
                pp_r2c: 10,
                pp_d2c: 15,
                pp_sliver: 8,
                pp_min_pad_overlap: 5,
                pp_check_missing_pads_for_drills: "Yes",
                pp_use_compensated_rout: "Skeleton",
                pp_sm_spacing: "Yes",
                pp_tests: "SMD",
                pp_check_missing_pads_for_drills:"Yes"
            }
        }
        if(PAR.erf){ tmpItem.erf = PAR.erf }
        GEN.createChklist({  chklist: ck, items: [tmpItem] })
        GEN.chklistShow({ chklist: ck })
        GEN.chklistRun({ chklist: ck, nact: 1, area: 'profile' })
        var min_smd = ["min_smd_pad"]
        min_smd = min_smd.map(function(v){
            return GEN.getCheckAttr({job:job,step:step,checklist:ck,attr:v,nact:1})
        })
        min_smd = min_smd.filter(function(v){return v!="N/A"})
        if(min_smd.length == 0){
            res.bga_min_pitch_openging_size = "todo"  // todo
        } else {
            res.bga_min_pitch_openging_size = min_smd.sort(function(a,b){return a-b})[0]
        }
        GEN.deleteLayer({job:job,layer:[solder_layer_tmp,solder_layer_tmp+"+++"]})
    }
    GEN.deleteLayer({job:job,layer:min_pad_layer})
    GEN.deleteLayer({job:job,layer:min_pitch_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer1})
    GEN.deleteLayer({job:job,layer:tmp_layer2})
    return res
}
function bgaPitch(props){
    var job = props.job
    var step = props.step
    var layer = props.layer
    var ck = "tmp_chk"
    if(GEN.isChklistExists({job:job,step:step,chklist:ck})){
        GEN.COM("chklist_delete", { chklist: ck })
    }
    // 创建并运行
    var tmpItem = {
        name: "bgaPitch",
        nact: 1,
        action: "valor_analysis_signal",
        params: {   
            pp_layer: ".affected",
            pp_spacing: 20,
            pp_selected: "All",
            pp_r2c: 10,
            pp_d2c: 15,
            pp_sliver: 8,
            pp_min_pad_overlap: 5,
            pp_check_missing_pads_for_drills: "Yes",
            pp_use_compensated_rout: "Skeleton",
            pp_sm_spacing: "Yes",
            pp_tests: "Spacing",
            pp_check_missing_pads_for_drills:"Yes"
        }
    }
    if(PAR.erf) {
        tmpItem.erf = PAR.erf
    }
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [tmpItem]
    })
    GEN.chklistShow({ chklist: ck })
    GEN.affectedLayer({ affected: "yes", layer: layer, clear_before: "yes" })
    GEN.chklistRun({
        chklist: ck,
        nact: 1,
        area: 'profile'
    })
    
    var tmp_layer1 = "mk_1_"+layer+"_pitch"
    var tmp_layer2 = "ms_1_"+layer+"_pitch"

    if(GEN.isLayerExists({job:job,layer:tmp_layer1})){
        GEN.deleteLayer({job:job,layer:tmp_layer1})
    }
    if(GEN.isLayerExists({job:job,layer:tmp_layer2})){
        GEN.deleteLayer({job:job,layer:tmp_layer2})
    }
    GEN.COM("chklist_create_lyrs,chklist="+ck+",severity=3,suffix=pitch");
    GEN.workLayer({name:tmp_layer2,display_number:1,clear_before:'yes'});
    GEN.selectByFilter({attribute:[{attribute:".string",text:"p2p"}]})
    var tmp_layer = tmp_layer2 + "_tmp"
    selCopyLayer({job:job,layer:tmp_layer})
    GEN.workLayer({name:tmp_layer,display_number:1,clear_before:'yes'});
    var features = GEN.getFeatures({job:job,step:step,layer:tmp_layer})
    var res
    if(features && features.length){
        var tmp = features.map(function(v){
            var num = Math.sqrt((v.xe-v.xs)*(v.xe-v.xs) + (v.ye-v.ys)*(v.ye-v.ys))*1000
            return num.toFixed(4)
        })
        res = tmp.sort(function(a,b){return a-b})[0]
    }
    if (res) {
        // 过滤掉pitch不是最小的线
        GEN.COM("filter_set,filter_name=popup,update_popup=no,profile=in")
        GEN.COM("filter_set,filter_name=popup,update_popup=yes,slot=line,slot_by=length,min_len=0,max_len="+(res/1000)+0.0002)
        GEN.COM("filter_area_strt") 
        GEN.COM("filter_area_end,layer=,filter_name=popup,operation=select,area_type=none,inside_area=no,intersect_area=no")
        GEN.COM("filter_reset,filter_name=popup")
        GEN.selReverse()
        if(GEN.getSelectCount()>0){GEN.selDelete()}
        GEN.workLayer({name:layer,display_number:1,clear_before:'yes'});
        GEN.selRefFeat({layers:tmp_layer,use:'filter',mode:'touch'})
        GEN.selReverse()
        if(GEN.getSelectCount()>0){GEN.selDelete()}
    }
    GEN.deleteLayer({job:job,layer:tmp_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer1})
    GEN.deleteLayer({job:job,layer:tmp_layer2})
    // 矩阵
    return res || 20.1
}
function myFixed(str, num) {
	if(/^[1-9][0-9]*([.][0-9]+)?$/.test(str)){
		return Number(str).toFixed(num)
	} else {
		return str
	}
}
function createOutline(props){
    var job = props.job
    var step = props.step
    var matrix = props.matrix
    var outlines = Object.keys(matrix).filter(function(v){return /^outline$|^rout$/ig.test(v)})
    var drill_layer = Object.keys(matrix).filter(function(v){return matrix[v].layer_type=="drill" && matrix[v].context == "board"})
    var tmp_outline
    if(outlines.length){
        if( outlines.length > 1 && outlines.indexOf("outline")>=0){
            tmp_outline = "outline"
            if(cl(tmp_outline)) {
                return true
            }else{
                return cl("rout")
            }
        } else {
            return cl(outlines[0])
        }
    }else{return false}
    function cl(l){
        GEN.openStep({job:job, name:step})
        GEN.affectedLayer({affected:'no',mode:'all'})
        GEN.workLayer({name:l,display_number:2,clear_before:"yes"})
        GEN.selClearFeature()
        GEN.COM("sel_cut_data,det_tol=1,con_tol=1,rad_tol=0.1,filter_overlaps=no,delete_doubles=no,use_order=yes,ignore_width=yes,ignore_holes=none,start_positive=yes,polarity_of_touching=same")
        var tmp_layer = l+"+++"
        GEN.selRefFeat({layers:drill_layer[0],use:'filter',mode:'touch'})
        if(GEN.getSelectCount() > 0){
            var tmp_outline = l + "_tmp"
            selCopyLayer({job:job,layer:tmp_outline})
            GEN.workLayer({name:l,display_number:2,clear_before:"yes"})
            GEN.selAllFeat()
            GEN.selDelete()
            GEN.workLayer({name:tmp_outline,display_number:2,clear_before:"yes"})
            GEN.COM("sel_surf2outline,width=15")
            GEN.selAllFeat()
            selCopyLayer({job:job,layer:l})
            GEN.workLayer({name:l,display_number:2,clear_before:"yes"})
            GEN.selClearFeature()
            GEN.selCreateProfile()
            GEN.deleteLayer({job:job, layer:tmp_layer})
            GEN.deleteLayer({job:job, layer:tmp_outline})
            return true
        }
        GEN.deleteLayer({job:job, layer:tmp_layer})
        return false
    }
}
function has_steps(props){
	var job = props.job
	var pcs_step = props.pcs_step
	var array_step = props.array_step
	var res = GEN.getSubSteps({job:job, step:array_step})
	return res.indexOf(pcs_step) >= 0
}
function unSelect(){
    GEN.selectByFilter({attribute:[{attribute:'.drill',option:'plated'}],profile:'all',operation:"unselect"})
    GEN.selectByFilter({attribute:[{attribute:'.drill',option:'via'}],profile:'all',operation:"unselect"})
    GEN.selectByFilter({attribute:[{attribute:'.drill',option:'non_plated'}],profile:'all',operation:"unselect"})
}

function mkPath(path, list) {
	if (list.length) {
		var newPath = path + '/' + list.shift()
		if (!fs.dirExists(newPath)) {fs.mkdir(newPath)}
		return mkPath(newPath, list)
	} else {
		return path
	}
}
function saveMeans(props){
	var job = props.job
	var custstep = props.step
	var chklist = props.chklist
	var nact = props.nact
	var jobpath = GEN.getJobPath({job: job})
	var filter = props.filter
	var afterPath = mkPath(jobpath, ["user", "opencam", "steps"])
	var steps;
	if (custstep) {
		steps = [custstep]
	} else {
        steps = GEN.getStepList({job: job})
        steps = steps.filter(function(v){return v!="" || v!= " "})
	}
	if(!steps || steps.length==0){
		return
	}
	steps.forEach(function (step) {
		var chkPath = mkPath(afterPath, [step, "chk"])
		var chks;
		if (chklist) {chks = [chklist]}else {
			chks = GEN.getChecklist({job: job,step: step})
        }
        if(chks && chks.length){
            chks = chks.filter(function(v){return /quotation/.test(v)});
            if(chks && chks.length){
                chks.forEach(function (item, i) {
                    var afterChkPath = mkPath(chkPath, [item])
                    var hdr = "{\n\
        \"title\": \"" + item + "\",\n\
        \"sequence\": " + (i + 1) + "\n\
    }"
                    fs.writeFile(afterChkPath + "/hdr", _.toString(hdr))
                    var nacts = []
                    if (nact) {
                        nacts = [nact]
                    } else {
                        var nactCount = GEN.getChklistActCount({job:job,step:step,chklist:item}) || 0;
                        if(/quotation/.test(item)){
                            nactCount = 5;
                        }
                        for (var i = 0; i < nactCount; i++) {
                            nacts.push(i+1)
                        }
                    }
                    if(nacts.length > 0){
                        var actionPath = mkPath(afterChkPath, ["actions"])
                        nacts.forEach(function(index){
                            var titleFile = GEN.INFO({
                                entity_type: "check",
                                entity_path: job + "/" + step + "/" + item,
                                data_type: "TITLE",
                                options: "action=" + index,
                                parse:'no'
                            })
                            var title = fs.readFile(titleFile) || item
    
                            if(title && title != ""){
                                if(/=/.test(title)){
                                    title = title.split("=")[1].trim()
                                    title = title.substring(1, title.length-1)
                                }
                                var nackPath = mkPath(actionPath, [index+"-"+title]) 
                            fs.writeFile(nackPath + "/hdr", "{\n\
        \"title\": \"" + (index + "-" + title) + "\",\n\
        \"sequence\": " + index + ",\n\
        \"dfm_name\": \"" + title + "\"\n\
    }");
                                var displayFile = GEN.INFO({
                                    entity_type: 'check',
                                    entity_path: job + "/" + step + "/" + item,
                                    data_type: 'MEAS_DISP_ID',
                                    options: "action=" + index,
                                    parse:'no'
                                });
                                fs.writeFile(nackPath + "/disp", fs.readFile(displayFile))
                                var measFile = GEN.INFO({entity_type:'check', entity_path:job+'/'+step+'/'+item, data_type:'MEAS', options:"index+action="+index, parse:'no'});
                                var means;
                                
                                if(filter){  // 处理过滤
                                    var fileArray = []
                                    var file = fs.openFile(measFile,'r');
                                    while(!file.atEnd()) {
                                        fileArray.push(file.readLine())
                                    }
                                    if(Array.isArray(filter)){
                                        var nowFilter = filter.filter(function(item2){
                                            for (var key in item2) {
                                                var value = item2[key]
                                                if (!Array.isArray(value)){
                                                    item2[key] = [value]
                                                }
                                            }
                                            return item2.step.indexOf(step)>=0 && item2.chk.indexOf(item)>=0 && item2.nact.indexOf(index)>=0
                                        })
                                        if (nowFilter.length > 0){
                                            var nowfilter = nowFilter.map(function(v){return v.filter}).reduce(function(a,b){
                                                b.forEach(function(v2){
                                                    if (a.indexOf(v2)<0){
                                                        a.push(v2)
                                                    }
                                                })
                                                return a
                                            },[])
                                            fileArray = fileArray.filter(function(line){
                                                var flag = false
                                                nowfilter.forEach(function(f){
                                                    var reg = new RegExp(f, "ig")
                                                    if (reg.test(line)){
                                                        flag = true
                                                    }
                                                })
                                                return flag
                                            })
                                        }
                                    } else if (typeof filter == "object") {
                                        if (filter[step] && filter[step][item] && filter[step][item][String(index)]) {
                                            var nowFilter = filter[step][item][String(index)]
                                            if (!Array.isArray(nowFilter)){nowFilter = [nowFilter]}
                                            fileArray = fileArray.filter(function(line){
                                                var flag = false
                                                nowFilter.forEach(function(f){
                                                    var reg = new RegExp(f, "ig")
                                                    if (reg.test(line)){
                                                        flag = true
                                                    }
                                                })
                                                return flag
                                            })
                                        }
                                    }
                                    means = fileArray.join("\n")
                                } else {
                                    means = fs.readFile(measFile)
                                }
                                fs.writeFile(nackPath + "/meas", means)
                            }
                        })
                    }
                })
            }
        }
	})
}
// 算弧长
function arcLength(x1,y1,x2,y2,x3,y3){
	var cos0 =  +((x3-x1)*(x2-x3) + (y3-y1)*(y2-y3)) / (Math.pow((Math.pow(x2-x3,2) + Math.pow(y2-y3,2)),0.5) * Math.pow((Math.pow(x3-x1,2) + Math.pow(y3-y1,2)),0.5))
    var tmp = Math.acos(cos0)
	var sin0 = Math.sin(tmp)
	var length = +Math.sqrt(Math.pow(x2-x1,2) + Math.pow(y2-y1,2)) / sin0 * tmp
	return length
}
function script_info(props){  // result_severity  progress
	if (mode === "aimdfm") {
		$.QDfm.updateRow({
			table: "pdm_aimdfm_task",
			data: props,
			where: { id: $.task_id }
		});
	}
}
function getMinSym(sym) {
	var min_size;
	for (var key in sym) {
		var info = sym[key]
		if(info.size){info.size = Number(info.size)}
		if(!info.size) {
			var width = Number(info.width)
			var height = Number(info.height)
			info.size = width < height ? height : width;
		}
		if(info.size) {
			if(!min_size){min_size = info.size}
			else if (info.size < min_size) {
				min_size = info.size
			}
		}
	}
	return min_size
}
function rmOutProfile(props) {
	var job = props.job;
	var step = props.step;
	var matrix = GEN.getMatrix({job:job})
	var board_layers = []
	for (var key in matrix) {
		if(matrix[key].context == "board") {
			board_layers.push(key)
		}
	}
	if(board_layers.length > 0) {
		GEN.openStep({job:job, name:step})
		GEN.affectedLayer({affected:'no',mode:'all'})
		GEN.clearLayers()
		GEN.selClearFeature()
		board_layers.forEach(function(layer){
			var bk = layer + "_tmpprofile";
			if(GEN.isLayerExists({job:job, layer:bk})){
				GEN.workLayer({name:bk,display_number:2,clear_before:'yes'})
				GEN.selAllFeat()
				if(GEN.getSelectCount() > 0) {GEN.selDelete()}
			}
			GEN.workLayer({name:layer,display_number:2,clear_before:'yes'})
			GEN.selectByFilter({profile:"out"})
			if(GEN.getSelectCount() > 0){
				GEN.selMoveOther({target_layer:bk,invert:'no',dx:0,dy:0,size:0})
			}
		})
		GEN.clearLayers()
        GEN.selClearFeature()
        GEN.closeStep()
	}
}
function mvOutProfile (props) {
	var job = props.job;
	var step = props.step;
	var matrix = GEN.getMatrix({job:job})
	var profile_layers = []
	var layers = Object.keys(matrix)
	layers.forEach(function(layer){
		if( /^(.*)_tmpprofile$/ig.test(layer) && layers.indexOf(RegExp.$1) >= 0) {
			profile_layers.push({
				layer:RegExp.$1,
				profile_layer:layer
			})
		}
	})
	if(profile_layers.length >= 0) {
		GEN.openStep({job:job, name:step})
		GEN.affectedLayer({affected:'no',mode:'all'})
		GEN.clearLayers()
		GEN.selClearFeature()
		profile_layers.forEach(function(item){
			GEN.workLayer({name:item.profile_layer,display_number:2,clear_before:'yes'})
			GEN.selAllFeat()
			if(GEN.getSelectCount() > 0) {
				GEN.selMoveOther({target_layer:item.layer,invert:'no',dx:0,dy:0,size:0})
			}
			GEN.deleteLayer({job:job, layer:[item.profile_layer]})
		})
		GEN.clearLayers()
		GEN.selClearFeature()
        GEN.closeStep()
	}
}

function autoUn(path, dirname) {
    if(/\.rar$/.test(path)){
		fs.mkdir(dirname)
        process.exec('/opt/rar/unrar', ['x' , path, dirname, "-o+"])
    } else {
        process.exec('/usr/local/lib/p7zip/7za', ['x' , path, "-o"+dirname, "-aoa"])
    }
}

function exportInfo(info){
    fs.writeFile("/home/toplinker/samba/Test_Scott/tmp", _.toString(info))
}

function sendEmail(msg) {
    if(mailUserList && mailUserList.length) {
        var info = {
            host:"10.90.79.37",
            port:"25",
            from:'topdfm@sys.com',
            connection_type:mail.ConnectionType.TCP,
            subject:msg.subject,
            content:msg.content
        };
        info.to = mailUserTo.join(";")
        info.cc = mailUserCc.join(";")
        info.bcc = mailUserBcc.join(";")
        var err = mail.sendMail(info);
        if (err.isValid()) {
            print(err.text());
        }
    }
}
function moveSlotDrls(props){
	var job = props.job;
	var step = props.step;
	var drl_layer = props.layer;
	var flag;
	GEN.affectedLayer({affected:'no',mode:'all'})
    GEN.workLayer({name:drl_layer,display_number:2,clear_before:'yes'})
    GEN.selectByFilter({feat_types:"line"})
	if(GEN.getSelectCount() > 1) {
		GEN.selClearFeature();
		var feats = GEN.getFeatures({job:job, step:step, layer:drl_layer, options:"feat_index", units:"mm"}).filter(function(v){
			return v.type == "line"
		})
		feats.forEach(function(item){
			GEN.selClearFeature();
			var index = item.index;
			// 选择index
			GEN.selLayerFeat({layer:drl_layer, index:index,operation:"select"})
			var tmp_layer = drl_layer + "_tmp"
			if(GEN.isLayerExists({job:job, layer:tmp_layer})){
				GEN.deleteLayer({job:job, layer:tmp_layer})
			}
			GEN.selMoveOther({target_layer:tmp_layer,invert:'no',dx:0,dy:0,size:0})
        	GEN.workLayer({name:tmp_layer,display_number:2,clear_before:'yes'})
			// 参考选择辅助层 include 的移动走
			GEN.selRefFeat({layers:drl_layer,use:'filter',mode:"cover"})
			if(GEN.getSelectCount() > 0) {
				GEN.selMoveOther({target_layer:drl_layer + "_inner_slot",invert:'no',dx:0,dy:0,size:0})
                flag = drl_layer + "_inner_slot"
            } else {
				GEN.selMoveOther({target_layer:drl_layer,invert:'no',dx:0,dy:0,size:0})
            }
            if(GEN.isLayerExists({job:job, layer:tmp_layer})){
				GEN.deleteLayer({job:job, layer:tmp_layer})
			}
			GEN.selClearFeature();
            GEN.workLayer({name:drl_layer,display_number:2,clear_before:'yes'})
		})
	}
    return flag
}