拡張ミラー反転

ミラー反転を、2点クリックではなく、オブジェクトの辺をワン・クリックでできたらちょっといいんだけど、という軽い気持ちでスクリプトを組み始めたら、結構大変でした。ミラー基準線は、グループ化図形でも取ることができます。拘束点を持つ寸法の単独反転ができませんが、実用上は問題無いレベルになったかな?と思えるようになりましたので公開します。

動作の流れ

  1. 選択図形をカウント
  2. 選択図形にハンドルを割り当て
  3. ミラー基準線をピックアップ
  4. ひとつひとつの図形をミラー反転

なんでこんな面倒な処理をするのか?ForEachObjectInLayer を使えばもっと楽なんじゃない?と動作の流れを見て思う人もいるかもしれませんが、SetPolylineVertex のバグを回避する方法が無いこと(実は拘束点を無視すれば回避する方法を最近見つけた)と、図形の(上下・左右)反転をハンドル指定で行う関数が存在しないために、ForEachObjectInLayer に変わるものとして、一般化したソースを作ってみたくなったのです。
選択図形カウント&ハンドル割当部分は更に一般化したソースに書き直したいと思っています。

2014.3.27 追記
急に思いついたのですが、下記のようにした方が簡単だと思うので、今度試してみようと思います。

  1. オブジェクトから基準線となる線分を取得
  2. その線分の傾きと中点座標の情報を取得
  3. 選択図形を回転(その線分の傾きを水平か垂直にする)
  4. 左右、又は上下反転
  5. 選択図形を元の角度に回転
  6. 選択図形を線分の元の中点座標を基準に移動
  7. 線分消去

{*******************************************************************************
                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 );