procedure XXX_3f;
{ 選択した線の重複をチェックして、不要な線を削除します。 }
{ 部分的に重なっている線も1本にまとめます。 }
{ 2011/05/06 与太郎 }
{ 2011/05/08 修正 (2) }
{ 2011/05/12 修正 (2a) }
{ 2011/05/13 修正 (3) }
{ 2011/05/15 修正 (3a) }
{ 2011/05/15 修正 (3b) }
{ 2011/05/20 修正 (3c) }
{ 2011/05/22 修正 (3d) }
{ 2011/05/22 修正 (4) 2次元配列版 }
{ 2011/05/26 修正 (4a) 2次元配列版 }
{ 2011/05/26 修正 (5) 2次元配列版(メモリ節約) }
{ 2011/05/29 修正 (5a) 2次元配列版(メモリ節約) }
{ 2011/05/30 修正 (6) 属性をテーブル化 }
{ 2011/06/09 修正 (3e) IsOverlapped手続きの修正 }
{ 2011/06/09 修正 (6a) IsOverlapped手続きの修正 }
{ 2011/06/20 修正 (3f) GetUnion手続きの修正 }
{$ DEBUG}
label
9999;
const
{ プラグイン用パラメータ }
pUseLayer = true; { レイヤ別にチェック }
pUseClass = true; { クラス別にチェック }
pUseAttributes = true; { 属性別にチェック }
pNameCheck = true; { 名前がある図形は削除しない }
pRecordCheck = true; { レコードがある図形は削除しない }
pPrecision = 0.001; { 許容誤差 }
pDebug = true; { デバッグのためにソート用文字列を書き出す }
AnglePrec = 0.001;{by degree 検索する角度の許容誤差 }
ArrayBottom = -32768;
ArrayTop = 32767;
MinQSNum = 23;{ クイックソート/選択ソートの境界数 }
SortKey = 1;
MsgStep = 200;
MsgStep2 = 10;
LineObj = 2;
SelectedObjects = 2;
TraverseShallow = 0;
EditableLayers = 4;
type
AttributeType = structure
hLyr :handle;
hCls :handle;
lnWd, lnPat, lnFCol, lnBCol :integer;
end;{AttributeType}
LineType = structure
sSort :string;{ ソート用文字列。優先度:レイヤ>クラス>属性>角度 }
ptSt, ptEd :vector;
ang :real;
attrs :AttributeType;
h :handle;
next :longint;{ レイヤ、クラス、属性が等しい次のデータのID }
end;{LineType}
var
iLine, nLine, btmLine, topLine, iMsg, nDel, nUnDel :longint;
nMatch, nOverlap :longint;
hLn :dynArray[] of handle;
line :dynarray[] of LineType;
quit :boolean;
errMsg :string;
t, t0 :longint;
{SubRoutins}
function If_H(condition:boolean; dtTrue, dtFalse:handle):handle;
{ IF関数(ハンドル) }
begin
if condition then
If_H:= dtTrue
else
If_H:= dtFalse;
end;{If_H}
function If_S(condition:boolean; dtTrue, dtFalse:string):string;
{ IF関数(文字列) }
begin
if condition then
If_S:= dtTrue
else
If_S:= dtFalse;
end;{If_S}
function If_L(condition:boolean; dtTrue, dtFalse:longint):longint;
{ IF関数(長整数) }
begin
if condition then
If_L:= dtTrue
else
If_L:= dtFalse;
end;{If_L}
function If_R(condition:boolean; dtTrue, dtFalse:real):real;
{ IF関数(実数) }
begin
if condition then
If_R:= dtTrue
else
If_R:= dtFalse;
end;{If_R}
function If_V(condition:boolean; dtTrue, dtFalse:vector):vector;
{ IF関数(ベクトル) }
begin
if condition then
If_V:= dtTrue
else
If_V:= dtFalse;
end;{If_V}
function XY2Vec(x, y:real):vector;
{ ベクトルを返す。 }
begin
XY2Vec.x:= x; XY2Vec.y:= y;
end;{XY2Vec}
function AddLine(h:handle):boolean;
{ 線のハンドルを配列に追加する。 }
var
err :boolean;
begin
err:= false;
if GetType(h) = LineObj then begin
if iLine < ArrayTop then begin
iLine:= iLine + 1;
hLn[iLine]:= h;
end{if}
else begin
err:= true;
errMsg:= Concat('線が', ArrayTop-ArrayBottom+1, '本以上あります');
quit:= not YNDialog(Concat(errMsg, '。', Chr(13), '処理を続けますか?'));
if quit then
errMsg:= Concat(errMsg, 'ので、中止しました。')
else
errMsg:= Concat(errMsg, 'が、処理を続けました。');
end;{else}
iMsg:= iMsg + 1;
if MsgStep <= iMsg then begin
Message('線を取得中:', iLine-ArrayBottom+1);
iMsg:= 0;
end;{if}
end;{if}
AddLine:= err; { trueならForEachObjectXXXループを終了する。 }
end;{AddLine}
procedure GetAttributes(h:handle; var attrs:AttributeType);
{ 属性を返す。 }
var
r, g, b :longint;
begin
attrs.hLyr:= If_H(pUseLayer, GetLayer(h), nil);
attrs.hCls:= If_H(pUseClass, GetObject(GetClass(h)), nil);
if pUseAttributes then begin
attrs.lnWd:= GetLW(h);
attrs.lnPat:= GetLS(h);
GetPenFore(h, r, g, b); RGBToColorIndex(r, g, b, attrs.lnFCol);
GetPenBack(h, r, g, b); RGBToColorIndex(r, g, b, attrs.lnBCol);
end
else begin
attrs.lnWd:= 0;
attrs.lnPat:= 0;
attrs.lnFCol:= 0;
attrs.lnBCol:= 0;
end;
end;{GetAttributes}
function ExpString(s:string; digit:integer):string;
{ 指定した文字数になるように左端に空白を付ける。 }
begin
while Len(s) < digit do
s:= Concat(' ', s);
ExpString:= s;
end;{ExpString}
function GetAttrsStr(attrs:AttributeType):string;
{ 属性を文字列にする。 }
var
s :string;
begin
s:= '';
if pUseLayer then
s:= Concat(s, ExpString(Concat(attrs.hLyr), 10));
if pUseClass then
s:= Concat(s, ExpString(Concat(attrs.hCls), 10));
if pUseAttributes then begin
s:= Concat(s, ExpString(Concat(attrs.lnWd), 4));
s:= Concat(s, ExpString(Concat(attrs.lnPat), 4));
s:= Concat(s, ExpString(Concat(attrs.lnFCol), 4));
s:= Concat(s, ExpString(Concat(attrs.lnBCol), 4));
end;{if}
GetAttrsStr:= s;
end;{GetAttrsStr}
procedure Swap_V(var dt1, dt2:vector);
{ ベクトル型変数の値を交換する。 }
var
tmp :vector;
begin
tmp:= dt1;
dt1:= dt2;
dt2:= tmp;
end;{Swap_V}
procedure CreateLineTable;
{ 線テーブルを作成する。 }
var
i :longint;
a :real;
h :handle;
rvs :boolean;
begin
iMsg:= 0; Message('線情報を取得中:0 / ', nLine);
for i:= btmLine to topLine do begin
h:= hLn[ArrayBottom - btmLine + i];
line[i].h:= h;
GetSegPt1(h, line[i].ptSt.x, line[i].ptSt.y);
GetSegPt2(h, line[i].ptEd.x, line[i].ptEd.y);
a:= Vec2Ang(line[i].ptEd - line[i].ptSt);
rvs:= true;
if a < 0 then
a:= a + 180
else if 180 <= a then
a:= a - 180
else
rvs:= false;
if rvs then Swap_V(line[i].ptSt, line[i].ptEd);
line[i].ang:= a;
GetAttributes(h, line[i].attrs);
line[i].sSort:= Concat(GetAttrsStr(line[i].attrs), ExpString(concat(Round(a/AnglePrec*10)), 12));
iMsg:= iMsg + 1;
if MsgStep <= iMsg then begin
Message('線情報を取得中:', i-btmLine+1, ' / ', nLine);
iMsg:= 0;
end;{if}
end;{for}
end;{CreateLineTable}
function Small_L(i, j:longint):longint;
{ 小さいほうの数字を返す。 }
begin
if i < j then
Small_L:= i
else
Small_L:= j;
end;{Small_L}
procedure SortLines(left, right:longint);
{ 線の配列をソートする。}
var
i, j :Longint;
pivot :string;
tmp :LineType;
procedure Sort2(st, ed:longint);
{ 選択ソート(数が少ない場合) }
var
i, j, max :longint;
tmp :LineType;
begin
{選択ソート}
for i:= ed downto st+1 do begin
max:= st;
for j:= st+1 to i do begin
if line[max].sSort < line[j].sSort then
max:= j;
end;{for}
if i <> max then begin
tmp:= line[i];
line[i]:= line[max];
line[max]:= tmp;
end;
end;{for}
end;{Sort2}
begin{SortLines}
Message('直線をソート中! ', left-ArrayBottom+1, ' / ', nLine);
if (right - left) <= MinQSNum then begin
Sort2(left, right);
end
else begin
pivot:= line[(left + right) div 2].sSort;
i:= left;
j:= right;
repeat
while (line[i].sSort < pivot) do i:= i + 1;
while (line[j].sSort > pivot) do j:= j - 1;
if (i <= j) then begin
tmp:=line[i];
line[i]:=line[j];
line[j]:=tmp;
i:= i+1;
j:= j-1;
end;{if}
until (i > j);
SortLines(left, j);
SortLines(i, right);
end;{else}
{ Message('直線をソート中! ', right-ArrayBottom+1, ' / ', nLine); }
end;{SortLines}
procedure SetNextID;
{ line[]のnextの値を設定します。 }
var
i, bs :longint;
begin
bs:= btmLine;
for i:= btmLine to topLine - 1 do begin
if line[i].attrs = line[i+1].attrs then begin
line[i].next:= i + 1;
end
else begin
line[i].next:= bs;
bs:= i + 1;
end;
end;{for}
line[topLine].next:= If_L(line[topLine-1].attrs = line[topLine].attrs, bs, topLine);
end;{SetNextID}
function IsOverlapped(x12:real; pt21, pt22:vector; prec:real; var pt31, pt32:vector):boolean;
{ 2本の直線が重なっていたらTrueを返す。pt31とpt32には合成した線の座標が入る。 }
{ (0, 0)~(x12, 0)が線1、pt21~pt22が線2である。 }
var
result :boolean;
pt1, pt2 :vector;
y1, y2, d1, d2 :real;
procedure GetOverlap(pt11, pt12, pt21, pt22:vector; var pt31, pt32:vector);
{ pt11~pt12とpt21~pt22の重なる範囲を返す。 }
var
tmp :real;
begin
pt31:= If_V(pt11.x <= pt21.x, pt21, pt11);
pt32:= If_V(pt12.x <= pt22.x, pt12, pt22);
end;{GetOverlap}
procedure GetUnion(pt11, pt12, pt21, pt22:vector; var pt31, pt32:vector);
{ pt11~pt12とpt21~pt22の両方の直線を合成して返す。 }
begin
pt31:= If_V(pt11.x < pt21.x, pt11, pt21);
pt32:= If_V(pt12.x < pt22.x, pt22, pt12);
end;{GetUnion}
begin{IsOverlapped}
if ((pt21.x < -prec) & (pt22.x < -prec)) | (((x12+prec) < pt21.x) & ((x12+prec) < pt22.x)) then begin
result:= false;
end{if}
else begin
GetUnion(XY2Vec(0, 0), XY2Vec(X12, 0), pt21, pt22, pt31, pt32);
GetOverlap(XY2Vec(0, 0), XY2Vec(X12, 0), pt21, pt22, pt1, pt2);
y1:= pt31.y + (pt1.x - pt31.x) / (pt32.x - pt31.x) * (pt32.y - pt31.y);
y2:= pt31.y + (pt2.x - pt31.x) / (pt32.x - pt31.x) * (pt32.y - pt31.y);
result:= (Abs(pt1.y - y1) < prec) & (Abs(pt2.y - y2) < prec);
end;{else}
IsOverlapped:= result;
end;{IsOverlapped}
procedure CheckOverlap;
{ 線の重なりをチェックして不要な線を消します。 }
var
i, j, iDel, iChg :longint;
pt11, pt12, pt21, pt22, pt1, pt2 :vector;
pt11R, pt12R, pt21R, pt22R, pt1R, pt2R :vector;{ 回転後の座標(pt11ベース) }
rot :real;
nm1, nm2 :string;
result :boolean;
begin
if pDebug then begin
{ ソート用文字列の書き出し }
Message('ソート用文字列を書き出し中...');
for i:= btmLine to topLine do begin
write(i:8, ':', line[i].sSort, ' :', line[i].next);
if (i < topLine) & (line[i+1].sSort < line[i].sSort) then
Write(' Sort Error!');
WriteLn;
end;
end;{if}
iMsg:= 0; Message('線の重複チェック中:0 / ', nLine);
nDel:= 0; nUnDel:= 0;
pt11R:= XY2Vec(0, 0); pt12R.y:= 0;
for i:= btmLine to topLine - 1 do begin
iMsg:= iMsg + 1;
if MsgStep2 <= iMsg then begin
Message('線の重複チェック中:', i-btmLine+1, ' / ', nLine, ' Del=', nDel, ' (Ang=',line[i].ang, ')');
iMsg:= 0;
end;{if}
if line[i].h <> nil then begin
{ line[i]が水平になるように座標変換 }
pt11:= line[i].ptSt;
pt12:= line[i].ptEd; pt12R.x:= Norm(pt12-pt11);
rot:= line[i].ang;
j:= line[i].next;
while (i <> j) & (line[i].h <> nil) & (Abs(line[j].ang-line[i].ang) <= AnglePrec) do begin
if (line[j].h <> nil) then begin
pt21:= line[j].ptSt; pt22:= line[j].ptEd;
if (Norm(pt21 + pt22 - pt11 - pt12) < (Norm(pt22 - pt21) + Norm(pt12 - pt11) + 4*pPrecision)) then begin
pt21R:= Ang2Vec(Vec2Ang(pt21-pt11) - rot, Norm(pt21-pt11));
pt22R:= Ang2Vec(Vec2Ang(pt22-pt11) - rot, Norm(pt22-pt11));
if IsOverlapped(pt12R.x, pt21R, pt22R, pPrecision, pt1R, pt2R) then begin
pt1:= If_V(Norm(pt1R-pt11R) < Norm(pt1R-pt21R), pt11, pt21);
pt2:= If_V(Norm(pt2R-pt12R) < Norm(pt2R-pt22R), pt12, pt22);
iDel:= i; iChg:= j;{ Default }
result:= true;
if pNameCheck then begin
nm1:= GetName(line[i].h); if nm1 = 'none' then nm1:= '';
if nm1 <> '' then begin
nm2:= GetName(line[j].h); if nm1 = 'none' then nm2:= '';
if nm2 = '' then begin
iDel:= j; iChg:= i;
end
else begin
result:= false;
end;
end;{else}
end;{if}
if result & pRecordCheck then begin
if NumRecords(line[i].h) <> 0 then begin
if NumRecords(line[j].h) = 0 then begin
iDel:= j; iChg:= i;
end
else begin
result:= false;
end;
end;
end;{if}
if result then begin
nDel:= nDel + 1;
DelObject(line[iDel].h); line[iDel].h:= nil;
line[iChg].ptSt:= pt1; line[iChg].ptEd:= pt2;
SetSegPt1(line[iChg].h, pt1.x, pt1.y);
SetSegPt2(line[iChg].h, pt2.x, pt2.y);
if i = iChg then begin
pt11:= pt1;
pt12:= pt2; pt12R.x:= Norm(pt12 - pt11);
end;
end{if}
else begin
nUnDel:= nUnDel + 1;
end;{else}
end;{if}
end;{if}
end;{if}
j:= line[j].next;
end;{while}
end;{if}
end;{for}
end;{CheckOverlap}
begin{main}
t0:= GetTickCount;
quit:= false; errMsg:= '';
iLine:= ArrayBottom - 1;
Allocate hLn[ArrayBottom..ArrayTop];
iMsg:= 0; Message('線を取得中:');
ForEachObjectInLayer(AddLine, SelectedObjects, TraverseShallow, EditableLayers);
if quit then GoTo 9999;
nLine:= iLine - ArrayBottom + 1;
if nLine < 2 then begin
errMsg:= '線を2本以上選択してください!';
GoTo 9999;
end;
if nLine <= 32767 then
btmLine:= 1
else
btmLine:= ArrayBottom;
topLine:= nLine + btmLine - 1;
Allocate line[btmLine..topLine];
CreateLineTable;
Message('ソート中...');
if 0 < btmLine then
SortArray(line, topLine, SortKey)
else
SortLines(btmLine, topLine);
SetNextID;
CheckOverlap;
ReDrawAll;
9999:
t:= GetTickCount;
errMsg:= Concat(If_S(errMsg <> '', Concat(errMsg, Chr(13)), '終了しました!'), '(t=', (t-t0)/60, 'sec.)', Chr(13), nLine, '個中 ', nDel, '個削除 ', nUnDel, '個削除不能');
AlrtDialog(errMsg);
WriteLn(errMsg);
ClrMessage;
end;{main}
Run(xxx_3f);