=head
 NAME: 
 DESCRIPTION:  Run Overlay
 PARAMETER:
	[
		{
			name : 'step',
			title : 'Panel Step',
			type : 'LineEdit',
			value : 'E:/',
			pack : {row:0,column:1},
			property : {
				tool_tip:'panel step名称,如未设置,默认为panel'
			}
		},
		{
			name : 'units',
			title : 'Units',
			type : 'ComboBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'mm',text:'mm'},
					{name:'inch',text:'inch'},
				],
				tool_tip:'脚本运行genesis的工作单位,如未设置,默认为inch'
			},
			pack : {row:1,column:1},
		},
		{
			name : 'panel_category',
			title : '板框类型',
			type : 'TextEdit',
			property:{tool_tip:'未定义则默认为标准的category:overlay'},
			setter:function(obj,value,self){
				obj.setPlainText(value);
			},
			getter:function(obj,self){
				return obj.plainText
			}
		},
		{
			name : 'version',
			title : 'Version',
			type : 'ComboBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'release',text:'正式版'},
					{name:'test',text:'测试版'},
					{name:'select',text:'选择版本'},
				],
				tool_tip:'板框版本:正式版为1,测试版为0,如未设置,默认为选择版本'
			},
			pack : {row:1,column:1},
		},
		{
			name : 'font',
			title : 'Font',
			type : 'LineEdit',
			pack : {row:0,column:1},
			property : {
				tool_tip:'genesis中添加文字字体,默认为standard_new'
			}
		},
		{
			name : 'default_layer',
			title : '默认层别',
			type : 'TextEdit',
			pack : {row:0,column:1},
			property : {
				tool_tip:'默认层别'
			},
			setter:function(obj,value,self){
				obj.setPlainText(value);
			},
			getter:function(obj,self){
				return obj.plainText
			}
		},
		{
			name : 'save_job',
			title : '保存料号',
			type : 'RadioBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'Yes',text:'Yes'},
					{name:'No',text:'No'},
				],
				tool_tip:'脚本结束后自动保存料号,未设定,默认为No'
			},
			pack : {row:1,column:1},
		}
	]
	
 VERSION_HISTORY:
	V1.00 2019-05-17 Alan Fu
	    1.新版本.
		
 HELP:
	<html><body bgcolor="#DDECFE">
		<font size="3" color="#003DB2"><p>功能简介</p></font>
		  <p>  Run Overlay </p>
	</body></html>
=cut
#################################################################################################################
#################################################################################################################
use strict;
use utf8;
use JSON;
use Encode;
use Data::Dump 'dump';
use Number::Format 'round';
use_module('TL_GenMath');
use_module("PubFunction");
my $Func = PubFunction->new();
my $Math = TL::GenMath->new();
#
my (@Report,$M,$S,$P,$L,$D,$MAP_DATA,@LAYERS,$JOB_PATH,$LAYER_TYPE_LIST,@LAYER_ROWS,$_TMP_LAYER_STK);
my ($Job,$Step,$Return) = ($JOB,undef,'finish');
my ($_FEAT_NUM);
#
$PAR->{step}  = 'panel' if(!defined($PAR->{step}));
$PAR->{units} = 'inch'  if(!defined($PAR->{units}));
$PAR->{panel_category} = eval($PAR->{panel_category}) if $PAR->{panel_category};
$PAR->{version} = 'select' if(!defined($PAR->{version}));
$PAR->{font} = 'standard_new' if(!defined($PAR->{font}));
$PAR->{save_job} = 'No' unless $PAR->{save_job};
$PAR->{map_layer} = 'pnl-map' unless $PAR->{map_layer};
$PAR->{map_layer_error} = 'pnl-map-error' unless $PAR->{map_layer_error};
#################################################################################################################
#################################################################################################################
try {
	show_loading("判断是否选择料号...",0,position=>'n');
	unless($Job){
		$GUI->msgbox(-icon=>'error',-text=>"请先选择料号后再执行脚本!");
        return 'Cancel';
	}
	
	##
	update_loading("检查${Job}是否存在...",0,position=>'n');
	unless ( $GEN->isJobExists(job=>$Job) ){
        $GUI->msgbox(-icon=>'error',-text=>"料号${Job}不存在,请确认!");
        return 'Cancel';
    }
	
	##
    update_loading("正在打开料号${Job}...",0,position=>'n');
    $GEN->openJob(job=>$Job) unless ($GEN->isJobOpen(job=>$Job));
	
	##
	update_loading("过滤工作STEP...",0,position=>'n');
	my $ans_step = get_work_step();
	return $ans_step if $ans_step;
	
	##
	if (! $GEN->isLayerExists(job=>$Job,layer=>$PAR->{map_layer} ) ) {
		$GUI->msgbox(-type=>'error',-text=>"$PAR->{map_layer}  不存在, 请检查!");
        return 'Error';
	}
	
	##
	my $panel_category = $PAR->{panel_category} || 'overlay';
	unless(defined($panel_category)){
		$GUI->msgbox(-icon=>'error',-text=>"未定义板框运行类型!");
		return "error";
	}
	
	##
	update_loading("获取并选择板框执行版本..",0,position=>'n');
	my $version;
	my $version_list = $IKM->select_arrayhash(-table=>"pdm_panelmap",-field=>"version",-order=>"version ASC",-where => {category=>$panel_category});
	my @tmp_version;
	foreach my $ver(sort{$a->{version} <=> $b->{version} } @$version_list){
		push @tmp_version,$ver->{version} unless grep {$_ eq $ver->{version}} @tmp_version;
	}
	$version_list = \@tmp_version;
	##
	if( scalar(@$version_list) == 1 ){
		$version = $version_list->[0];
	}
	elsif($PAR->{version} eq "release" and grep( {$_ eq '1'} @$version_list) ){
		$version = "1";
	}
	elsif($PAR->{version} eq "test" and grep( {$_ eq '0'} @$version_list) ){
		$version = "0";
	}
	else{
		$version = $GUI->select_from_treeview(
			-headstock=>'tl-question',
			-defaultsize=>[200,300],
			-selectmode=>'single',
			-title=>__('请选择板框版本'),
			-treemapping=>[version=>'Glib::String'],
			-treecolumns=>[
			   {title=>__('Version'),renders=>[{class=>'Text',text=>'version'}]},
			],
			-treedata=>[map {{version=>$_}} @$version_list],
			-valuefield=>'version',
			-selectvalue=>$version_list->[0],
			-searchfield=>['version'],
		);
		return 'Cancel' unless($version);
	}
	
	##
	update_loading("打开${Step}STEP...",0,position=>'n');
	$GEN->openStep(job=>$Job,name=>$Step);
	$GEN->clearLayers();
	$GEN->affectedLayer( mode=>'all',affected=>'no' );
	$GEN->COM( "sel_options,clear_mode=clear_after,display_mode=all_layers,area_inout=inside,area_select=select,select_mode=standard,area_touching_mode=exclude");
	$GEN->zoomHome();
	$GEN->units(type=>$PAR->{units});
	$GEN->COM('display_sr',display=>'no');
	$GEN->COM('disp_off');
	
	#1.读取map 信息
	update_loading("正在读取pnl-map层信息 ,请稍候..",0,position=>'n');
	$MAP_DATA = PANEL_MAP_TO_DATA(job=>$Job,step=>$Step,layers=>[$PAR->{map_layer},$PAR->{map_layer_error}],units=>$PAR->{units});
	
	#2.读取MAP传来的值
	update_loading("正在读取pnl-map产生的信息文件 ,请稍候..",0,position=>'n');
	$JOB_PATH = $GEN->getJobPath(job=>$Job);
	unless (-f $JOB_PATH.'/user/PNL_map_info'){
		$GUI->msgbox('Map信息文件'.$JOB_PATH.'/user/PNL_map_info不存在!');
		return 'Error';
	}
	my $map_info = do($JOB_PATH.'/user/PNL_map_info');
	$S = $map_info->{S};
	$P = $map_info->{P};
	$L = $map_info->{L};
	$D = $map_info->{D};
	$M = $GEN->getMatrix(job=>$Job);
	delete $M->{$PAR->{map_layer}};
	delete $M->{$PAR->{map_layer_error}};
	#3.用户选择层
	update_loading("选择板框添加层别..",0,position=>'n');
	my $default_layer;
	if (lc($PAR->{default_layer}) eq 'all'){
		$default_layer = [keys %$M];
	}
	else{
		$default_layer = eval($PAR->{default_layer});
	}
	@LAYERS = $GUI->select_layer(
		-title=>__('Run Panel Overlay'),
		-layermatrix=>$M,
		-selectmode => 'multiple',
		-default => $default_layer,
	);
	return 'Cancel' unless(@LAYERS);
	
	update_loading("开始板框准备工作..",0,position=>'n');
	my $db_data = $IKM->select_arrayhash(
		-table=>"pdm_panelmap",
		-field=>["id", "parent_id", "version", "category", "flow_order", "symbol_name", "row_name", "map_symbol" , "hole_symbol" , "pattern_symbol", "condition", "content", "status",
				 "active", "map_class", "avoid_hole_class" , "avoid_pattern_class" , "row_type" , "layer_type", "priority"],
		-where=>{version=>$version,category=>$panel_category,active=>1},
		#-order=>'flow_order asc',
		-order=>"version ASC,parent_id ASC,flow_order ASC",
	);
	
	foreach my $row (@$db_data){
		if ($row->{row_type} eq 'Map Header' or $row->{row_type} eq 'Map Footer' or $row->{row_type} eq 'Map Symbol'){
			$row->{row_type} = 'Category';
		}
		push @LAYER_ROWS,$row;
	}
	foreach my $stack (values %$S){
		my $stk = $stack->{key};
		$L->{$stack->{top_layer_num}}{stack} = $stk;
		$L->{$stack->{top_layer_num}}{layer_side} = 'Top';
		$L->{$stack->{bot_layer_num}}{stack} = $stk;
		$L->{$stack->{bot_layer_num}}{layer_side} = 'Bot';
		foreach my $drl (keys %$D){
            my $drill = $D->{$drl};
            if ($drill->{drl_type} eq 'Laser'){
                if ($drill->{drl_start_num} == $stack->{start_num}){
                    $drill->{stack} = $stk;
                    $drill->{drl_side} = 'Top';
                }
                elsif($drill->{drl_end_num} == $stack->{end_num}){
                    $drill->{stack} = $stk;
                    $drill->{drl_side} = 'Bot';
                }
            }
           else{
                if ($drill->{drl_start_num} == $stack->{start_num} and
                    $drill->{drl_end_num} == $stack->{end_num})
                {
                    $drill->{stack} = $stk;
                }
            }
        }
	}
	
	_EXEC_ROWS(['Public','Overlay Header']);
	##
	foreach my $key(keys %$LAYER_TYPE_LIST){
		$LAYER_TYPE_LIST->{$key} = eval($LAYER_TYPE_LIST->{$key});
	}
	##
	my $_layer_n = 1;my $_layer_count = scalar(@LAYERS) + 2;
	foreach my $LAYER (@LAYERS){
		my ($LAYER_SIDE,$LAYER_NUM,$LAYER_STACK);
		$LAYER_NUM = $M->{$LAYER}{layer_num} || $M->{$LAYER}{tl_num};
		if ($M->{$LAYER}{drl_start_num} and $M->{$LAYER}{drl_end_num}){
			##core上打镭射增加
			if( $P->{laser_on_core} ){
				foreach my $stack (sort{$b->{stack_level} <=> $a->{stack_level} || $a->{start_num} <=> $b->{start_num}} values %$S){
					if ($M->{$LAYER}{drl_start_num} == $stack->{start_num} and $M->{$LAYER}{drl_end_num} == $stack->{end_num}){
						$LAYER_STACK = $stack->{key}; last;
					}
				}
			}
			##
			unless($LAYER_STACK){
				foreach my $stack (sort{$b->{stack_level} <=> $a->{stack_level} || $a->{start_num} <=> $b->{start_num}} values %$S){
					if ($M->{$LAYER}{drl_start_num} == $stack->{start_num} or $M->{$LAYER}{drl_end_num} == $stack->{end_num}){
						$LAYER_STACK = $stack->{key}; last;
					}
				}
			}
			if (exists $D->{$LAYER_STACK}){
				if ($M->{$LAYER}{layer_side}){
					$LAYER_SIDE = $M->{$LAYER}{layer_side} eq 'top' ? 'Top' : ($M->{$LAYER}{layer_side} eq 'bottom' ? 'Bot' : '');
				}
				else{
					$LAYER_SIDE = $D->{$LAYER_STACK}{drl_side};
				}
			}
		}
		elsif(exists $L->{$LAYER_NUM}){
			$LAYER_STACK = $L->{$LAYER_NUM}{stack};
			$LAYER_SIDE = $L->{$LAYER_NUM}{layer_side};
		}
		else{
			$LAYER_STACK = '1-'.$P->{layer_count};
			$LAYER_SIDE = $M->{$LAYER}{layer_side} eq 'top' ? 'Top' : ($M->{$LAYER}{layer_side} eq 'bottom' ? 'Bot' : '');
		}
		
		if(!defined($LAYER_SIDE) and defined($M->{$LAYER}{layer_side})){
			$LAYER_SIDE = $M->{$LAYER}{layer_side} eq 'top' ? 'Top' : ($M->{$LAYER}{layer_side} eq 'bottom' ? 'Bot' : '');
		}
		
		$_TMP_LAYER_STK = $LAYER_STACK;
		_EXEC_ROWS(['Layer Header'],$LAYER);
		show_loading("正在创建[${LAYER}]层板框图形...",$_layer_n/$_layer_count,position=>'n');
		CREATE_LAYER_OVERLAY(layer=>$LAYER,layer_stack=>$LAYER_STACK,layer_num=>$LAYER_NUM,layer_side=>$LAYER_SIDE);
		_EXEC_ROWS(['Layer Footer'],$LAYER);
		$_layer_n++;
		
	}
	$GEN->COM('disp_on');
	_EXEC_ROWS(['Overlay Footer']);
	
	##
	$GEN->clearLayers();
	$GEN->affectedLayer( mode=>'all',affected=>'no' );
	
	hide_loading();
	
	#报告
	if (@Report){
		#push @Report,"\n\n".$GEN->getUserName().":".$DB->get_now();
		my $Report = join("\n",@Report);
		$Report =~ s/\n/<br>/g;
		$Report =~ s/\s/\<p\>\&NBSP\;\<\/p\>/g;
		my $report_save = '<html><body text="#000022">';
		$report_save .= "$Report";
		$report_save .= '</body></html>';
		$IKM->update_flow_report(-report=>$report_save);
	}
	else{
		$IKM->update_flow_report(-report=>'');
	}
	
	##保存料号
	if( $PAR->{save_job} =~ /yes/i ){
		show_loading("$Job 正在保存料号,请稍候...",0,position=>'n');
		$GEN->checkInout(job=>$Job,mode=>'out');
		$GEN->saveJob(job=>$Job);
		hide_loading();
	}
	
    ###output and return status, if genesis error, it will output genesis error command
	unless ($GEN->{STATUS}){
		return $Return;
	}
	else{
		$GUI->msgbox(-icon=>'error',-text=>join("\n",@{$GEN->{STATUS}}));
		#addFlowNotes(-notes=>"   Genesis Error:\n ".join("\n   ",@{$GEN->{STATUS}}));
		return 'Error';
	}
}

catch Error::Simple with {
	my $error = shift;
	$GUI->msgbox(-icon=>'error',-text=>$error);
	return 'Error';
}

finally{
	$GEN->COM('filter_reset',filter_name=>'popup');
	$GEN->clearHighlight();
	$GEN->COM('disp_on');
};
#################################################################################################################
#################################################################################################################
sub get_work_step {
    my @steps =  $GEN->getStepList(job=>$Job);
	if ( @steps == 0 ) {
		$GUI->msgbox(-icon=>'error',-text=>'在料号中没有Step存在,你将退出!');
		return 'Cancel';
	}
	elsif (@steps != 0){
		my @tmp_steps = grep(/^$PAR->{step}$/,@steps);
		if ( @tmp_steps == 0 ) {
		   $GUI->msgbox(-icon=>'warning',-text=>'根据脚本参数过滤出来的step不存在,请检查资料或者脚本参数配置!');
		   return 'Cancel';
		}
		elsif (@tmp_steps == 1) {
			$Step = $tmp_steps[0];
		}
        else {
			update_loading("选择工作step...",0,position=>'n');
            $Step = $GUI->select_step(
				-title=>'请选择工作 Step',
				-steplist=>[@tmp_steps],
				-default=>[$tmp_steps[0]],
				-gen=>$GEN,
				-selectmode=>'single'
			);
            return 'Cancel' unless ($Step);            
        }
	}
	return undef;
}

sub _parse_layer_types{
	my $LAYER = shift;
	my $type_str = shift;
	my $layer_types = eval($type_str);
	$layer_types = [keys %$LAYER_TYPE_LIST] unless $layer_types;
	my $LAYER_TYPES;
	foreach my $type (@$layer_types){
		next unless $LAYER_TYPE_LIST->{$type};
		if (ref($LAYER_TYPE_LIST->{$type}) eq 'Regexp'){
			$LAYER_TYPES->{$type} = 1 if $LAYER =~ $LAYER_TYPE_LIST->{$type};
		}
		elsif(ref($LAYER_TYPE_LIST->{$type}) eq 'ARRAY'){
			foreach my $t (@{$LAYER_TYPE_LIST->{$type}}){
				if (lc($t) eq lc($type)){
					$LAYER_TYPES->{$type} = 1;
					last;
				}
			}
		}
		elsif(ref($LAYER_TYPE_LIST->{$type}) eq 'CODE'){
			$LAYER_TYPES->{$type} = 1 if  $LAYER_TYPE_LIST->{$type}->(LAYER=>$LAYER,M=>$M,D=>$D,L=>$L,P=>$P);
		}
		else{
			$LAYER_TYPES->{$type} = 1 if lc($LAYER) eq  lc($LAYER_TYPE_LIST->{$type});
		}
	}
	return $LAYER_TYPES;
}

sub MAP_TO_LAYER_FEATS {
	my %par = @_; #(layer=>'',draw_layer=>,parent_id=>)
	my $LAYER = $par{layer};
	my $LAYER_STACK = $par{layer_stack};
	my $LAYER_NUM = $par{layer_num};
	my $LAYER_SIDE = $par{layer_side};
	my $DRAW_LAYER = $par{draw_layer};
	$DRAW_LAYER= $par{layer} unless $DRAW_LAYER;
	my @feats;
	##
	$par{parent_id} = 0 unless $par{parent_id};
	##
	foreach my $row (@LAYER_ROWS){
		#条件
		next if $par{parent_id} != $row->{parent_id};
		#$GUI->debug(dump($par{parent_id},  $row->{parent_id}));
		if ($row->{row_type} eq 'Layer'){
			#my $ans = $GUI->debug(dump(1111,'---',$LAYER,'---',$row->{layer_type}));
			#exit unless $ans eq 'ok';
			my $LAYER_TYPES = _parse_layer_types($LAYER,$row->{layer_type});
			#my $ans = $GUI->debug(dump($LAYER,'---',$LAYER_TYPES,'---',$row->{layer_type}));
			#exit unless $ans eq 'ok';
			next unless $LAYER_TYPES;
			#$GUI->debug(dump($row->{layer_type},   $LAYER_TYPES));
			my $content = eval($row->{content});
			if ($@) {
				print dump($row),"\n";
				print dump($@),"\n";
				print dump(ref($content)),"\n";
				print dump($content),"\n";
				$GEN->PAUSE('error');
				$GUI->msgbox(-icon=>'error',-text=>"[$row->{row_name}]在${LAYER}上执行错误!\n $@");
				exit;
			}
			if (ref($content) eq 'CODE'){
				$content = $content->(layer=>$LAYER,layer_stack=>$LAYER_STACK,layer_num=>$LAYER_NUM,layer_side=>$LAYER_SIDE,draw_layer=>$DRAW_LAYER);
			}
			if (ref($content) eq 'ARRAY' or ref($content) eq 'HASH'){
				$content = [$content] unless ref($content) eq 'ARRAY';
				foreach my $item (@$content){
					if ($item->{feat_type} ){
						push @feats,$item;
						next;
					}
					my $map_pads;
					$item->{layer_stack} = $LAYER_STACK unless defined $item->{layer_stack};
					#$item->{layer_flag} = 'S,E' unless $item->{layer_flag};
					$item->{layer_num} = $LAYER_NUM unless $item->{layer_num};
					if ($item->{side_flag}){
						if (uc($item->{side_flag}) eq 'T' or uc($item->{side_flag}) eq 'TOP'){
							next unless (uc($LAYER_SIDE) eq 'T' or uc($LAYER_SIDE) eq 'TOP');
						}
						else{
							next if (uc($LAYER_SIDE) eq 'T' or uc($LAYER_SIDE) eq 'TOP');
						}
					}
					if($item->{map_pads}){
						$map_pads = delete $item->{map_pads};
					}
					else{
						if (ref($item->{condition}) eq 'CODE'){
							$item->{condition} = $item->{condition}->(layer=>$LAYER,layer_stack=>$LAYER_STACK,layer_num=>$LAYER_NUM,layer_side=>$LAYER_SIDE,draw_layer=>$DRAW_LAYER);
							next if (defined $item->{condition} and $item->{condition} == 0);
						}
						$map_pads = GET_MAP_FEATS(%$item,deep_copy=>0);
					}
					
					next unless $map_pads;
					foreach my $map_pad (@$map_pads){
						my $tmp = $Func->deep_copy($item);
						foreach my $k (keys %$tmp){
							$tmp->{$k} = $tmp->{$k}->(layer=>$LAYER,layer_stack=>$LAYER_STACK,layer_num=>$LAYER_NUM,layer_side=>$LAYER_SIDE,draw_layer=>$DRAW_LAYER,map_pad_stk=>$map_pad->{stack},map_pad=>$map_pad) if ref($tmp->{$k}) eq 'CODE';
						}
						if ($tmp->{layer_flag}){
							next unless _parse_layer_flag($map_pad->{stack},$tmp->{layer_flag},$tmp->{layer_num});
						}
						if (defined $item->{text}){
							push @feats,map_feat_to_text(%$tmp,map_pad=>$map_pad);
						}
						elsif(defined $item->{xs}){
							push @feats,map_feat_to_line(%$tmp,map_pad=>$map_pad);
						}
						else{
							push @feats,map_feat_to_pad(%$tmp,map_pad=>$map_pad);
						}
					}
				}
			}
		}
		push @feats,MAP_TO_LAYER_FEATS(%par,parent_id=>$row->{id});
	}
	return @feats;
};


sub GET_MAP_FEATS{
	my %par = @_; #(map_flag => , layer_stack => ,deep_copy=>)
	#name,stack,tag
	$par{layer_stack} = $_TMP_LAYER_STK unless $par{layer_stack};
	$par{deep_copy} = 1 if (!defined $par{deep_copy});
	my ($m_name,$m_stack,$m_tag);
	if ($par{map_flag}){
		$par{map_flag} =~ s/\s+//g;
		($m_stack,$m_name,$m_tag) = split(':',$par{map_flag});
		if ($m_tag){
			my @tgs = split(/\s*(\(|\)|\&+|and|or|\|+)\s*/,$m_tag);
			foreach my $t (@tgs){
				if ($t =~ /^\&+$/){
					$t = 'and';
				}
				elsif ($t =~ /^\|+$/){
					$t = 'or';
				}
				elsif ($t =~ /^\s*([^ ]+)\s*\=\s*([^ ]+)\s*/){
					my ($n,$v) = ($1,$2);
					if ($v =~ /^\[(.*)\]$/){
						my $tvv = $1;
						my @vv = split(/\s*\,\s*/,$tvv);
						foreach my $tv (@vv){
							$tv = '$TAG->{'.$n.'} eq \'' . $tv . '\'';
						}
						$t = '('.join(' or ', @vv ).')';
					}
					else{
						$t = '$TAG->{' . $1 . '} eq \'' . $2 .'\'';
					}
				}
				else{
					$t = '$TAG->{' . $t . '}';
				}
			}
			$m_tag = join(' ',@tgs);
		}
	}
	if ($par{map_stack}){
		$m_stack = $par{map_stack};
	}
	if ($par{map_name}){
		$m_name = $par{map_name};
	}
	if ($par{map_tag}){
		$m_tag = $par{map_tag};
	}
	
	if ($m_stack){
		if (ref($m_stack) eq 'CODE'){
			$m_stack->(%par);
		}
	}
	else{
		$m_stack = 'S*';
	}
	
	my $stks;
	if (ref($m_stack) eq 'ARRAY'){
		foreach my $stk_flag (@$m_stack){
			my $t_stks = _parse_stack_flag($par{layer_stack},$stk_flag);
			foreach my $k (keys %$t_stks){
				$stks->{$k} = $t_stks->{$k};
			}
		}
	}
	elsif(ref($m_stack) eq 'HASH'){
		$stks = $m_stack;
	}
	else{
		$stks = _parse_stack_flag($par{layer_stack},$m_stack);
	}
	return () unless $MAP_DATA->{$m_name};
	my @map_feats;
	foreach my $map_stk (keys %{$MAP_DATA->{$m_name}}){
		next unless $stks->{$map_stk};
		foreach my $map_pad (@{$MAP_DATA->{$m_name}{$map_stk}}){
			my $TAG = $map_pad->{tag};
			if ($m_tag){
				if (ref($m_tag) eq 'CODE'){
					my $ret = $m_tag->(tag=>$TAG);
					next unless $ret;
				}
				else{
					my $ret = eval($m_tag);
					next unless $ret;
				}
			}
			push @map_feats,$map_pad;
		}
	}
	
	if ($par{deep_copy} and @map_feats){
		my $dc = $Func->deep_copy(\@map_feats);
		@map_feats = @$dc;
	}
	foreach my $item (@map_feats){
		$item->{TYPE} = 'MAP_FEAT';
	}
	if (wantarray){
		return @map_feats;
	}
	else{
		return \@map_feats;
	}
}

sub _EXEC_ROWS {
	my $row_type = shift;
	my $LAYER = shift;
	my $parent_id = shift || 0;
	my %row_types;
	foreach my $t (@$row_type,'Category'){
		$row_types{$t} = 1;
	}
	foreach my $row (@LAYER_ROWS){
		next if  $row->{parent_id} ne $parent_id;
		if ($row->{row_type} ne 'Category' and $row_types{$row->{row_type}}){
			if (defined $LAYER){
				my $LAYER_TYPES = _parse_layer_types($LAYER,$row->{layer_type});
				next unless $LAYER_TYPES;
			}
			my $content = eval($row->{content});
			if ($@) {
				$GUI->msgbox(-icon=>'error',-text=>"[$row->{row_name}]在${LAYER}上执行错误!\n $@");
				return;
			}
			if (ref($content) eq 'CODE'){
				$content->(layer=>$LAYER);
			}
		}
		_EXEC_ROWS($row_type,$LAYER,$row->{id});
	}
	return ;
};



sub map_feat_to_pad {
	my %par = @_;#(map_pad=>{x=>,y=>,angle=>,mirror=>})
	$par{nx} =  1 unless $par{nx};
	$par{ny} =  1 unless $par{ny};
	$par{dx} =  0 unless $par{dx};
	$par{dy} =  0 unless $par{dy};
	$par{sx} = 0 unless $par{sx};
	$par{sy} = 0 unless $par{sy};
	my @pads;
	foreach my $num_nx (1..$par{nx}){
		foreach my $num_ny (1..$par{ny}){
			#偏移
			my $pad;
			my ($dx_shift,$dy_shift) = (($num_nx-1)*$par{dx},($num_ny-1)*$par{dy});
			$pad->{feat_type} = 'pad';
			$pad->{x} = $par{map_pad}{x} + $par{sx} + $dx_shift;
			$pad->{y} = $par{map_pad}{y} + $par{sy} + $dy_shift;
			$pad->{angle} = $par{angle} || 0;
			$pad->{mirror} = $par{mirror} || 'no';
			$pad->{polarity} = $par{polarity} || 'positive';
			$pad->{symbol} = $par{symbol};
			$pad->{priority} = $par{priority};
			$pad->{num} = $_FEAT_NUM++;
			if ($par{attributes}){
				if (ref($par{attributes}) eq 'ARRAY'){
					push @{$pad->{attributes}},@{$par{attributes}};
				}
				else{
					push @{$pad->{attributes}},$par{attributes};
				}
			}
			push @{$pad->{attributes}},@{$par{map_pad}{attributes}};
			push @pads,$pad;
		}
	}
	return TL::GenMath->p_trans({x=>$par{map_pad}{x},y=>$par{map_pad}{y}},$par{map_pad}{angle},$par{map_pad}{mirror},0,0,@pads);
}

sub map_feat_to_line {
	my %par = @_;#(map_pad=>{x=>,y=>,angle=>,mirror=>})
	
	$par{nx} =  1 unless $par{nx};
	$par{ny} =  1 unless $par{ny};
	$par{dx} =  0 unless $par{dx};
	$par{dy} =  0 unless $par{dy};
	$par{xs} = 0 unless $par{xs};
	$par{ys} = 0 unless $par{ys};
	$par{xe} = 0 unless $par{xe};
	$par{ye} = 0 unless $par{ye};
	my @lines;
	foreach my $num_nx (1..$par{nx}){
		foreach my $num_ny (1..$par{ny}){
			#偏移
			my $line;
			my ($dx_shift,$dy_shift) = (($num_nx-1)*$par{dx},($num_ny-1)*$par{dy});
			$line->{feat_type} = 'line';
			$line->{xs} = $par{map_pad}{x} + $par{xs} + $dx_shift;
			$line->{ys} = $par{map_pad}{y} + $par{ys} + $dy_shift;
			$line->{xe} = $par{map_pad}{x} + $par{xe} + $dx_shift;
			$line->{ye} = $par{map_pad}{y} + $par{ye} + $dy_shift;
			$line->{polarity} = $par{polarity} || 'positive';
			$line->{symbol} = $par{symbol};
			$line->{priority} = $par{priority};
			$line->{num} = $_FEAT_NUM++;
			if ($par{attributes}){
				if (ref($par{attributes}) eq 'ARRAY'){
					push @{$line->{attributes}},@{$par{attributes}};
				}
				else{
					push @{$line->{attributes}},$par{attributes};
				}
			}
			push @{$line->{attributes}},@{$par{map_pad}{attributes}};
			push @lines,$line;
		}
	}
	foreach my $line (@lines){
		my ($ps,$pe) = TL::GenMath->p_trans({x=>$par{map_pad}{x},y=>$par{map_pad}{y}},$par{map_pad}{angle},$par{map_pad}{mirror},0,0,{x=>$line->{xs},y=>$line->{ys}},{x=>$line->{xe},y=>$line->{ye}});
		($line->{xs},$line->{ys}) = ($ps->{x},$ps->{y});
		($line->{xe},$line->{ye}) = ($pe->{x},$pe->{y});
	}
	return @lines;
}

sub map_feat_to_text{
	my %par = @_;
	$par{sx} = 0 unless $par{sx};
	$par{sy} = 0 unless $par{sy};
	my $text;
	$text->{feat_type} = 'text';
	$text->{x} = $par{map_pad}{x} + $par{sx};
	$text->{y} = $par{map_pad}{y} + $par{sy};
	$text->{angle} = $par{angle} || 0;
	$text->{mirror} = $par{mirror} || 'no';
	$text->{polarity} = $par{polarity} || 'positive';
	$text->{text}  = $par{text};
	$text->{x_size}   = $par{x_size} || 0.15;
	$text->{y_size}   = $par{y_size} || 0.15;
	$text->{line_width}   = $par{line_width} || 10;
	$text->{anchor}       = $par{anchor} || 'center';
	$text->{trans_anchor} = $par{trans_anchor} ;
	$text->{fontname}     = $par{fontname} || $PAR->{font};
	$text->{type}         = $par{type};
	$text->{text_length}  = $par{text_length};
	$text->{priority} = $par{priority};
	$text->{num} = $_FEAT_NUM++;
	if ($par{attributes}){
		if (ref($par{attributes}) eq 'ARRAY'){
			push @{$text->{attributes}},@{$par{attributes}};
		}
		else{
			push @{$text->{attributes}},$par{attributes};
		}
	}
	push @{$text->{attributes}},@{$par{map_pad}{attributes}};
	my ($tmp) = TL::GenMath->p_trans({x=>$par{map_pad}{x},y=>$par{map_pad}{y}},$par{map_pad}{angle},'no',0,0,$text);
	my $mirror;
	if ($par{map_pad}{mirror} eq 'yes'){
		if ($tmp->{mirror} eq 'no'){
			$mirror = 'yes';
		}
		else{
			$mirror= 'no';
		}
	}
	else{
		$mirror = $tmp->{mirror};
	}
	
	if ($mirror eq 'yes' or $mirror eq 'x') {
		if ($tmp->{angle} == 90 or $tmp->{angle} == 270){
			$tmp->{angle} += 180;
		}
	}
	elsif($mirror eq 'y'){
		if ($tmp->{angle} == 0 or $tmp->{angle} == 180){
			$tmp->{angle} += 180;
		}
	}
	$tmp->{angle} = $tmp->{angle} % 360;
	$tmp->{mirror} = (!$mirror or $mirror eq 'no')? 'no' : 'yes';
	
	return ($tmp);
	
	
}

sub GET_FLAG_STACK{
	my ($stk,$stk_flag) = @_;
	my $stks = _parse_stack_flag($stk,$stk_flag);
	my @retstks = sort {$S->{$b}{stack_level} <=> $S->{$a}{stack_level} ||$S->{$a}{start_num} <=> $S->{$a}{end_num}} keys %$stks;
	if (wantarray){
		return @retstks;
	}
	else{
		return $retstks[0];
	}
}


sub _parse_layer_flag{
	my ($stk,$layer_flag,$layer_number) = @_;
	$layer_flag =~ s/\s+//g;
	$layer_flag = 'S,E' if $layer_flag eq 'SE';
	my $stack = $S->{$stk};
	my $stk_start_num = $stack->{top_layer_num} || 1;
	my $stk_end_num = $stack->{bot_layer_num} || $P->{layer_count};
	my @list = split(/\,+/,$layer_flag);
	foreach my $item (@list){
		my ($from,$to);
		if ($item =~ /^([^.]*)\.+([^.]*)$/){
			($from,$to) = ($1,$2);
		}
		else{
			$from = $to = $item;
		}
		$from = 1 unless $from;
		$to = $P->{layer_count} unless $to;
		foreach my $flag ($from,$to){
			if ($flag =~ /^(S|E)([+-]?\d+)?/){
				my ($se,$n) = ($1,$2); 
				$n = 0 unless $n;
				if ($se eq 'S'){
					$flag = $stk_start_num - $n;
				}
				else{
					$flag = $stk_end_num + $n;
				}
			}
		}
		return 1 if ($layer_number >= $from and $layer_number <= $to);
	}
	return 0;
}


#获取Stack中第几层的层号
sub GET_STK_NUM_LAYER{
	my ($stk,$num) = @_;
	return unless $S->{$stk};
	my $stack = $S->{$stk};
	if ($num == 1){
		return $stack->{start_num};
	}
	elsif($num == -1){
		return $stack->{end_num};
	}
	elsif($num > 1){
		my $layer_num = int($stack->{start_num}) + $num - 1;
		return if $layer_num > $stack->{end_num};
		return $layer_num;
	}
	elsif($num < 0){
		my $layer_num = int($stack->{end_num}) - $num + 1;
		return if $layer_num < $stack->{start_num};
		return $layer_num;
	}
}

#获取层位于压合中的第几层
sub GET_LAYER_STK_NUM {
	my ($layer_num,$stk) = @_;
	return unless $S->{$stk};
	my $stack = $S->{$stk};
	my $lyr_num = int($layer_num);
	return if ($lyr_num < int($stack->{start_num}) or $lyr_num > int($stack->{end_num}));
	my $na = $lyr_num - int($stack->{start_num}) + 1;
	my $nb = $lyr_num - int($stack->{end_num}) - 1;
	if (wantarray){
		return ($na,$nb);
	}
	else{
		return $na;
	}
}

sub _parse_stack_flag{
	my ($stk,$stk_flag) = @_;
	my %stks;
	my $invert;
	$stk_flag = 'S*' unless $stk_flag;
	if ($stk_flag =~ /^\!/){
		$stk_flag =~ s/^\!//;
		$invert = 1;
	}
	if ($stk_flag eq 'S'){
		$stks{$stk} = 1;
	}
	elsif($stk_flag eq 'SO' or $stk_flag eq 'SE'){
		$stks{1 . '-' . $P->{layer_count}} = 1;
	}
	elsif($stk_flag eq 'S-'){
		foreach my $s (@{$S->{$stk}{all_sub_stacks}}){
			$stks{$s} = 1;
		}
	}
	elsif($stk_flag eq 'S+'){
		foreach my $s (@{$S->{$stk}{all_parent_stacks}}){
			$stks{$s} = 1;
		}
	}
	elsif($stk_flag eq 'S-1'){
		foreach my $s (@{$S->{$stk}{sub_stacks}}){
			$stks{$s} = 1;
		}
	}
	elsif($stk_flag eq 'S+1'){
		if ($S->{$stk}{parent_stack}){
			$stks{$S->{$stk}{parent_stack}} = 1;
		}
	}
	elsif($stk_flag eq 'S-T'){
		if (@{$S->{$stk}{sub_stacks}}){
			$stks{$S->{$stk}{sub_stacks}[0]} = 1;
		}
	}
	elsif($stk_flag eq 'S-B'){
		if (@{$S->{$stk}{sub_stacks}}){
			$stks{$S->{$stk}{sub_stacks}[-1]} = 1;
		}
	}
	elsif($stk_flag eq 'S*'){
		foreach my $s (keys %$S){
			$stks{$s} = 1;
		}
	}
	elsif($stk_flag =~ /^S(\d+\-\d+)$/){
		$stks{$1} = 1;
	}
	if ($invert){
		my %tmp;
		foreach my $s (keys %$S){
			$tmp{$s} = 1 unless $stks{$s};
		}
		return \%tmp;
	}
	else{
		return \%stks;
	}
}

sub CREATE_LAYER_OVERLAY{
	my %par = @_; #(layer=>'',draw_layer=>,invert=>)
	my $LAYER = $par{layer};
	my $LAYER_STACK = $par{layer_stack};
	my $LAYER_NUM = $par{layer_num};
	my $LAYER_SIDE = $par{layer_side};
	my $DRAW_LAYER = $par{draw_layer};
	$DRAW_LAYER= $par{layer} unless $DRAW_LAYER;
	#$GEN->openStep(job=>$Job,name=>$Step);
	$GEN->affectedLayer(affected=>'yes',mode=>'single',layer=>[$DRAW_LAYER],clear_before=>'yes');
	$GEN->COM('sel_all_feat');
	$GEN->selDelete();
	my @feats = MAP_TO_LAYER_FEATS(%par);
	#$GEN->PAUSE("feats === \n ".dump(\@feats));
	ADD_FEATS_ON_LAYER($DRAW_LAYER,\@feats,invert=>$par{invert});
}

sub ADD_FEATS_ON_LAYER{
	my ($DRAW_LAYER,$FEATS,%par) = @_;
	#$GEN->openStep(job=>$Job,name=>$Step);
	$GEN->affectedLayer(affected=>'yes',mode=>'single',layer=>[$DRAW_LAYER],clear_before=>'yes');
	$GEN->selDelete() if $GEN->getSelectCount();
	my $polarity_invert = {'positive'=>'negative','negative'=>'positive'};
	foreach my $feat (sort{$b->{priority} <=> $a->{priority} || $a->{num} <=> $b->{num}} @$FEATS){
		my %tmp = %$feat;
		my $type = delete $tmp{feat_type};
		delete $tmp{priority}; delete $tmp{num};
		$tmp{polarity} = 'positive' unless $tmp{polarity};
		if ($par{invert}){
			$tmp{polarity} = $polarity_invert->{$tmp{polarity}};
		}
		if ($type eq 'pad'){
			$GEN->addPad(%tmp);
		}
		elsif($type eq 'text'){
			$GEN->addText(%tmp);
		}
		elsif($type eq 'line'){
			$GEN->addLine(%tmp)
		}
		elsif($type eq 'rect'){
			$GEN->addRectangle(%tmp);
		}
		elsif($type eq 'poly'){
			$GEN->addPolygon(%tmp);
		}
	}
	$GEN->clearLayers();
	$GEN->affectedLayer(mode=>'all',affected=>'no'); 
}

sub PANEL_MAP_TO_DATA{
	my %par = @_;
	$par{units} = 'inch' if(!defined($par{units}));
	my $MapData;
	foreach my $layer (@{$par{layers}}){
		next unless ($GEN->isLayerExists(job=>$par{job},layer=>$layer));
		my @feats = $GEN->getFeatures(job=>$par{job},step=>$par{step},layer=>$layer,units=>$par{units});
		foreach my $item (@feats){
			next if $item->{type} ne 'pad';
			foreach my $attr (@{$item->{attributes}}){
				next if($attr !~ /^tl_string\=(.*)/);
				$attr = $1;
				next if($attr !~ /^S/);
				my @attrs = split(':',$attr);
				my $stack = shift @attrs;
				$stack =~ s/^S//;
				my $name = shift @attrs;
				my %tags;
				foreach my $tag (@attrs){
					if ($tag =~ /^(.*)\=(.*)$/){
						$tags{$1} = $2;
					}
					else{
						$tags{$tag} = 1;
					}
				}
				
				push @{$MapData->{$name}{$stack}},{
					attributes => [{attribute=>'tl_string',text=>$attr}],
					x => $item->{x},
					y => $item->{y},
					symbol => $item->{symbol},
					angle => $item->{angle},
					mirror => $item->{mirror},
					stack => $stack,
					name => $name,
					tag => \%tags
				}
			}
		}
	}
	return $MapData;
}

sub mm2mil{
	my $mm = shift;
	return $mm*100/2.54;
}

sub mm2inch{
	my $mm = shift;
	return $mm*100/2.54/1000;
}

sub mil2inch{
	my $mil = shift;
	return $mil/1000;
}

sub inch2mil{
	my $mil = shift;
	return $mil*1000;
}

sub inch2mm{
	my $inch = shift;
	return 25.4*$inch;
}


sub MAX{
	my @sizes = @_;
	my $size;
	foreach my $s (@sizes){
		$size = $s if (!defined $size or $size < $s);
	}
	return $size;
}

sub MIN{
	my @sizes = @_;
	my $size;
	foreach my $s (@sizes){
		$size = $s if (!defined $size or $size > $s);
	}
	return $size;
}