/*
    NAME: 
    DESCRIPTION: 客户数据分析
    PARAMETER:
	[
		{
			name : 'step_filter',
			title : 'Step过滤',
			type : 'LineEdit',
			property : {tool_tip : '过滤step信息'}
        },
        {
            name : 'erf',
			title : 'erf名称',
			type : 'LineEdit',
			property : {tool_tip : ''}
        },
        {
            name : 'auto_save',
			title : '自动保存',
            type : 'RadioBox',
            property : {
				item_list:[
					{name:'yes',text:'YES'},
					{name:'no',text:'NO'},
				],
				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 IKM = $.ikm;
var GEN = $.gen;
var GUI = $.gui;
var Job = $.job;
var JobId = $.job_id;
var oChecklistName = "mychecklist"
var info = {
    min_line_width: ["line"],
    min_line_spacing: ["l2l"],
    min_line2pad: ["p2line"],
    min_pad2pad: ["p2p", "smd2smd", "smd2pad"],
    min_ar: ["ar","pth_ar"]
}
var mode = "develop"   // 运行模式  develop 开发模式 方便调试
var pcs_step = "cad"  
var array_step = "stp"
try {
    // 获取脚本参数
    var par = $.par;
    var parParams = ["step_filter", "erf", "auto_save"]; // par应该有的属性
    var isParExist = parParams.reduce(function (a, b) {
        if (!par.hasOwnProperty(b)) {
            a = false;
        }
        else if (par[b] == "") {
            switch (b) {
                case "step_filter":
                    par.pcs_step = "pcs";
                    break;
                case "auto_save":
                    par.auto_save = "yes";
                    break;
            }
        }
        return a;
    }, true);
    if(!isParExist){return "Error"} // 如果par没有parParams里的属性 就退出
    // ! tmp<
    par.vc_src_01005_pad_result = ".smd"
    // ! tmp>
    // 获取matrix相关信息
    var job = Job  // 分析的料号
    var matrixInfo = getMatrixInfo({job:job})
    var matrix_analysis = UPLOAD_LAYER_MATRIX({job:job,matrixInfo:matrixInfo})  // 分析matrix

    // 开始前业务判断
    err = beforeStart({job:job})
    if(err){
        throw err
    }

    // 获取matrix  signal   drill
    var matrix = matrixInfo.matrix
    var signalLayers = getLayers({ matrix: matrix, type: "signal" })
    var drillLayers = getLayers({ matrix: matrix, type: "drill" })

    var stepList = GEN.getStepList({job:Job})   // 获取steplist

    stepList = stepList.filter(function(v){  // 过滤step
        var reg = new RegExp("^"+par.step_filter+"$","ig")
        return reg.test(v)
    })
    // 脚本开始

    // 料号数据分析
    // var jobInfo = saveJobInfo({job:job,pcs_step:pcs_step,array_step:array_step,matrixInfo:matrixInfo,matrix_analysis:matrix_analysis},par)

    // 分析钻孔属性  // todo 镭射切割长度
    analysis_drill({job:job,pcs_step:pcs_step,array_step:array_step,matrixInfo:matrixInfo,matrix_analysis:matrix_analysis},par)
    // 层数据分析
    // var hasattrs = [".smd"]
    // var ishasattrs = hasattrs.reduce(function(a,b){  // 判断是否含有.smd属性
    //     if(!hasAttr({job:job,steplist:stepList,attr:b,layers:matrixInfo.mOuters})){ a = false }
    //     return a
    // },true)
    // if(!ishasattrs){ // 如果找不到.smd 分析.smd
    //     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=Yes)(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")
    // }
    // // 分析smd
    // var smdInfo = smdAnalysis({job:Job,steplist:stepList,layers:matrixInfo.mOuters,attr:".smd"})  
    // // 分析bga
    // var bgaInfo = bgaAnalysis({job:Job,steplist:stepList,layers:matrixInfo.mOuters,attr:".bga"})
    // if(JSON.stringify(bgaInfo)  == "{}"){
    //     GUI.msg("can not find .bga attr")
    // }
    // var save_info = [smdInfo, bgaInfo, jobInfo.laser_info];  // 保存 smd  和  bga数据
    // save_info.forEach(function(item){
    //     Object.keys(item).forEach(function(key){
    //         var val = item[key]
    //         IKM.save_layerinfo({
    //             jobid: JobId,
    //             layer: key,
    //             layerinfohash: val
    //         })
    //     })
    // })
    // stepList.forEach(function(step){
    //     GEN.openStep({ job: Job, step: step })
    //     GEN.clearLayers()
    //     GEN.affectedLayer({ affected: 'no', mode: 'all' })
    //     // 创建chklist并运行 如果chklist存在先删除
    //     if (GEN.isChklistExists({ job: Job, step: step, chklist: oChecklistName })) {
    //         GEN.COM("chklist_delete", { chklist: oChecklistName })
    //     }
    //     createChklistAndRun({   // 创建checklist并运行
    //         layers: signalLayers,
    //         items: [{
    //             name: "signal_layer_checks",
    //             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"
    //             }
    //         }]
    //     })
    //     // signal层分析结果
    //     var res = analysisChkAttr({layers:signalLayers, info:info, step:step})
    //     // 铜面积
    //     var copper_percent_pcs = signalLayers.map(function(v){
    //         var tmp = {layer:v}
    //         tmp.copper_percent = GEN.copperArea({layer1:v}).percent + "%"
    //         return tmp
    //     })
    //     // 钻孔
    //     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(v.size)})
    //         .reduce(function(a,b){
    //             if(b.pad!="0"){
    //                 a.push(b.symbol)
    //             }
    //             return a 
    //         },[])[0]
    //         return {layer:v,symbol:symbol,start:matrix[v]["drl_start"],end:matrix[v]["drl_end"]}
    //     })
    //     var drillRes = analysisDrill(drillToSignals,step) // 钻孔分析结果
 
    //     //  数据入库 
    //     Object.keys(res).forEach(function(key){
    //         var val = res[key]
    //         IKM.save_layerinfo({
    //             jobid: JobId,
    //             layer: key,
    //             layerinfohash: val
    //         })
    //     })
    //     copper_percent_pcs.forEach(function(item){
    //         IKM.save_layerinfo({
    //             jobid: JobId,
    //             layer: item.layer,
    //             layerinfohash: {
    //                 copper_percent: item.copper_percent
    //             }
    //         })
    //     })
    //     drillRes.forEach(function(item){
    //         IKM.save_layerinfo({
    //             jobid: JobId,
    //             layer: item.layer,
    //             layerinfohash: {
    //                 drl_pad_top: item.drl_pad_top,
    //                 drl_pad_bot: item.drl_pad_bot,
    //             }
    //         })
    //     })
    // })
    // matrix信息
    if (/yes/ig.test(par.auto_save)) {
        GEN.saveJob({ job: Job });
    }
    GEN.checkInout({job:Job,mode:"in"})
    GEN.closeJob({job:Job})
    return 'Done';
}
catch (error) {
    GUI.msg(error);
    return 'Error';
}

function getLayers(par) { // 获取操作层 {type:"drill", matrix: matrix}
    var arr = []
    for (var key in par.matrix) {
        var val = matrix[key]
        if (val.context == "board" && val.layer_type == par.type) {
            arr.push(val.name)
        }
    }
    return arr
}

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
    layers.forEach(function (v) {
        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: 1,
                    attr: v + "_min_" + type
                })
                if (a === "N/A" && /\d+/.test(tmp)) {
                    a = tmp
                }
                if (/\d+/.test(a) && /\d+/.test(tmp) && Number(tmp) < Number(a)) {
                    a = tmp
                }
                return a
            }, "N/A")
        })
    })
    return hash
}

function analysisDrill(par, step){
    // {"layer":"d1-2","symbol":"r3.937","start":"top","end":"isl2"}
    var res = par.map(function(drill){
        GEN.affectedLayer({affected:"yes",layer:drill.layer,clear_before:"yes",mode:"all"})
        GEN.selClearFeature()
        GEN.selectByFilter({feat_types:"pad",include_syms:drill.symbol})
        // 拷贝到_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,invert:'no',dx:0,dy:0,size:0})
        GEN.affectedLayer({affected:"no",mode:"all"})
        GEN.selClearFeature();
        [drill.start,drill.end].forEach(function(item, i){
            GEN.workLayer({name:item,display_number:1,clear_before:"yes"})
            GEN.selRefFeat({
                layers: tmplayer,
                use: "filter",
                mode: "include",
                f_types: "pad",
                pads_as:"shape",
                polarity:"positive",
                include_syms:drill.symbol,
                filter: {
                    feat_types:'pad'
                }
            })
            var res = ""
            var pads = GEN.getFeatures({job:Job,step:step,layer:item,options:"select"})
            if(pads && pads.length) {
                pads = pads.filter(function(item){
                    return /^r\d+/.test(item.symbol)
                })
                pads = pads.sort(function(a,b){
                    return parseInt(a.symbol) - parseInt(b.symbol) 
                })
                res = pads[0].symbol.slice(1)
            }
            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 getMatrixInfo(props){  // 获取matrix各种信息
    var job = props.job || Job
    var matrix = GEN.getMatrix({job:job})
    var res = {
        matrix: matrix,
        matrixVal: [],
        mSignal: {},
        mSignals: [],
        mDrill: {},
        mDrills: [],
        mSolderMask: {},
        mSolderMasks: [],
        mOuters: [],
        mBoardLayer: []
    }
    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"){
           a.push({
                signalL: b.name,
                solderL: res.mSolderMasks.filter(function(v){
                    return v.side == b.side
                })[0]["name"]
           })
        }
        return a
    },[])
    return res
}

function beforeStart(props){  // 脚本开始前的业务判断
    var job = props.job;
    var j = {job:job}
    if(!GEN.isJobExists(j)){return "job: "+job+" is not exist"}
    if(!GEN.isJobOpen(j)){GEN.openJob(j)}
    if(GEN.checkInout({job:job,mode:"test"}) != 0){
        if (mode != "develop"){
            return "the job checked"
        }
    }
    GEN.checkInout({job:Job,mode:"out"})
}

function hasAttr(props){  // 判断层 有没有部分属性
    var job = props.job || Job // 有没有smd和bga属性的物件
    var attr = props.attr
    var res = false
    try {
        if(!GEN.isJobOpen({job:Job})){GEN.openJob({job:Job})}
        var steplist = props.steplist || GEN.getStepList({job:job})
        var layers = props.layers
        steplist.forEach(function(step){
            GEN.openStep({job:job,name:step})
            for (var i = 0;i <layers.length;i++){
                var layer = layers[i]
                GEN.workLayer({name:layer.signalL,display_number:2,clear_before:'yes'})
                GEN.selClearFeature()
                GEN.selectByFilter({attribute:attr})
                var count = GEN.getSelectCount()
                if(count && count > 0){
                    throw true
                }
            }
            GEN.clearLayers()
            GEN.closeStep()
        })
    }
    catch (msg) {
        res = msg
        GEN.clearLayers()
        GEN.closeStep()
    }
    return res
}

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,step:step})
        layers.forEach(function(layer){
            // 计算开窗 数量 
            GEN.workLayer({name:layer.solderL,display_number:2,clear_before:'yes'})
            if (!res.hasOwnProperty(layer.solderL)){
                res[layer.solderL] = {}
            }
            GEN.selectByFilter({profile:'in'})
            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/})
                if (!res.hasOwnProperty(layer.signalL)){
                    res[layer.signalL] = {}
                }
                res[layer.signalL]["min_smd_width"] = symbolInfo["min_width"]
                res[layer.signalL]["min_smd_length"] = symbolInfo["min_length"]
                // 分析最小smd间距
                // 创建一个checklist并且分析
                var min_smd_pitch = smdPitch({job:job,step:step,layer:tmp_layer})
                res[layer.signalL]["min_smd_pitch"] = min_smd_pitch
                // 分析开窗宽高
                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/})
                res[layer.signalL].min_smd_opening_width = symbolInfo_solder["min_width"]
                res[layer.signalL].min_smd_opening_length = symbolInfo_solder["min_length"]
                GEN.deleteLayer({job:job,layer:solderL_tmp})
                GEN.deleteLayer({job:job,layer:tmp_layer})
            }
        })
        GEN.closeStep()
    })
    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,step: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].min_bga_size = min_symbols_info["min_bga_size"]
                res[layer.signalL].min_bga_openging_size = min_symbols_info["min_bga_openging_size"]
                res[layer.signalL].min_bga_pitch = min_symbols_info["min_bga_pitch"]
                // 分析所有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].bga_min_pitch = all_bga_min_pitch_info["bga_min_pitch"]
                res[layer.signalL].bga_min_pitch_pad_size = all_bga_min_pitch_info["bga_min_pitch_pad_size"]
                res[layer.signalL].bga_min_pitch_openging_size = all_bga_min_pitch_info["bga_min_pitch_openging_size"]
                GEN.deleteLayer({job:job,layer:tmp_layer})
            } 
        })
        GEN.closeStep()
    })
    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 symbolAnalysis(props){
    var symbols = Object.keys(props.symbols).reduce(function(a,b){
        if(props.filterReg.test(b)){
            var res = /^rect(\d+\.\d+|\d+)x(\d+\.\d+|\d+)/ig.exec(b).slice(1)
            var num1 = res[0], num2 = res[1]
            if(num1<=num2){
                a.width.push(num1)
                a.length.push(num2)
            } else {
                a.width.push(num2)
                a.length.push(num1)
            }
        }
        return a
    },{width:[],length:[]})
    return {
        min_width:symbols.width.reduce(function(a,b){return b-a > 0 ? a : b}),
        min_length:symbols.length.reduce(function(a,b){return b-a > 0 ? a : b})
    }
}

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 })
    }
    // 创建并运行
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [{
            name: "smdPitch",
            nact: 1,
            action: "valor_analysis_signal",
            erf: "S2_conductor_Check",
            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"
            }
        }]
    })
    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 symbols = GEN.getLayerSymsHist({job:job,layer:tmp_layer,step:step})
    // 获取最小smd间距
    var res = _.values(symbols).sort(function(a,b){return Number(a.size)-Number(b.size)})[0].size
    GEN.deleteLayer({job:job,layer:tmp_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer1})
    GEN.deleteLayer({job:job,layer:tmp_layer2})
    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})
    // 分析数据
    // 尺寸
    res.min_bga_size = symbols.size
    // 开窗大小
    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})
    res.min_bga_openging_size = _.values(symbols_solder).reduce(function(a,b){
        if (b.pad > 0 && b.line) {
            a.push({symbol:b.symbol, size:Number(b.size)})
        }
        return a
    },[]).sort(function(a,b){return a.size-b.size})[0].size
    // 间距
    res.min_bga_pitch = bgaPitch({job:job,step:step,layer:tmp_layer})

    GEN.deleteLayer({job:job,layer: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 symbols = props.symbols
    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 })
    }
    // 创建并运行
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [{
            name: "bgaPitch",
            nact: 1,
            action: "valor_analysis_signal",
            erf: "S2_conductor_Check",
            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"
            }
        }]
    })
    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:"bga_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 symbols = GEN.getLayerSymsHist({job:job,layer:tmp_layer,step:step})
    // 获取最小smd间距
    var min_symbol = _.values(symbols).sort(function(a,b){return Number(a.size)-Number(b.size)})[0]
    res.bga_min_pitch = min_symbol.size
    // 所有BGA中间距最小的PAD大小
    GEN.selClearFeature()
    GEN.selectByFilter({include_syms:min_symbol.symbol})
    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]
    res.bga_min_pitch_pad_size = min_pad_symbol.size

    // 最小pitch开窗大小
    GEN.workLayer({name:solder_layer,display_number:1,clear_before:'yes'});
    GEN.selClearFeature()
    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"})
    var symbols_solder = GEN.getLayerSymsHist({job:job,layer:solder_layer_tmp,step:step})
    var symbols_solder_list = _.values(symbols_solder).filter(function(v){return /\d+/ig.test(v.size)})
    if(symbols_solder_list.length){
        res.bga_min_pitch_openging_size = symbols_solder_list.sort(function(a,b){return Number(a.size)-Number(b.size)})[0].size
    }
    GEN.deleteLayer({job:job,layer: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 })
    }
    // 创建并运行
    GEN.createChklist({ // 创建checklist
        chklist: ck,
        items: [{
            name: "bgaPitch",
            nact: 1,
            action: "valor_analysis_signal",
            erf: "S2_conductor_Check",
            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"
            }
        }]
    })
    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:"bga_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 symbols = GEN.getLayerSymsHist({job:job,layer:tmp_layer,step:step})
    // 获取最小smd间距
    var res = _.values(symbols).sort(function(a,b){return Number(a.size)-Number(b.size)})[0].size
    GEN.deleteLayer({job:job,layer:tmp_layer})
    GEN.deleteLayer({job:job,layer:tmp_layer1})
    GEN.deleteLayer({job:job,layer:tmp_layer2})
    // 矩阵

    return res
}

function saveJobInfo(props,par){
    var job = props.job
    var pcs = props.pcs_step
    var arr = props.array_step
    var matrix_analysis = props.matrix_analysis
    var matrixInfo = props.matrixInfo
    var matrix = matrixInfo.matrix
    var info = {
        layer_count: "",     // Board属性的signal或者power_ground层
        vc_card_size_w: "",    //  card短边尺寸
        vc_card_size_l: "",     // card长边尺寸
        vc_array_size_w: "",    //  array短边尺寸
        vc_array_size_l: "",      //  array长边尺寸
        vc_pcs_count_on_panel: "",   // todo array中pcs的数量
        stack_vias_number: "",   // via孔连续叠加的层数
        stack_vias_more: "",   // yes|no : 14层板以上时,Stack Vias >=12时,存yes
        depth_drilling: "",          // yes|no :存在depth_drill 层时,,存yes
        depth_routing: "",       // yes|no :存在depth_routing 层时,,存yes
        laser_via_on_buried_hole: "",       // todo yes|no:via孔在埋孔上时,存yes
        milling_bit_size: "",       // todo 检查array中pcs的最小间距值
        milling_length: "",     // todo 检查array中铣切长度
        vc_src_01005_pad_result: "",     //  yes|no:board层中检查存在01005属性物件时,存yes
        ATS_technology_25dc: "",           // yes|no:存在cavity层别时存yes
        ATS_technology_25dr: "",           // yes|no:存在cavity层别时存yes
        vc_src_EDGE_PLATING: "",    // yes|no:检查线路外形是否存在物件,存在则存yes
        edge_plating_length: "",        //  todo
        glod_finger: "",     // todo
        glod_finger_area: "",       //  todo
        solder_mask_side: "",       // top|bot|both:检查防焊层所在面次
        silk_screen_side: "",       // top|bot|both:检查文字层所在面次
    }
    info.layer_count = matrixInfo.mSignals.length   // 10

    var pcs_profileLimits = GEN.getProfileLimits({job:job,step:pcs})
    var array_profileLimits = GEN.getProfileLimits({job:job,step:arr})

    info.vc_card_size_w = pcs_profileLimits.xsize.toFixed(3)
    info.vc_card_size_l = pcs_profileLimits.ysize.toFixed(3)
    info.vc_array_size_w = array_profileLimits.xsize.toFixed(3)
    info.vc_array_size_l = array_profileLimits.ysize.toFixed(3)
    // IKM.msg(GEN.getRepeat({job:"1",step:"stp1"})) // ?

    // 找出 镭射孔
    var laser_layers = []
    for (var key in matrix_analysis) {
        var val = matrix_analysis[key]
        if(val.type == "laser_drill"){
            laser_layers.push(val)
        }
    }
    GEN.openStep({job:job,name:pcs})
    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].name
        var cover_layer = laser_layers[i+1].name
        GEN.workLayer({name:start_layer,display_number:2,clear_before:'yes'})
        GEN.selClearFeature()
        GEN.selRefFeat({layers:cover_layer,use:'filter',mode:'cover'})
        var count = GEN.getSelectCount()
        if(count>0){
            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].name + "_cover" 
        GEN.selClearFeature()
        GEN.workLayer({name:layers[0].name,display_number:2,clear_before:'yes'})
        GEN.selAllFeat()
        selCopyLayer({job: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: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].name,use:'filter',mode:'cover'})
        var count = GEN.getSelectCount()
        if (count < 1) {
            GEN.deleteLayer({job:job,layer:start})
            return ++end_index
        }
        var nextstartlayer = layers[0].name + "_cover" 
        selCopyLayer({job:job,layer:nextstartlayer})
        GEN.deleteLayer({job:job,layer:start})
        return start_cover_next(nextstartlayer,layers,++end_index)
    }
    analysis_via_number(laser_layers.slice())
    if (via_vias_info.length == 1){
        info.stack_vias_number = via_vias_info[0]
    }else{
        info.stack_vias_number = via_vias_info.reduce(function(a,b){return a-b>0?a :b})
    }
    info.stack_vias_more = "no"
    if(GEN.getLayerCount({job:job}) > 14 && info.stack_vias_number >= 12){
        info.stack_vias_more = "yes"
    }

    // via孔在埋孔上?

    info.depth_drilling = matrix.hasOwnProperty("depth_drill") ? "yes" : "no"
    info.depth_routing = matrix.hasOwnProperty("depth_routing") ? "yes" : "no"
    var is_cavity = matrix.hasOwnProperty("cavity") ? "yes" : "no"
    info.ATS_technology_25dc = is_cavity  // no
    info.ATS_technology_25dr = is_cavity  // no

    var solder_paste_layers = _.values(matrix).filter(function(v){return v.layer_type == "solder_paste"})
    var solder_solder_mask = _.values(matrix).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 solder_solder_info= solder_solder_mask.map(function(v){return v.side}).reduce(function(a,b){
        if(a.indexOf(b)<0){ a.push(b) }
        return a
    },[])
    
    if(solder_paste_info.length == 0){
        info.solder_mask_side = "no"
    } else if (solder_paste_info.length == 1) {
        info.solder_mask_side = solder_paste_info[0]
    } else {
        info.solder_mask_side = "both"
    }
    if(solder_solder_info.length == 0){
        info.silk_screen_side = "no"
    } else if (solder_solder_info.length == 1) {
        info.silk_screen_side = solder_solder_info[0]
    } else {
        info.silk_screen_side = "both"
    }
    // 线路外形
    info.vc_src_EDGE_PLATING = "no"
    GEN.affectedLayer({affected:'no',mode:'all'})
    try {
        matrixInfo.mSignals.forEach(function(v){
            GEN.workLayer({name:v.name,display_number:2,clear_before:'yes'})
            GEN.selClearFeature()
            GEN.selRefFeat({layers:"rout",mode:"touch",use:"filter"})
            if( GEN.getSelectCount()>0){
                throw "yes"
            }
        })
    } catch (msg) {
        info.vc_src_EDGE_PLATING = msg
        GEN.affectedLayer({affected:'no',mode:'all'})
        GEN.selClearFeature()
    }

    info = Object.keys(info).reduce(function(a,b){// 过滤结果
        if(info[b] != ""){
            a[b] = info[b]
        }
        return a
    },{})

    // 有无属性vc_src_01005_pad_result
    GEN.openStep({job:job,name:pcs})
    GEN.affectedLayer({affected:'yes',layer:matrixInfo.mBoardLayer,clear_before:'yes'});
    GEN.selClearFeature()
    GEN.selectByFilter({attribute:par.vc_src_01005_pad_result})
    info.is_01005_pad = GEN.getSelectCount() > 0? "yes" : "no"
    GEN.affectedLayer({affected:'no',mode:'all'});
    GEN.closeStep()

    IKM.save_job_info({
        jobid: JobId,
        jobinfohash:info
    })
    return {info:info,laser_info:laser_info}
}

function UPLOAD_LAYER_MATRIX(props){
    var matrixInfo = props.matrixInfo
    var job = props.job
    props.jobcategory = "work"
	var matrix = ANALYSIS_STACKUP({job:job, matrixInfo:matrixInfo, jobcategory:props.jobcategory});
	var func = 'function(ARGV)\
	{\
		var jobId = ARGV["job_id"];\
		var tableName = "pdm_job_" + ARGV["jobcategory"] + "_layer";\
		//GUI.msgbox({text: TDataParse.variant2JsonStr(tableName)});\
		var db = new TSqlQueryV2(T_SQLCNT_POOL.getSqlDatabase());\
		db.begin();\
		try{\
			var oLayers = db.selectMapMap({table:tableName, field:["odb_name","release_status"], where:{job_id:jobId}, uniquefield:"odb_name"});\
			if (db.lastError().isValid()) throw db.lastError();\
			//GUI.msgbox({text: TDataParse.variant2JsonStr(oLayers)});\
			\
			db.deleteRow({table:tableName, where:{job_id:jobId}});\
			if (db.lastError().isValid()) throw db.lastError();\
			\
			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"];\
			\
			for (var i=0;i<ARGV["layers"].length;i++)\
			{\
				layerName = ARGV["layers"][i];\
				var layerInfo = ARGV["matrix"][layerName];\
				var tmpData = {"job_id":jobId, "name": layerInfo["name"]};\
				if (ARGV["releasestatus"] != undefined && ARGV["releasestatus"] != null && ARGV["releasestatus"].length>0\
					&& ARGV["releasestatus"][layerInfo["odb_name"]] != undefined && ARGV["releasestatus"][layerInfo["odb_name"]] != null )\
				{\
					tmpData["release_status"] = ARGV["releasestatus"];\
				}\
				else\
				{\
					tmpData["release_status"] = 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]];\
				}\
				//GUI.msgbox({text: TDataParse.variant2JsonStr(tmpData)});\
				db.insertRow({table:tableName, data:tmpData});\
				if (db.lastError().isValid()) throw db.lastError();\
			}\
			db.commit();\
			return new TDataResponse();\
		}\
		catch (err)\
		{\
			print(err.text());\
			db.rollback();\
			return new TDataResponse(err, "");\
		}\
	}';
    // var ret = IKM.command( func,
    //     {   job_id :JobId, 
    //         matrix:matrix,
    //         layers: Object.keys(matrix).sort(function(a,b){return matrix[a]["row"] - matrix[b]["row"]}), 
    //         jobcategory:props.jobcategory}, 1);
   
	// if (ret.errText) {
	// 	return {
	// 		jobcategory:props.jobcategory,
	// 		errText :ret.errText,
	// 		errCode :ret.errCode,
	// 	};
    // }
    return matrix
}

function ANALYSIS_STACKUP(props){
    var job = props.job
    if(!props.hasOwnProperty("jobcategory")){
        props.jobcategory = 'work'
    }
    var matrix = JSON.parse(JSON.stringify(props.matrixInfo.matrix))
	var layer_count = IKM.get_jobinfo({jobname:JobId,jobcategory:props.jobcategory,jobinfo:'layer_count'});
    if(!layer_count){
        layer_count = GEN.getLayerCount({job:job})
    }
	IKM.save_job_info({jobid:JobId,jobcategory:'work',jobinfohash:{TL_layer_count:layer_count}});
	_.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 == 'drill'){
			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 (/^txt(\d+)\-(\d+)$/.test(layer.odb_name)){
            var tmp = /^txt(\d+)\-(\d+)$/.exec(layer.odb_name)
			var drl_star = tmp[1];
			var drl_end = tmp[2];
			layer.name = 'drill' + drl_star + '-' + drl_end;
			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_star == 1 || drl_end == layer_count ){
				layer.type = 'blind_drill';
			}
			else{
				layer.type = 'bury_drill';
			}
		}
		else if (/^d(\d+)\-(\d+)$/.test(layer.odb_name)){
            var tmp = /^d(\d+)\-(\d+)$/.exec(layer.odb_name)
			var drl_star = tmp[1];
			var drl_end = tmp[2];
			layer.name = 'd'+drl_star+'-'+drl_end;
			layer.drl_start_num = drl_star;
			layer.drl_end_num = drl_end;
			layer.drl_from_num = drl_star;
			layer.drl_to_num = drl_end;
			layer.type = 'laser_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 analysis_drill(props,par){  // 分析钻孔
    var job = props.job
    var pcs = props.pcs_step
    var arr = props.array_step
    var matrix = props.matrix_analysis
    // 获取镭射层
    var laser_layers = Object.keys(matrix).filter(function(key){
        return matrix[key].type == "laser_drill" && matrix[key].odb_context=="board"
    })
    GEN.openStep({job:job,name:pcs})
    laser_layers.forEach(function(layer){
        GEN.workLayer({name:layer,display_number:1,clear_before:"yes"})
        GEN.selClearFeature()
        GEN.selAllFeat()
        GEN.selAddAttr({attribute:"via"})  // ?
        GEN.selAllFeat()
    })
    GEN.closeStep()
}