=head
 NAME: 
 DESCRIPTION: Run Map
 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 : 'change_standard_symbol',
			title : '转为自定义Symbol',
			type : 'ComboBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'Yes',text:'Yes'},
					{name:'No',text:'No'},
				],
				tool_tip:'因标准symbol在旋转后会失去角度信息,所以需转为自定义symbol,默认为yes'
			},
			pack : {row:1,column:1},
		},
		{
			name : 'condition',
			title : '全部添加',
			type : 'ComboBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'Yes',text:'Yes'},
					{name:'No',text:'No'},
				],
				tool_tip:'全部添加如果是YES 所有SYMBOL添加出来(仅调试使用,将忽略symbol的添加条件的判断),默认为No'
			},
			pack : {row:1,column:1},
		},
		{
			name : 'debug_priority',
			title : '调试等级',
			type : 'SpinBox',
			pack : {row:1,column:1},
			property : {
				tool_tip:'symbol的优先级小于设定调试等级的,将不会添加出来,默认为0'
			}
		},
		{
			name : 'debug_add_size_symbol',
			title : '调试-添加大小Symbol',
			type : 'ComboBox',
			property : {
				size_policy:'Expanding,Fixed',
				item_list:[
					{name:'Yes',text:'Yes'},
					{name:'No',text:'No'},
				],
				tool_tip:'设定为yes,将按图形symbol->孔symbol->map symbol的优先级顺序添加,否则直接添加map symbol,默认为No'
			},
			pack : {row:1,column:1},
		},
		{
			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> Overlay Run map </p>
	</body></html>
=cut
#################################################################################################################
#################################################################################################################
use strict;
use utf8;
use JSON;
use Encode;
use Data::Dump 'dump';
use Number::Format 'round';
use_module('TL_GenMath');
#
my $Json = new JSON;
my $Math = TL::GenMath->new();
my ($Job,$Step,$Return) = ($JOB,undef,'finish');
my (@Report,$M,$S,$P,$L,$D);
my ($SIZE_DATA,$MAP_DATA);#SYMBOL、计算模块、计算模型
my $CALC_COUNT;
my (%SymbolExists,@MapOkPads,@MapFailPads,$isSymbolExitsFilter);
#
$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->{change_standard_symbol} = 'Yes' if(!defined($PAR->{change_standard_symbol}));
$PAR->{condition}  = 'No' if(!defined($PAR->{condition}));
$PAR->{debug_priority}  = 0 if(!defined($PAR->{debug_priority}));
$PAR->{debug_add_size_symbol}  = "No" if(!defined($PAR->{debug_add_size_symbol}));
$PAR->{save_job} = 'No' unless $PAR->{save_job};
$PAR->{safe_dist}  = 0.05  if(!defined($PAR->{safe_dist}));
$PAR->{map_layer} = 'pnl-map' unless $PAR->{map_layer};
$PAR->{map_layer_error} = 'pnl-map-error' unless $PAR->{map_layer_error};
#
my $JOB_PATH = $GEN->getJobPath(job=>$Job);
#################################################################################################################
#################################################################################################################
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;
	
	##
	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{
		my @version = $GUI->select_from_treeview(
			-headstock=>'tl-question',
			-defaultsize=>[200,300],
			-selectmode=>'single',
			-title=>__('Please Select Version'),
			-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);
		$version = $version[0];
	}
	
	##
	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});
	
	##
	update_loading("解析板框代码并确认配置信息,请稍候..",0,position=>'n');
	my $tmp_db_map_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"],
		-order=>"version ASC,parent_id ASC,flow_order ASC",
		-where => {version=>$version,active=>1,category=>$panel_category,row_type=>['Category','Public','Map Header','Map Symbol','Map Footer']},
	);
	foreach my $item (@$tmp_db_map_data){
		$item->{avoid_hole_class} = eval($item->{avoid_hole_class});
		$item->{avoid_pattern_class} = eval($item->{avoid_pattern_class});
		$item->{map_class}   = eval($item->{map_class});
		my %map_class;
		if ($item->{map_class}){
			foreach my $cls (@{$item->{map_class}}){
				$map_class{$cls} = 1;
			}
		}
		$item->{map_class} = \%map_class;
	}
	
	my @map_rows;
	my @map_footer_rows;
	my $row_num = 0;
	my $sub_execute_db_map_data;
	$sub_execute_db_map_data = sub{
		my $db_data = shift;
		foreach my $row (@$db_data){
			$row->{_row_num} = ++$row_num;
			if ($row->{row_type} eq 'Public'){
				$row->{priority} = 9999;
			}
			elsif($row->{row_type} eq 'Map Header'){
				$row->{priority} = 8888;
			}
			elsif($row->{row_type} eq 'Map Footer'){
				$row->{priority} = -8888;
			}
			if ($row->{row_type} eq 'Map Footer'){
				push @map_footer_rows,$row;
			}
			elsif ($row->{row_type} ne 'Category'){
				push @map_rows,$row;
			}
			
			$sub_execute_db_map_data->([grep{$_->{parent_id} eq $row->{id}} @$tmp_db_map_data]);
		}
	};
	$sub_execute_db_map_data->([grep{!($_->{parent_id})} @$tmp_db_map_data]);
	
	my $row_count = scalar(@map_rows) + 2;
	my $row_n = 0;
	foreach my $row (sort{$b->{priority} <=> $a->{priority} || $a->{_row_num} <=> $b->{_row_num}} @map_rows){
		#条件
		next if ($PAR->{debug_priority} and $row->{priority} < $PAR->{debug_priority});
		my $disp_name = $row->{row_name}; $disp_name =~ s/\n/ /g;
		update_loading('正在计算['.$disp_name.']...',($row_n++)/$row_count);
		my $condition = eval($row->{condition});
		if ($@) {
			$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]条件失败! \n $@");
			return "Error";
		}
		$condition = $condition->(%$row) if($condition and ref($condition) eq "CODE");
		next unless((!defined($condition) or $condition == 1) or $PAR->{condition} eq "Yes");
		#计算
		my $calculate_pads;
		$calculate_pads  = eval($row->{content});
		if ($@) {
			$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]坐标失败! \n  $@");
			return "Error";
		}
		if (ref($calculate_pads) eq 'CODE'){
			eval{
				$calculate_pads = $calculate_pads->(%$row);
			};
			if ($@) {
				$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]坐标失败! \n  $@");
				return "error";
			}
		}
		PUSH_MAP_DATA($calculate_pads,$row);
	}
	$GEN->openStep(job=>$Job,name=>$Step);
	$GEN->units(type=>$PAR->{units});
	
	$PAR->{map_layer} = 'pnl-map' unless $PAR->{map_layer};
	$PAR->{map_layer_error} = 'pnl-map-error' unless $PAR->{map_layer_error};
	$GEN->deleteLayer(job=>$Job,layer=>[$PAR->{map_layer_error}]);
	$GEN->createLayer(job=>$Job,layer=>$PAR->{map_layer},context=>'misc',type=>'signal') unless ($GEN->isLayerExists(job=>$Job,layer=>$PAR->{map_layer}));;
	$GEN->clearLayers();
	$GEN->affectedLayer(mode=>'all',affected=>'no');
	$GEN->affectedLayer(affected=>'yes',mode=>'single',layer=>[$PAR->{map_layer}],clear_before=>'yes');
	$GEN->COM('sel_all_feat');
	$GEN->selDelete() if $GEN->getSelectCount();
	show_loading('正在pnl-map层添加Symbol...',0,position=>'n');
	my $pad_count = scalar(@MapOkPads) + scalar(@MapFailPads) + 2;
	my $pad_n = 1;
	foreach my $pad (@MapOkPads){
		update_loading('正在'.$PAR->{map_layer}.'层添加Symbol['.$pad->{symbol}.']'."  $pad_n/$pad_count",$pad_n/$pad_count);
		$GEN->addPad(%$pad);
		$pad_n++;
	}
	$GEN->workLayer(name=>$PAR->{map_layer},display_number=>1,clear_before=>'yes');
	if (@MapFailPads){
		$GEN->createLayer(job=>$Job,layer=>$PAR->{map_layer_error},context=>'misc',type=>'signal') unless ($GEN->isLayerExists(job=>$Job,layer=>$PAR->{map_layer_error}));;
		$GEN->affectedLayer(affected=>'yes',mode=>'single',layer=>[$PAR->{map_layer_error}],clear_before=>'yes');
		$GEN->COM('sel_all_feat');
		$GEN->selDelete() if $GEN->getSelectCount();
	
		foreach my $pad (@MapFailPads){
			update_loading('正在'.$PAR->{map_layer_error}.'层添加Symbol['.$pad->{symbol}.']'."  $pad_n/$pad_count",$pad_n/$pad_count);
			$GEN->addPad(%$pad);
			$pad_n++;
		}
		$GEN->workLayer(name=>$PAR->{map_layer_error},display_number=>1,clear_before=>'yes');
		$GEN->displayLayer(name=>$PAR->{map_layer},number=>2);
	}
	
	update_loading("pnL-map层创建完成,脚本即将结束..",0,position=>'n');
	foreach my $row (sort{$b->{priority} <=> $a->{priority} || $a->{_row_num} <=> $b->{_row_num}} @map_footer_rows){
		my $disp_name = $row->{row_name}; $disp_name =~ s/\n/ /g;
		my $condition = eval($row->{condition});
		if ($@) {
			$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]条件失败! \n $@");
			return "Error";
		}
		$condition = $condition->(%$row) if($condition and ref($condition) eq "CODE");
		next unless((!defined($condition) or $condition == 1) or $PAR->{condition} eq "Yes");
		#计算
		my $calculate_pads;
		$calculate_pads  = eval($row->{content});
		if ($@) {
			$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]坐标失败! \n  $@");
			return "Error";
		}
		if (ref($calculate_pads) eq 'CODE'){
			eval{
				$calculate_pads->(%$row);
			};
			if ($@) {
				$GUI->msgbox(-icon=>'error',-text=>"载入[$disp_name]坐标失败! \n  $@");
				return "error";
			}
		}
	}
	
	##
	unless( @MapFailPads or $PAR->{map_error} ){
		$GEN->deleteLayer(job=>$Job,step=>$Step,layer=>$PAR->{map_layer_error});
	}
	
	hide_loading();
	$GUI->msgbox(-text=>"请确认 $PAR->{map_layer} 层中的Symbol位置,并手动在 $PAR->{map_layer_error} 修正好!") if ( @MapFailPads or $PAR->{map_error} );
	
	my $info_file = $JOB_PATH.'/user/PNL_map_info';
	open(my $fh,'>',$info_file);
	print $fh dump({P=>$P,L=>$L,D=>$D,S=>$S});
	close($fh);
	##报告
	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->workLayer(name=>,number=>1,clear_before=>'yes');
};
#################################################################################################################
#################################################################################################################
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 PUSH_MAP_DATA{
	my ($calculate_pads,$item) = @_;
	return unless ref($calculate_pads) eq 'ARRAY';
	#return unless defined $item->{name};
	my @size_pads;
	foreach my $pad (@$calculate_pads){
		$pad->{name} = $item->{symbol_name} unless $pad->{name};
		$pad->{class} = $item->{map_class} unless $pad->{class};
		if ($pad->{class} and $pad->{class}{'Pattern'}){
			$pad->{pattern_symbol} = $item->{pattern_symbol} || $pad->{hole_symbol} || $item->{hole_symbol} unless $pad->{pattern_symbol};
		}
		else{
			delete $pad->{pattern_symbol};
		}
		
		if ($pad->{class} and $pad->{class}{'Hole'}){
			$pad->{hole_symbol} = $item->{hole_symbol} || $pad->{pattern_symbol} || $item->{pattern_symbol} unless $pad->{hole_symbol};
		}
		else{
			delete $pad->{hole_symbol};
		}
		$pad->{map_symbol} = $item->{map_symbol} || $pad->{pattern_symbol} || $pad->{hole_symbol} unless $pad->{map_symbol};
		unless (defined $pad->{cannot_move}){
			if ($item->{priority} >= 99){
				$pad->{cannot_move} = 1;
			}
		}
		$pad->{stack} = '1-'.$P->{layer_count} unless $pad->{stack};
		unless($pad->{map_symbol}){
			print dump($pad),"\n";
		}
		if( (! $SymbolExists{$pad->{map_symbol}}) and $pad->{map_symbol} ne 'NO' and
			($pad->{map_symbol} !~ /^(rect|oval|r|s|rc_ths)\d+/) and 
			(! _isSymbolExists($pad->{map_symbol})))
		{
			$pad->{map_symbol} = $pad->{pattern_symbol} || $pad->{hole_symbol};
		}
		if ($PAR->{change_standard_symbol} eq 'Yes'){
			if ($pad->{map_symbol} =~ /^(rect|oval|r|s)\d+/){
				my $pnl_sym  = 'pnl_' . $pad->{map_symbol};
				unless ($SymbolExists{$pnl_sym} or _isSymbolExists($pnl_sym)){
					_create_symbol($pnl_sym,$pad->{map_symbol});
				}
				$pad->{map_symbol}  = $pnl_sym;
			}
		}
		$SymbolExists{$pad->{map_symbol}} = 1;
		
		my @pad_tag = ('S'.($pad->{stack}) , $pad->{name});
		
		
		if ($pad->{tag}){
			if (ref($pad->{tag}) eq 'ARRAY'){
				foreach my $k (@{$pad->{tag}}){
					push @pad_tag,$k;
				}
			}
			elsif(ref($pad->{tag}) eq 'HASH'){
				foreach my $k (sort keys %{$pad->{tag}}){
					my $v = $pad->{tag}{$k};
					if (defined $v){
						push @pad_tag,$k.'='.$v;
					}
					else{
						push @pad_tag,$k;
					}
				}
			}
			else{
				push @pad_tag,$pad->{tag};
			}
		}
		
		if ($pad->{size_shiftx}){
			push @pad_tag , 'sx='.$pad->{size_shiftx}; 
		}
		if ($pad->{size_shifty}){
			push @pad_tag , 'sy='.$pad->{size_shifty};
		}
		
		push @pad_tag , 'hs=' . $pad->{hole_symbol} if $pad->{hole_symbol};
		push @pad_tag , 'ps=' . $pad->{pattern_symbol} if $pad->{pattern_symbol};
		
		my $pad_attr = [{attribute=>'tl_string',text=>join(':',@pad_tag)}];
		#push @$pad_attr,$PAR->{can_not_move_attr} if $pad->{cannot_move};
		my $map_symbol =  $pad->{map_symbol};
		if ($PAR->{debug_add_size_symbol} eq 'Yes'){
			$map_symbol = $pad->{pattern_symbol} || $pad->{hole_symbol} || $pad->{map_symbol};
		}
		
		my $map_pad = {
			attributes=>$pad_attr,
			x => $pad->{x},
			y => $pad->{y},
			angle=> $pad->{angle} || 0,
			symbol=> $map_symbol ,
			mirror => $pad->{mirror}
		};
		
		my $size_pad = {
			x => $pad->{x},
			y => $pad->{y},
			stack => $pad->{stack},
			hole_symbol => $pad->{hole_symbol},
			pattern_symbol => $pad->{pattern_symbol} ,
			angle => $pad->{angle},
			class => $pad->{class},
			name => $pad->{name},
		};
		
		push @{$MAP_DATA->{$pad->{name}}{$pad->{stack}}} , $pad;
		
		$size_pad = _shift_size_pad(pad=>$size_pad,shiftx=>$pad->{size_shiftx},shifty=>$pad->{size_shifty});
		$size_pad->{angle} = $pad->{angle};
		if ($pad->{failed}){
			push @MapFailPads,$map_pad unless $map_pad->{symbol} eq 'NO';
		}
		else{
			push @MapOkPads,$map_pad unless $map_pad->{symbol} eq 'NO';
			push @{$SIZE_DATA->{$pad->{name}}},$size_pad;
		}
	}
}

=h
	如果以前检查过了那就不检查第二次
=cut
sub _isSymbolExists{
	my $symbol = shift;
	if(!defined($isSymbolExitsFilter->{$symbol})){
		$isSymbolExitsFilter->{$symbol} = 0;
		if($GEN->isSymbolExists(job=>'genesislib',symbol=>$symbol) or $GEN->isSymbolExists(job=>$Job,symbol=>$symbol)){
			$isSymbolExitsFilter->{$symbol} = 1;
		}
	}
	return $isSymbolExitsFilter->{$symbol};
}

sub GET_AVOID_FEATS {
	my %par = @_; #(stack=>'1-8',class=>['S*:*','S-:grp'],name=>[],size_type =>'pattern'|'hole',range=>{x1=>,x2=>,y1=>,y2=>})
	$par{size_type} = 'pattern' unless $par{size_type};
	if (ref($par{class}) eq 'HASH'){
		my @class;
		foreach my $k (sort keys %{$par{class}}){
			push @class,$k;
		}
		$par{class} = \@class;
	}
	my @avoid_feats;
	if ($par{class}){
		foreach my $name (keys %$SIZE_DATA){
			foreach my $class (@{$par{class}}){
				my ($stk_flag,@cls) = split(':',$class);
				my $ivt = 0; my $all = 0;
				if ($cls[0] =~ /^\!/){
					$ivt = 1;
					$cls[0] =~ s/^\!//;
				}
				$all = 1 if (! $cls[0] or $cls[0] eq '*');
				my $stks = _parse_stack_flag($par{stack},$stk_flag) if $stk_flag;
				foreach my $pad (@{$SIZE_DATA->{$name}}){
					next unless $pad->{$par{size_type}.'_symbol'};
					if (!$stks or ($stks and $stks->{$pad->{stack}})){
						if ($all){
							push @avoid_feats,{
								x      => $pad->{x},
								y      => $pad->{y},
								symbol => $pad->{$par{size_type}.'_symbol'},
								angle  => $pad->{angle},
								name   => $name,
								stack  => $pad->{stack},
							} if $pad->{$par{size_type}.'_symbol'};	
						}
						else{
							if ($ivt){
								my $ok = 1;
								foreach my $_cls (@cls){
									if ($pad->{class}{$_cls}){
										$ok = 0; last;
									}
								}
								push @avoid_feats,{
									x      => $pad->{x},
									y      => $pad->{y},
									symbol => $pad->{$par{size_type}.'_symbol'},
									angle  => $pad->{angle},
									name   => $name,
									stack  => $pad->{stack},
								} if ($ok and $pad->{$par{size_type}.'_symbol'});	
							}
							else{
								my $ok = 0;
								foreach my $_cls (@cls){
									if ($pad->{class}{$_cls}){
										$ok = 1; last;
									}
								}
								push @avoid_feats,{
									x      => $pad->{x},
									y      => $pad->{y},
									symbol => $pad->{$par{size_type}.'_symbol'},
									angle  => $pad->{angle},
									name   => $name,
									stack  => $pad->{stack},
								} if ($ok and $pad->{$par{size_type}.'_symbol'});	
							}
						}
					}
				}
				
				
			}
		}
	}
	
	if ($par{name}){
		foreach my $name (keys %$SIZE_DATA){
			foreach my $nam (@{$par{name}}){
				my ($stk_flag,$nm) = split(':',$nam);
				next unless $nm eq $name;
				if ($stk_flag){
					my $stks = _parse_stack_flag($par{stack},$stk_flag);
					foreach my $pad (@{$SIZE_DATA->{$name}}){
						if ($stks->{$pad->{stack}}){
							push @avoid_feats,{
								x      => $pad->{x},
								y      => $pad->{y},
								symbol => $pad->{$par{size_type}.'_symbol'},
								angle  => $pad->{angle},
								name   => $name,
								stack  => $pad->{stack},
							} if ($pad->{$par{size_type}.'_symbol'});
						}
					}
				}
				else{
					foreach my $pad (@{$SIZE_DATA->{$name}}){
						push @avoid_feats,{
							x      => $pad->{x},
							y      => $pad->{y},
							symbol => $pad->{$par{size_type}.'_symbol'},
							angle  => $pad->{angle},
							name   => $name,
							stack  => $pad->{stack},
						} if ($pad->{$par{size_type}.'_symbol'});	
					}
				}
			}
		}
	}
	
	
	if ($par{exclude_name}){
		my @tmp;
		my %flg;
		foreach my $nam (@{$par{exclude_name}}) {
			my ($stk_flag,$nm) = split(':',$nam);
			unless($stk_flag){
				$stk_flag = 'S*';
				$nm = $nam;
			}
			if ($stk_flag){
				my $stks = _parse_stack_flag($par{stack},$stk_flag);
				foreach my $s (keys %$stks){
					$flg{$s.':'.$nm} = 1;
				}
			}
		}
		
		foreach my $p (@avoid_feats) {
			my $k = $p->{stack}.':'.$p->{name};
			push @tmp , $p unless  $flg{$k};
		}
		
		@avoid_feats = @tmp;
	}
	
	if ($par{range}){
		$par{range} = [$par{range}] unless ref($par{range}) eq 'ARRAY';
		my @tmp;
		foreach my $area (@{$par{range}}){
			my $xmin = (exists $area->{x1}) ? $area->{x1} : $area->{xmin};
			my $xmax = (exists $area->{x2}) ? $area->{x2} : $area->{xmax};
			my $ymin = (exists $area->{y1}) ? $area->{y1} : $area->{ymin};
			my $ymax = (exists $area->{y2}) ? $area->{y2} : $area->{ymax};
			($xmin,$xmax) = sort{$a <=> $b} sort ($xmin,$xmax);
			($ymin,$ymax) = sort{$a <=> $b} sort ($ymin,$ymax);
			foreach my $feat (@avoid_feats) {
				my ($l,$s) = $feat->{symbol} =~ /(\d+\.?\d*)/g;
				my $sum = ($l + $s)/1000;
				if (TL::GenMath->is_range_intersect({min=>$xmin,max=>$xmax},{min=>$feat->{x} - $sum,max=>$feat->{x} + $sum}) or
					TL::GenMath->is_range_intersect({min=>$ymin,max=>$ymax},{min=>$feat->{y} - $sum,max=>$feat->{y} + $sum}))
				{
					push @tmp,$feat;
				}
			}
		}
		return \@tmp;
	}
	else{
		return \@avoid_feats;
	}
}


sub _parse_stack_flag{
	my ($stk,$stk_flag) = @_;
	my %stks;
	my $invert;
	$stk_flag = 'S*' unless $stk_flag;
	if ($stk =~ /^\!/){
		$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 GET_MAP_FEATS2{
	my @pars = @_;
	my @return;
	foreach my $p (@pars){
		push @return , (GET_MAP_FEATS(%$p));
	}
	if (wantarray){
		return @return;
	}
	else{
		return \@return;
	}
	
}

sub GET_MAP_FEATS{
	my %par = @_; #(map_flag => , layer_stack => ,deep_copy=>)
	#name,stack,tag
	$par{layer_stack} = '1-'.$P->{layer_count} 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 = $IKM->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 _create_symbol{
	my ($symbol,$pad_sym) = @_;
	$GEN->createSymbol(job=>$Job,name=>$symbol);
	$GEN->openSymbol(job=>$Job,name=>$symbol);
	$GEN->units(type=>$PAR->{units} || 'inch');
	$GEN->addPad(x=>0, y=>0, symbol=>$pad_sym);
	$GEN->closeSymbol();
	$GEN->openStep(job=>$Job,name=>$Step);
}


sub _shift_size_pad{
	my %par = @_; #pad=>,shiftx=>,shift=>
	my $pad = $par{pad};
	my ($point) = TL::GenMath->p_trans({x=>$pad->{x},y=>$pad->{y}},0,'no',$par{shiftx},$par{shifty},$pad);
	return $point;
}

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;
}	

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;
}