
コラム
AutoLISPでブロック内の図形を取得するには?(tblobjname,entnext)

AutoCADでは、AutoLISPと呼ばれるプログラミング言語を使って機能を拡張することができます。
AutoLISPについてはこちら→AutoLISPで業務を効率化する 第一回【使用方法】
AutoLISPを上手く使えば、既存のコマンドの手順を簡略化したり、複数のコマンドを組み合わせたり、更には新たな機能を作成することも可能です。今回は、その中でも「ブロック」をAutoLISPで扱う方法について解説します。
AutoCADのブロックを編集する機能

まずは、AutoCADの「ブロック」そのものについて整理します。
ブロックとは?
AutoCADにおいてブロックは「複数の図形をまとめたもの」です。ボルトや矢印など、複数の図形のまとまりで表現する必要のあるものも、ブロックにすると1個の図形として扱う事ができます。また、同じ種類のブロックを複数配置した場合、すべて同期されているので、ブロックを編集すると同じブロックにも一括で変更が反映されます。
詳しくはこちら
→AutoCADのブロックとグループの違いとは?使い分けや編集方法について解説
ブロックを編集するコマンド
ブロックの中身を編集するコマンドは、主に「ブロックエディタ」[BEDIT]、「インプレイスブロック編集」[REFEDIT]の2つです。
※ブロックエディタ画面、ブロックの内部座標系に移動します。

ブロックエディタは編集画面(ブロック固有の座標系)に移動するため、モデル空間での位置関係が分からなくなります。しかし、インプレイスブロック編集を使うとモデル空間のままブロックを編集することができます。
また、編集とは少し違いますが「分解」[EXPLODE]コマンドを使えばブロックを分解して、中身の図形をモデル空間に展開することもできます。
少々蛇足ですが、「ネストされたオブジェクトを複写」[NCOPY]を使えば、モデル空間からブロック内の任意の図形を選んでコピーすることができます。いちいち編集画面に移らなくていいので便利です。
AutoLISPでブロックを操作する
AutoLISPでブロックを扱う場合、通常の図形とは少々勝手が異なります。
AutoLISPで業務を効率化する 第三回【図形の操作】にて解説しましたが、AutoLISPにおいて、最もオーソドックスな図形の操作方法はssget(あるいはentsel)を起点に図形名を取得することです。しかし、ブロックに対してssget(entsel)を行っても、取得されるのはそのブロックの図形名であって、中身の図形名ではありません。
ブロック内の図形を取得する
咄嗟に思いつく方法として、ブロックを分解することが思い浮かぶかもしれません。分解すればモデル空間で触れるようになるので、一時的に分解して図形データを取得したのち、[UNDO]コマンドで再度ブロックに戻します。[UNDO]コマンドにはLISPの処理は含まれないため、コマンドの結果だけ戻してLISPの処理結果だけ残す事は可能です。
しかしながら、分解した後の図形と分解前のブロックにある図形は長さや色などのパラメータは同じでも、全く別の図形扱いになります(図形名が異なる)。そのため、単にデータを取得することは可能ですが、図形そのものを編集することはできません。
では、ブロック内の図形そのものを取得するにはどうすればいいでしょうか。
tblobjname関数
tblobjname関数を用いることでブロック内の図形名を取得することができます。ただし、取得できるのはそのブロックの「テーブル項目」と呼ばれるオブジェクトの図形名のみです。
構文は(tblobjname “BLOCK” “ブロック名“)です。
entnext関数
entnext関数を用いることで、ブロック内の図形を順に取得することができます。
構文は(entnext 図形名)と非常に簡単。
entnextは引数に渡した図形名の次の図形名を返す関数です。モデル空間内にある図形が引数だと単にモデル空間の図形を作成順に取得しますが、ブロック内にある図形が引数だと走査対象はそのブロック内にある図形のみです。次の図形がない場合はnilを返します。
スクリプト
では、tblobjnameとentnextを使って実際にブロック内の図形を取得してみましょう。例として、円を3個含んだブロックを作成します。

以下は、取得した図形の図形名をコマンドラインに表示させるLISPです。
while関数は条件式でnilが返るとループから抜けます。そして、entnext関数は調べる図形がなくなるとnilを返します。entnextの処理をwhileの条件式にすることで、ブロックの中身を全部調べ終わったら次へ進む、という処理になるのです。
(defun c:TEST_GET_BLOCK_01 ( / blk blk_name blk_en)
;まずはブロックを選択して図形データを取得する
;※本来なら誤動作でブロック以外が選択されないような処理も入れるが、式が煩雑化するので割愛
(setq
blk (car (entsel "Select block:"))
blk_name (cdr (assoc 2 (entget blk)))
blk_en (tblobjname "block" blk_name)
);q
(princ "\n")
(princ blk_en)
;
;whileを使ったループ処理
(while (setq blk_en (entnext blk_en))
(princ "\n")
(princ blk_en)
);while
(princ)
);defun
ブロック内の図形は3つしかないのに、取得した図形名は4つあります。つまり「テーブル項目」オブジェクト+ブロック内の図形×3という事ですね。
本当に正しく取得できたかを確かめるために、半径の値をコマンドラインに表示させてみましょう。
(defun c:TEST_GET_BLOCK_02 ( / blk blk_name blk_en)
(setq
blk (car (entsel "Select block:"))
blk_name (cdr (assoc 2 (entget blk)))
blk_en (tblobjname "block" blk_name)
);q
(princ "\n")
;円のDXFグループコード40番は中心座標
(princ (cdr (assoc 40 (entget blk_en))))
(while (setq blk_en (entnext blk_en))
(princ "\n")
(princ (cdr (assoc 40 (entget blk_en))))
);while
(princ)
);defun

1つだけnilが返りましたが、その他は整合しています。これは単に「テーブル項目」オブジェクトにはDXFグループコードが40の値が存在していないため、nilが返ったという事でしょう。ブロック内の図形がちゃんと取得されたことが確認できました。
ブロック内の図形を編集する
図形を編集する際には少し注意が必要です。ブロックを修正するという事は、モデル空間に存在する同名のブロックも同時に変えてしまうという事です。そのため、元のブロックを残しておきたい時は新たな別のブロックとして定義するなどの処理が必要になります。
また、色やレイヤーを変えるだけなら問題ありませんが、移動や複写など、座標を使用する処理ではワールド座標とブロック内座標が異なることを考慮する必要があります。例えば、ブロック内の図形を右に100㎜動かしたい時。この場合の処理は簡単で、単に図形のx座標値に+100してentmod関数で戻せばよいだけです。
では、ワールド内の任意の位置に図形を移動させたいとするとどうでしょうか。ブロック内の半径100㎜の円をワールド座標で「400,200,0」の位置に移動させるとします。
まず、ワールド座標で円の座標を確認してみます。
同じ中心位置に1回り大きめの円を書いてみました。座標は「300,200,0」となっています。

次に、ブロックエディタを起動して、該当の円の座標を確認してみます。
プロパティ欄を見てみると「300,0,0」となっていました。

つまり、この図形から取得される座標は「300,0,0」です。ワールド座標と円が持つ座標とでは、y軸に200㎜の差が生じているため、ワールド座標の値をそのまま上書きすると200㎜ずれてしまいます。そのため、この200㎜の差を埋める必要があります。
端的に言えば、この差はブロックの挿入基点の座標値そのものです。
このブロック自体の座標をプロパティで見てみると「0,200,0」となっています。

つまり、移動先の座標値からこの挿入基点の座標値を差し引くことで、円の移動先の座標値が求まります。後はこの座標値を図形データに上書きすれば良いだけですね。
(defun c:TEST_MOVE_INSIDEBLOCK_EN_01 ( / blk blk_pt blk_name blk_en en_data pt_move en_move en_pt en_pt2 )
(setq
blk (car (entsel "Select block:"))
blk_pt (cdr (assoc 10 (entget blk)))
blk_name (cdr (assoc 2 (entget blk)))
blk_en (tblobjname "block" blk_name)
pt_move (getpoint "\nClick the destination:")
en_move (list (- (car pt_move) (car blk_pt)) (- (cadr pt_move) (cadr blk_pt)) 0.0)
);q
(while (setq blk_en (entnext blk_en))
(setq en_data (entget blk_en))
(if (= 100 (cdr (assoc 40 en_data)))
(progn
(setq
en_pt (cdr (assoc 10 en_Data))
;en_pt2 (list (+ (car en_move) (car en_pt)) (+ (cadr en_move) (cadr en_pt)) 0.0)
);q
(entmod (subst (cons 10 en_move) (assoc 10 en_data) en_data))
);pro
);if
);while
(princ)
);defun
と、ここで問題が起きます。このLISPを使っても図形が動きません。そんな時は「全再作図」[REGENALL]コマンドを使ってみてください。ブロック内の編集がちゃんとモデル空間で反映されていないんですね。ブロックを編集するLISPを作る場合は、処理の最後に(command-s “._regenall”)で自動的に再作図するようにしておくと良いかもしれません。
ブロック内のブロック(ネストされたブロック)の図形を取得する
ブロックの中にブロックがネストされている場合、TEST_GET_BLOCK_01を使用しても一番外側のブロックの中身しか取得できません。ループ処理の中にループ処理を組み込む必要があります。更に別のループ処理を追加してもいいのですが、式が長くなる上に、煩雑化して読みにくいです。そこで、1つ小技をご紹介いたします。
; 1. ベースのLISPコマンド
;
(defun c:TEST_GET_BLOCK_03 ( / blk lst)
(setq
blk (car (entsel "Select block:"))
lst '()
lst (GET_INSIDEBLOCK_EN blk lst)
);q
(princ)
);defun
; 2. ループ処理のためのLISP関数
;
(defun GET_INSIDEBLOCK_EN ( blk lst / blk_name blk_en)
(setq
blk_name (cdr (assoc 2 (entget blk)))
blk_en (tblobjname "block" blk_name)
lst (cons blk_en lst)
);q
(princ "\n")
(princ blk_en)
(while (setq blk_en (entnext blk_en))
(setq
lst (cons blk_en lst)
blk_type (cdr (assoc 0 (entget blk_en)))
);q
; 分岐処理:取得した図形がブロック(INSERT)だった場合に、GET_INSIDEBLOCK_ENを実行する。
(if (= blk_type "INSERT")
(setq lst (GET_INSIDEBLOCK_EN blk_en lst));※引数に渡す変数名は関数で定義したものと一致していなくても良いです。
);
(princ "\n")
(princ blk_en)
);while
;関数の処理結果としてリストが返るようにする
lst
);defun
※動画ではTEST_GET_BLOCK_02となっていますが、03のことです。
詳細は省きますが、上記の様にLISPをコマンドではなく関数として定義して、関数内でネストして記述します。この様な再帰構造にすれば、何度もループ処理を記述する必要はありません。
図形の数について補足
動画内では8個の図形名が取得されていました。実際の図形は、円が5個、ブロックが2個の計7個です。これは、ネストされたブロックの図形名とそのブロックのテーブル項目オブジェクトの図形名の2つがカウントされてしまったのが原因です。最初の処理では、ブロックのテーブル項目図形名+中身の図形(円×2とブロック×1)で4個。そして、そのブロックのテーブル項目図形名+中身の図形(円×3)で4個。これで計8個という事になります。
tblobjname関数のその他の使い方
tblobjnameは、指定されたシンボルテーブル項目の図形名を返す関数です。つまりブロック以外のシンボルテーブル項目を持つ図形にも使用することができます。
具体的には、
|
シンボルテーブル名 |
内容 |
| “APPID” |
登録アプリケーション名 |
| “BLOCK” |
ブロック定義 |
|
“DIMSTYLE” |
寸法スタイル |
| “LAYER” |
画層(レイヤー) |
| “LTYPE” |
線種 |
|
“STYLE” |
文字スタイル |
| “UCS” |
ユーザー座標系 |
| “VIEW” |
登録ビュー |
| “VPORT” |
ビューポート設定 |
に対して使用できるようです。
この中では”DIMSTYLE”、”LAYER”、”LTYPE”、”STYLE”、”UCS”辺りは使う場面も多そうですね。
まとめ
今回はAutoLISPでtblobjnameとentnext関数を使ってブロックの中身を操作する方法について解説しました。
そもそもブロックは複数の図形を同期して使用できる便利な機能です。ブロックを使いこなすだけでも充分に効率的に作用します。
効率的に作図された図面で今回得た知識が役に立つとすれば、例えば、ネストされているブロックの中身全てを確認して編集しなければならない時などでしょうか。普通は複数のブロックを一括で編集したいという気持ちになることはないかもしれません。
しかし、全ての図面が整えられているわけではありません。それは単に技術者の努力不足と言う問題ではなく、ソフトごとの互換性や、業務の環境が特殊だったりと、事情は様々です。なぜか矢印のブロックが1つ1つ違う名前で定義されていたり、寸法がブロックで作成されていたりと、様々な事情で大量のブロックを何とかしなくてはならない場面に遭遇した経験があります。
読者の皆さんが同じような壁に直面した時にこの記事が助けになればと思います。