コラム

  • HOME
  • コラム
  • AutoLISPでモードを切り替えるには?(initget+getkword)

AutoLISPでモードを切り替えるには?(initget+getkword)

AutoLISPでモードを切り替えるには?(initget+getkword)

AutoLISP(以降LISP)とは、AutoCADの機能を拡張することができるプログラミング言語です。ExcelのVBAの様に機能を追加することができます。

そもそも、AutoLISPが何かを知りたい方はこちら↓
AutoLISPで業務を効率化する 第一回【使用方法】

AutoLISPについては知っているが、開発は経験はないという方はこちら↓
AutoLISPで業務を効率化する 第二回【開発準備】

LISPを使って作業を効率化しようとするときに、似たような内容で一部だけ処理を変えたい時が多くあります。
単純な解決策は別々のコマンドとして作ってしまうことですが、既存のAutoCADのコマンドを見るとそのような作りにはなっていません。基本的にユーザー入力によって何らかの処理(あるいはオプション)を切り替えることができるようになっているはずです。

※例:[OFFSET]コマンド

同様に、LISPでもユーザーが処理を切り替えられるようにすることができます。
本記事では、「initget」と「getkword」関数を使ったキーワード分岐の基本について解説します。

ユーザー入力が必要な理由

まずは、ユーザー入力による分岐処理が必要な状況について考えてみましょう。
できることなら、ユーザーが選ぶまでもなく全てが完了するのがベストに思えます。しかし、状況によってはそうできなかったり、そうしない方が良かったりする場合があります。

単一のコマンドで複数の動作を切り替えたい場合

冒頭で話したような、「似たような内容で一部だけ処理を変えたい」という状況です。例えば、[QSELECT]コマンドを簡易化して、とある個所から「線」だけを選択するLISPを作成するとします。この処理はとても簡単です。

(defun c:SOL ( / ss);Select Only Line
(setq ss (ssget  '((0 . "LINE"))))
(sssetfirst nil ss)
(princ)
);defun

しかし、今度は「ポリライン」だけ選択したくなりました。すると以下の様なコマンドができます。

(defun c:SOPL ( / ss);Select Only PolyLine※SOPだとPointとかぶるので。
(setq ss (ssget  '((0 . "LWPOLYLINE"))))
(sssetfirst nil ss)
(princ)
);defun

上記2つのコマンドの差は「ssget」関数の引数に渡る連想リストの中身が「LINE」か「LWPOLYLINE」か、ただそれだけです。このためだけにわざわざ2つコマンドを用意するなら、1つにしてしまう方が良いかもしれません。

ある処理を行いたい時と行いたくない時がある場合

例えば何かの参照のために作成したオブジェクトがあり、それを削除したい時があるとします。代表的なものとしては「点」でしょうか。このためのLISPを作成します。

(defun c:DOP ( / ss);Delete Only Point
(setq ss (ssget  '((0 . "POINT"))))
(command-s "._ERASE" ss "")
(princ)
);defun

見ての通り、(sssetfirst nil ss)という式が(command-s “._ERASE” ss “”)に置き換わっているだけですね。

ここで、前項のようにただ「点」オブジェクトを選択したいだけの時があるとします。このためにSOP(Select Only Point)と言うコマンドも作成するなら、1つのコマンドで[選択]から[削除]に処理を切り替えられた方が便利な気がしますね。

誤操作を防ぐ安全確認として

LISPは便利ですが、状況によっては致命的な問題を引き起こす可能性は否定できません。
少し処理を間違えて無限ループに突入したり、思いがけない部分が処理に含まれて変更されるなど、データの健全性に影響を及ぼします。
前述したような簡単なスクリプトでは気にする必要はありませんが、より大規模でオブジェクトの変更を伴うLISPでは、「保存するか否か」、「実行するか否か」をユーザー入力で選ばせておくと、ミスが生じる前に立ち止まることができる可能性が上がります。

「initget」関数と「getkword」関数

具体的な中身の解説に入っていきます。冒頭で述べた通り、キーワードを定義(登録)する「initget」と、入力を受け取る「getkword」の2つの関数をセットで使用します。

getkword関数でユーザーの入力を取得する

getkword関数は、事前にinitget関数で登録されたキーワードがある場合、そのいずれかを入力または選択するようにユーザーに促す関数になります。

initget関数で登録した場合、その文字列と対応するもの以外は受け付けません。ユーザーが誤って無関係なキーを押した場合は、AutoCADが自動的に再入力を要求します。

getkword関数の構文

getkword関数の構文は(getkword “コマンドラインに入力する文字列”)と非常に単純ですが、initget関数と組み合わせる場合は文字列の書き方にルールがあります。

(getkword "\nユーザーへの質問文 [キーワード文字列1/キーワード文字列2]: ")

です。これをこのままコマンドラインに入力すると以下の様な状態になります。

[ ]の中身が薄いグレーでハイライト表示されていますね。また、”/”で区切られた部分が独立して表示されています。それぞれ選択肢として認識されているわけです。

ちなみに、ハイライト表示された選択肢をクリックすることもできますが、initgetで登録されたキーワードがないため、以下の様に「そのようなオプションキーワードはありません。」と表示され、再入力が要求されます。前述の「誤って無関係なキーを押した場合」と同様です。

空入力について

何も入力せずにenterを押すことを空入力と言います。基本的に空入力を行うと返り値はnilになるため、その後、文字を期待する処理にnilが渡るためエラーで停止します。

対策の1つは、空入力後のnilを前提とした分岐処理を用意することです。

(if (null ans) (setq ans "キーワード1"))

と言うように、空入力でnilが返った後にif文を入れておくと、繰り返しキーワードを選択(入力)せずともエンターを押すだけで処理が進むので楽です。

もう1つの対策は、initget関数側で行えます。

initget関数で選択肢(キーワード)を事前登録する

initget関数は、直後に呼び出されるユーザー入力関数(getkwordやgetpointなど)に対して、入力できる値を制限したり、有効なキーワード文字列を事前設定する関数です。この関数でキーワードを定義した場合、直後のgetkword関数の返り値は、その関数で入力(選択)された文字列ではなく、initget関数のキーワードになるので注意してください。

initget関数の構文

基本構文は (initget [ビット値] “キーワード文字列”) となります。

第2引数の”キーワード文字列”は半角スペースで区切って記述し、大文字で指定したアルファベットがプロンプト上でのショートカット入力キーとして機能します。

大文字のみが認識されるため、例えば “Yes No” と指定した場合は「Y」と「N」のみが対象です。そのため、以下の様に日本語などを混ぜることができます。

(initget "Yes No")

(getkword "\n処理を実行しますか? [はい(Y)/いいえ(N)]: ")

「はい(Y)」を選択するとinitget関数で定義したキーワードの”Yes”が返ります。

initgetの第一引数に1を入力

第一引数に数値(ビット値)を入れると色々な処理ができます。中でも、1と設定することで、空入力を禁止することが可能です。普通に空入力をするとnilが返って処理が終わりますが、1と入れた状態だと再入力になります。

一見すると常に1と入れておいた方が良いかと思われるかもしれません。しかし、連続で何度もLISPを使いたい時などに空入力が禁止されていると毎回文字列を選択(入力)しなくてはなりません。対して、空入力を許可して、代わりに空入力時の処理を記述して置けば、エンターを押すだけで機能させられるので操作性が上がります。

 

実践:指定方向順に線分の長さを取得するLISP

では、実際にどう使うかを解説していきます。例として、選択した線分の長さを取得するLISPを作成しました。線分の位置(座標値)の順番で長さを取得するのですが、通常だと画面から見て左から右か、上から下か、それら逆かと選択肢が4つ生じます。ここで、この4択をinitget+getkwordで選んで取得方向を変えられるようにするわけです。

LISPのスクリプト

(defun c:TEST_GET_LINE_LENGTH ( / loc sl ss n en data p10 p11 len sort_val lst_data sorted_lst idx)
(vl-load-com)
;1.キーワードの登録:Up,Down,Left,Rightの頭文字。
(initget 1 "U D L R")
;2.キーワードの選択:矢印マークと一緒にキーワードを記述(識別されるのはキーワードのみ)。
;+使用言語を識別する処理:システム変数の"LOCAL"、日本語なら"JA"、英語なら"EN。"
(setq
loc (getvar "LOCALE")
sl (if (= loc "JA")(getkword "\n方向を指定 [↑(U)/↓(D)/←(L)/→(R)] : ")(getkword "\nSelect direction [^(U)/v(D)/<(L)/>(R)] : "))
);q
;3.線分の選択。
(if (= loc "JA")(princ "\n線分を選択: ")(princ "\nSelect line: "))
(setq
ss (ssget '((0 . "LINE")))
n (sslength ss)
);setq
;4.------------------------------繰り返し処理。
(repeat n
;4-1.各種データ取得。
(setq
n (1- n)
en (ssname ss n)
data (entget en)
p10 (cdr (assoc 10 data))
p11 (cdr (assoc 11 data))
len (distance p10 p11)
);q
;4-2.分岐処理:まずは縦並び横並びで処理を変更(座標値のxを使うかyを使うかで変わるため)。
;補足:始点と終点から中点を取得して基準にします(線分が交差している時の対策)。
(cond
((or (= sl "U") (= sl "D"));縦_y値を格納
(setq sort_val (* 0.5 (+ (cadr p10) (cadr p11))))
);UD
((or (= sl "L") (= sl "R"));横_x値を格納
(setq sort_val (* 0.5 (+ (car p10) (car p11))))
);LR
);cond
;4-3.データをリストに格納:(ソートに使う値, 長さ, 図形名)と言う形式で連想リスト化。
(setq lst_data (cons (list sort_val len en) lst_data))
);rep
;--------------------------------繰り返し処理終了。
;
;5.リストをソート:ソートに使う値は単純な数値なので、降順か昇順かの処理をキーワードで変更。
(setq
sorted_lst
(vl-sort lst_data
'(lambda (a b)
(if (or (= sl "U") (= sl "R"))
(< (car a) (car b))
(> (car a) (car b))
);if
);lambda
);vl-sort
);setq
;6.コマンドラインに結果を表示。
(if (= loc "JA")
(princ (strcat "\n>> 取得結果(方向: " sl " 順、合計: " (itoa (length sorted_lst)) " 本):"))
(princ (strcat "\n>> Result(Direction: " sl " _Total: " (itoa (length sorted_lst)) " item):"))
);if
(setq idx 1)
(foreach item sorted_lst
(if (= loc "JA")
(princ (strcat "\n [" (itoa idx) "] 線分長: " (rtos (cadr item) 2 2) " mm"))
(princ (strcat "\n [" (itoa idx) "] Line length: " (rtos (cadr item) 2 2) " mm"))
);if
(setq idx (1+ idx))
);foreach
(princ)
);defun

解説

大まかに処理の流れを解説します。

★をつけた個所が今回のメインです。

  1. ★キーワードの登録(initget)
  2. ★キーワードの選択(getkword)
  3. 線分の選択(ssget)
  4. 繰り返し処理(repeat)
    1. 各種データ取得
    2. ★分岐処理で使うデータを変更(cond)
    3. データをリストに格納(cons)
  5. ★リストをソート(vl-sort)
  6. コマンドラインに結果を表示

1と2で設定したキーワードで、4-2の分岐処理、5のソートのルールが決まります。このスクリプトではinitgetの第一引数を1に設定して空入力を禁止しています。もう少し頑張れば空入力の結果を保存された前回の処理結果にすることもできますが、解説の都合で今回は省略しました。
getkwordの結果は変数slに格納されます。後は、この変数slの中身をifやcondの条件式で判定することで処理を変えます。

その他、今回のスクリプトの処理について知りたい方は以下の記事をご覧ください。

選択、データ取得について知りたい方↓

『AutoLISPで業務を効率化する 第三回【図形の操作】』

リストについて知りたい方↓

『AutoLISPのリストとは?基本的な関数について解説(append,subst等…)』

『AutoLISPの便利なリスト操作関数について解説(foreach,mapcar等…)』

その他補足

今回はgetkwordをメインに解説しましたが、その他にもget系の関数は沢山あります。また、initgetの第一引数に1以外を入れた時の動作についてもまとめておきます。

座標や長さを画面から取得する関数

図形作図や編集で最も多用されるのが、点(Point)や距離(Distance)を取得する関数です。

・getpoint: 画面上のクリック点またはキーボード入力された座標を受け取り、3次元座標リスト ‘(X Y Z) を返す。

・getcorner: 基準となる点(第1引数)を指定すると、マウスカーソルに合わせて四角形のラバーバンドが表示され、対角点座標を取得できる。

・getdist: 画面上の2点を指定するか、数値を直接入力することで、距離を実数(Real)として返す。第1引数に基点を渡すと、その点からの距離測定ラバーバンドが表示される。

数値や文字列を取得する関数

・getint: 整数(Integer)を取得する。作図個数や分割数など、端数のない値を求める際に用いる。

・getreal: 小数を含む実数(Real)を取得する。部材厚やオフセット量など、ミリ単位・小数単位の寸法入力に適している。

・getstring: 任意の文字列(String)を取得する。第1引数に非nil(T)を渡すと、半角スペースを含めた文章全体の入力が可能になる。

initgetの第一引数(ビット値)による動作の違い

 

ビット値

機能・制御内容

対象入力関数

実務での主な活用例

1

空入力(Enterキーのみ / nil)を禁止する

すべての入力関数

必須入力の項目でEnterによるスキップを防ぎたい場合

2

ゼロ(0)の入力を禁止する

getint, getreal

割り算の分母や尺度など、0を指定されると困る場合

4

負の数値(マイナス値)の入力を禁止する

getint, getreal, getdist

部材長や個数など、正の数のみを受け付けたい場合

8

図面範囲(LIMITS)外の点指定を禁止する

getpoint, getcorner

規定の作図枠外への誤配置を防止したい場合

32

ラバーバンド(ドラッグ線)を破線表示にする

getpoint, getcorner

作図時の視覚的なガイドラインを破線で強調したい場合

64

2D平面上の点として扱う(3D座標の無視)

getdist

Z座標の高低差を無視して平面距離だけを取得したい場合

128

任意のキーボード入力を許可する

getpoint等の点取得関数

点だけでなく、任意の文字列やキーワードも入力させたい場合

 

まとめ

今回は、「initget」と「getkword」による条件分岐、および各種ユーザー入力関数の基本について解説しました。

単一の動作を繰り返すだけでなく、実行時にユーザーへ選択肢を提示して動作を切り替える仕組みを取り入れることで、自作ツールの汎用性は格段に高まります。また、不正入力を自動で弾く機構やYes/Noの確認画面を設けることで、現場の誰もが安心して使える実務ツールに仕上がります。

 

なんでもお気軽に
ご相談ください!

06-6195-3991

国土工営コンサルタンツ(株) 
営業時間:平日9:00 - 17:30

フォームから問合せする