拡張ミラー反転
ミラー反転を、2点クリックではなく、オブジェクトの辺をワン・クリックでできたらちょっといいんだけど、という軽い気持ちでスクリプトを組み始めたら、結構大変でした。ミラー基準線は、グループ化図形でも取ることができます。拘束点を持つ寸法の単独反転ができませんが、実用上は問題無いレベルになったかな?と思えるようになりましたので公開します。
動作の流れ
- 選択図形をカウント
- 選択図形にハンドルを割り当て
- ミラー基準線をピックアップ
- ひとつひとつの図形をミラー反転
なんでこんな面倒な処理をするのか?ForEachObjectInLayer を使えばもっと楽なんじゃない?と動作の流れを見て思う人もいるかもしれませんが、SetPolylineVertex のバグを回避する方法が無いこと(実は拘束点を無視すれば回避する方法を最近見つけた)と、図形の(上下・左右)反転をハンドル指定で行う関数が存在しないために、ForEachObjectInLayer に変わるものとして、一般化したソースを作ってみたくなったのです。
選択図形カウント&ハンドル割当部分は更に一般化したソースに書き直したいと思っています。
2014.3.27 追記
急に思いついたのですが、下記のようにした方が簡単だと思うので、今度試してみようと思います。
- オブジェクトから基準線となる線分を取得
- その線分の傾きと中点座標の情報を取得
- 選択図形を回転(その線分の傾きを水平か垂直にする)
- 左右、又は上下反転
- 選択図形を元の角度に回転
- 選択図形を線分の元の中点座標を基準に移動
- 線分消去
{*******************************************************************************
ExMirrorMove 拡張ミラー移動
Copyright 2010 兵藤善紀建築設計事務所
www.hyodo-arch.com
To Do 拘束情報をGETし、SETする方法を研究する。
2010.09.14 Ver 0.24 選択図形数、ハンドル配列の取得部分を外部関数化をすすめる。
拘束の研究を開始。
2010.09.13 Ver 0.22 選択図形の有無とは関係なく、グループ階層を調べる方法を発見
更に、グループ階層でのハンドル操作が可能になる。
2010.09.07 Ver 0.21 選択図形がグループのとき、グループ内の図形のときの条件分岐の整理
新たな事実として、グループ内は、グループを出ても、その時選択していた
図形を保持するので、カウント、ハンドル共思ったようにならない。
2010.09.04 Ver 0.20 A&A開発部から教えてもらった方法(FInGroup)で、
グループに入っているときのオブジェクトハンドルを得る。
2010.08.27 Ver 0.18 他のレイヤ上の選択図形も操作対象にする努力。
2010.08.16 Ver 0.16 円弧の中心座標のズレ対策として、GetBBoxを使うことにする。
2010.08.15 Ver 0.15 グループ編集時に他の図形を表示の切り替え中止。
グループから出る方式を、ネストの数にする。
2010.07.05 Ver 0.14 やっぱりFEOILを止め、HCenterで中心座標を出してみる
2010.07.05 Ver 0.13 複数の選択図形の場合、図形の中心点が取れないので、
FEOILを使ってみる。
2010.07.04 Ver 0.12 まず最初のバージョン
*******************************************************************************}
PROCEDURE ExMirrorMove;
LABEL 999;
VAR
ClickPT1 :Vector; {ミラー基準線を取得するためのクリック座標}
hh,hhNewObj,hhLine :Handle; {ミラー基準線操作ハンドル}
hhLayer,hhSel :Handle; {レイヤハンドル、選択図形操作対象ハンドル}
NNIG :Integer; {スクリプト実行時のグループ階層数}
SelObj:DYNARRAY[] OF Handle; {選択図形ひとつひとつに割り当てるハンドル}
NOSO,nn :Longint; {選択図形の数、図形ハンドル用カウンタ}
LP1,LP2,SelObjCenter :Vector; {ミラー基準線の両端の座標、選択図形の中心座標}
ArcMinRect1,ArcMinRect2 :Vector; {円弧図形が納まる最小四角形の座標}
LineAngle :Real; {ミラー基準線の角度}
aa,bb,cc,dd,pp,qq :Real; {LP1-LP2を通る式、SelObjCenterを通る式の係数}
LLIntersection,MoveDist :Vector; {二式の交点座標、移動距離}
NumOfInGroup :Longint; {ミラー基準線となる図形のグループのネスト数}
ii,kk :Integer; {カウンタ}
ObjTypeMess :String; {ダイアログ用メッセージ}
{****************** グループ階層を調べる ******************}
FUNCTION NumNestInGroup :Integer;
VAR
objH,parentH :HANDLE;
ii :Integer; {カウンタ}
BEGIN
Locus(0,0);
objH:=LNewObj;
parentH:=GetParent(objH);
ii:=0;
{コンテナのハンドルがレイヤハンドル(31)と等しくなるまで辿る}
WHILE GetType(parentH)<>31 DO Begin
parentH:=GetParent(parentH);
ii:=ii+1;
End;
DelObject(objH);
NumNestInGroup:=ii;
END;{NumNestInGroup}
{****************** グループに入っているときのカウント ******************}
FUNCTION NumInGroupObj( groupH : HANDLE ; numNest : Integer):Longint;
VAR
objH :HANDLE;
ii :Integer;
nn :Longint;
BEGIN
nn:=0;
{グループ階層数分、中に入る}
For ii:=1 To numNest Do Begin
groupH := FInGroup( groupH );
WHILE Not Selected( groupH ) DO groupH := NextObj( groupH );
End;
objH := groupH;
WHILE objH <> NIL DO BEGIN
IF Selected( objH ) THEN nn := nn + 1;
objH := NextObj( objH );
END;
NumInGroupObj:=nn;
END;{NumInGroupObj END}
{****************** 選択図形の数を返す ******************}
FUNCTION NumOfSelObj(numNest : Integer) :Longint;
VAR
hhLayer,hhSel :Handle; {レイヤハンドル、選択図形操作対象ハンドル}
NOSO :Longint; {選択図形の数}
BEGIN
NOSO:=0;
hhLayer:=FLayer;
While hhLayer<>nil Do Begin
hhSel:=FInLayer(hhLayer);
While hhSel<>nil Do Begin
If Selected(hhSel) Then Begin
If (numNest>0) & (GetType(hhSel)=11) Then Begin
NOSO:=NOSO+NumInGroupObj(hhSel,numNest);
End
Else NOSO:=NOSO+1;
End;
hhSel:=NextObj(hhSel);
End;
hhLayer:=NextLayer(hhLayer);
End;
NumOfSelObj:=NOSO;
END;{NumOfSelObj END}
{****************** グループに入っているときのハンドル割当 ******************}
PROCEDURE subGroupHandle( groupH : HANDLE ; numNest : Integer);
VAR
objH :HANDLE;
ii :Integer;
BEGIN
{グループ階層数分、中に入る}
For ii:=1 To numNest Do Begin
groupH := FInGroup( groupH );
WHILE Not Selected( groupH ) DO groupH := NextObj( groupH );
End;
objH := groupH;
WHILE objH <> NIL DO
BEGIN
If Selected( objH ) Then Begin
SelObj[nn]:= objH;
nn:=nn+1;
End;
objH := NextObj( objH );
END;
END;{subGroupHandle END}
BEGIN {****************** メインプログラム ******************}
{グループ階層を調べる}
NNIG:=NumNestInGroup;
{選択図形の数を調べる}
NOSO:=NumOfSelObj(NNIG);
{選択図形各々にハンドルを割り当てる}
If NOSO>0 Then Allocate SelObj[1..NOSO];
nn:=1;
hhLayer:=FLayer;
While hhLayer<>nil Do Begin
hhSel:=FInLayer(hhLayer);
While hhSel<>nil Do Begin
If Selected(hhSel) Then Begin
If (NNIG>0) & (GetType(hhSel)=11) Then subGroupHandle(hhSel,NNIG)
Else SelObj[nn]:=hhSel;
nn:=nn+1;
End;
hhSel:=NextObj(hhSel);
End;
hhLayer:=NextLayer(hhLayer);
End;
DSelectAll;
{選択図形数とオブジェクトタイプの確認
ObjTypeMess:=Concat(Num2Str(0,NOSO),'個 ');
For nn:=1 To NOSO Do Begin
ObjTypeMess:=Concat(ObjTypeMess,' ',Num2Str(0,GetType(SelObj[nn])));
End;
Message(ObjTypeMess);}
{初期化}
hh:= nil;
NumOfInGroup:=0;
{クリック座標直下の図形のハンドルを得る}
GetPt(ClickPT1.x, ClickPT1.y);
hh:=PickObject(ClickPT1.x, ClickPT1.y);
{グループのネスト数カウンタの初期化}
kk:=0;
{ミラー基準線がグループ(T=11)の場合は、グループに入る}
While (GetType(hh)=11) Do Begin
kk:=kk+1;
SetSelect(hh);
DoMenuTextByName('Group Navigation Chunk',1);
DSelectAll;
hh:=PickObject(ClickPT1.x, ClickPT1.y);
End;
NumOfInGroup:=kk; {グループのネスト数}
{ミラー基準線が四角形(T=3)多角形(T=5)曲線(T=21)の場合の処理(距離入力ダイアログの表示)}
IF (GetType(hh)=3) OR (GetType(hh)=5) OR (GetType(hh)=21) THEN Begin
hhNewObj:= HDuplicate(hh,0,0); {ハンドルをデュープした多角形に移す}
SetSelect(hhNewObj); {デュープした多角形を選択}
DoMenuTextByName('Convert to Lines',0); {デュープした多角形を線分に分解}
hhLine:=PickObject(ClickPT1.x, ClickPT1.y); {ClickPT1下の線分にハンドルを移す}
GetSegPt1(hhLine, LP1.x, LP1.y); {LP1とLP2の座標を取得}
GetSegPt2(hhLine, LP2.x, LP2.y);
LineAngle:=HAngle(hhLine);
If (LineAngle>90) & (LineAngle<=180) Then LineAngle:=LineAngle-180;
If (LineAngle>-180) & (LineAngle<=-90) Then LineAngle:=LineAngle+180;
SetSelect(hhLine); {ClickPT1下の線分を選択}
DeleteObjs; {多角形を分解した線分を消去}
End
{ミラー基準線が直線(T=2)の場合の処理(距離入力ダイアログの表示)}
ELSE IF GetType(hh)=2 THEN Begin
{直線の始点と終点の座標を取得}
GetSegPt1(hh, LP1.x, LP1.y);
GetSegPt2(hh, LP2.x, LP2.y);
LineAngle:=HAngle(hh);
If (LineAngle>90) & (LineAngle<=180) Then LineAngle:=LineAngle-180;
If (LineAngle>-180) & (LineAngle<=-90) Then LineAngle:=LineAngle+180;
End
{ミラー基準線が直線ではない場合の処理(距離入力ダイアログは表示しない)}
ELSE Begin
{グループ図形だった場合は、グループを出る}
IF NumOfInGroup>0 THEN For ii:=1 To NumOfInGroup Do DoMenuTextByName('Group Navigation Chunk',2);
ObjTypeMess:=Concat('クリックされた図形は、直線(Type=2)、四角形(T=3)、多角形(T=5)、曲線(T=21)のいずれでもありませんでした。
Object Type = ',Num2StrF(GetType(hh)));
AlrtDialog(ObjTypeMess);
Goto 999; {ミラーリングをスキップ}
End;
{グループ図形だった場合は、グループを出る}
IF NumOfInGroup>0 THEN For ii:=1 To NumOfInGroup Do DoMenuTextByName('Group Navigation Chunk',2);
{元の選択図形をひとつずつミラーリング}
DSelectAll;
IF NOSO>0 THEN Begin
For nn:=1 To NOSO Do begin
{ハンドル図形が寸法で拘束されている場合以外の処理}
If Not ((HasConstraint(SelObj[nn])) & (GetType(SelObj[nn])=63)) Then Begin
{ハンドル図形の中心座標}
If GetType(SelObj[nn])=6 Then Begin
GetBBox(SelObj[nn],ArcMinRect1.x,ArcMinRect1.y,ArcMinRect2.x,ArcMinRect2.y);
SelObjCenter.x:=ArcMinRect1.x+(ArcMinRect2.x-ArcMinRect1.x)/2;
SelObjCenter.y:=ArcMinRect2.y+(ArcMinRect1.y-ArcMinRect2.y)/2;
End
Else Begin
HCenter(SelObj[nn],SelObjCenter.x,SelObjCenter.y);
End;
{ミラー基準線の式(LP1-LP2を通る式: ax+by=p)}
aa:=LP2.y-LP1.y;
bb:=-(LP2.x-LP1.x);
pp:=aa*LP1.x+bb*LP1.y;
{ミラー基準線に直交しSelObjCenterを通る式(cx+dy=q)}
cc:=-bb;
dd:=aa;
qq:=cc*SelObjCenter.x+dd*SelObjCenter.y;
{上記2式の交点(逆行列による連立一次方程式の解法)}
LLIntersection.x:= (dd*pp-bb*qq)/(aa*dd-bb*cc);
LLIntersection.y:=(-cc*pp+aa*qq)/(aa*dd-bb*cc);
{移動距離}
MoveDist.x:=2*(LLIntersection.x-SelObjCenter.x);
MoveDist.y:=2*(LLIntersection.y-SelObjCenter.y);
{ハンドル図形を移動}
HMove(SelObj[nn],MoveDist.x,MoveDist.y);
{左右反転}
SetSelect(SelObj[nn]);
FlipHor;
SetDSelect(SelObj[nn]);
{ミラー基準線の角度に応じて回転移動}
SelObjCenter.x:=SelObjCenter.x+MoveDist.x;
SelObjCenter.y:=SelObjCenter.y+MoveDist.y;
HRotate(SelObj[nn],SelObjCenter.x,SelObjCenter.y,(2*LineAngle-180));
End;
end;
End;
999:
{元の選択図形を再選択}
DSelectAll;
IF NOSO>0 THEN Begin
For nn:=1 To NOSO Do SetSelect(SelObj[nn]);
End;
{選択図形の中心点を確認}
{Locus(SelObjCenter.x,SelObjCenter.y);
Locus(LLIntersection.x,LLIntersection.y);}
END;{ExMirrorMove}
RUN ( ExMirrorMove );