カスタム入力関数

カスタム入力関数の作成 1

リアルタイム入力と画面描画、そして オブジェクトスナップが扱えるようになったので、ここでは最終的に getdist 関数に似た入力関数を作成するのですが、その前に土台となるカスタマイズ可能な入力関数を作成します。簡単のため、マウスの移動とクリック、キー入力、そしてオブジェクトスナップに対応しますが、キーワードや座標の直接入力、直行モード、グリッドスナップなどといった機能は省略します。

getPointEventHandler 関数がインターフェースです。$lastPoint 引数はシステム変数 LASTPOINT に一時的に設定され、オブジェクトスナップで使用されます。$_string 引数が、【コマンドライン】に表示する入力を促す文字列、$_onMouseMove 引数はマウスが移動したときの動作を $_onLeftClick 引数は左クリックの際の動作をカスタマイズする関数を指定します。 いくつかローカル変数が宣言されています。$_osnapOrderr はスナップ順を表す整数、$_stillnesss と $_prePointt はある程度ユーザーがマウスを動かしたときにスナップ順をリセットするかどうか判断する際に使用します。$_keyBuffer は、ユーザーからのキー入力を保持するリスト形式のバッファで、後に入力したキーコードが前に来るように一文字ずつ格納していくことにします。ここではひとまず、文字入力は最後にバッファを逆順に変換して【コマンドライン】に表示されるだけとし、キーワードや直接座標入力に対応したい場合は、ここのコードを拡張します。 この関数の核は、getPointEventHandler:loop 関数を nil 以外が返ってくるまで繰り返し呼び出すことですが、その getPointEventHandler:loop 関数内では、リアルタイム入力を読み取り、イベントごとに処理を振り分けています。特にここでは、左クリックの際にスナップを調べた点でカスタム用の関数を呼び出しています。

(defun getPointEventHandler:loop (/ $_device $_code $_data $_osnapInfo)
  (setq $_device (grread T (+ 1 2 4 8) 0)
        $_code   (car $_device)
        $_data   (cadr $_device)
  )
  (cond ((= $_code 5)                   ;Mouse Move
         (getPointEventHandler:onMouseMove $_data)
        )
        ((= $_code 3)                   ;left click
         (apply $_onLeftClick
                (list (if (setq $_osnapInfo (OSnapInfomation $_data $_osnapOrder))
                        (cdr $_osnapInfo)
                        $_data
                      )
                )
         )
        )
        ((= $_code 25)                  ;right click
         (vl-list->string (reverse $_keyBuffer))
        )
        ((= $_code 2)                   ;key press
         (getPointEventHandler:onKeyPress $_data)
        )
  )
)

(defun getPointEventHandler ($lastPoint      $_string        $_onMouseMove
                             $_onLeftClick   /               $_flag
                             $_result        $_osnapOrder    $_stillness
                             $_prePoint      $_keyBuffer     $_lastpoint
                            )
  (if $lastPoint
    (progn (setq $_lastpoint (getvar "LASTPOINT")) (setvar "LASTPOINT" $lastPoint))
  )
  ;;
  (while (null $_flag)
    (setq $_osnapOrder 0)
    (prompt $_string)
    ;;
    (while (null $_result) (setq $_result (getPointEventHandler:loop)))
    ;;
    (if (= (type $_result) 'STR)
      (progn (prompt (strcat "\nThe $_keyBuffer is " $_result))
             ;; The following code is replaced with the 
             ;; one that analyzes the input string
             (setq $_result nil
                   $_flag T
             )
      )
      (setq $_flag T)
    )
  )
  (redraw)
  ;;
  (if $_lastpoint
    (setvar "LASTPOINT" $_lastpoint)
  )
  $_result
)

getPointEventHandler:loop 関数内で使用している getPointEventHandler:onMouseMove 関数は次のようなものです。AutoCAD ユーザーはスナップ順を TAB キーで変更できますが、ある程度マウスを動かすと変更した順をリセットします。そして、現在点がスナップするのかを調べて、必要ならその点にマーカーを描画します。最後に、マウスカーソル移時用のカスタム用の関数に、現在点を渡して処理を行います。

(defun getPointEventHandler:onMouseMove ($_pt)
  (if (and $_stillness
           (< (distance $_prePoint $_pt)
              (* (getvar "PICKBOX") (/ (getvar "VIEWSIZE") (cadr (getvar "SCREENSIZE"))))
           )
      )
    (setq $_stillness T)
    (setq $_stillness nil
          $_osnapOrder 0
          $_prePoint $_pt
    )
  )
  (redraw)
  (if (setq $_osnapInfo (OSnapInfomation $_pt $_osnapOrder))
    (OSnapInfomation:drawMarker $_osnapInfo)
  )
  (apply $_onMouseMove (list $_pt))
)

getPointEventHandler:onKeyPress 関数は次のようなものです。ファンクションキーやショートカットに応じてシステム変数を変更などします。その他、ENTER キーや TAB キーの動作に対応します。一般のキーが入力された場合は $_keyBuffer に文字ごとにリストで逆順に貯え、BACKSPACE キーが押された場合は $_keyBuffer の先頭から文字を取り除きます。

(defun getPointEventHandler:onKeyPress ($_char / $_flipSystemVariable)
  (defun $_flipSystemVariable (name bitValue)
    (setvar name (Boole 6 (getvar name) bitValue))
    nil
  )
  (cond ((or (= $_char 13) (= $_char 32)) ; ENTER or SPACE
         (vl-list->string (reverse $_keyBuffer))
        )
        ((= $_char 6) ($_flipSystemVariable "OSMODE" 16384)) ; F3,Ctrl+f
        ((= $_char 25) ($_flipSystemVariable "3DOSMODE" 1)) ; F4
        ((= $_char 5)                   ; F5,Ctrl+e
         (setvar "SNAPISOPAIR" (rem (1+ (getvar "SNAPISOPAIR")) 3))
         nil
        )
        ((= $_char 4) ($_flipSystemVariable "UCSDETECT" 1)) ; F6
        ((= $_char 7) ($_flipSystemVariable "GRIDMODE" 1)) ; F7,Ctrl+g
        ((= $_char 15) ($_flipSystemVariable "ORTHOMODE" 1)) ; F8
        ((= $_char 2) ($_flipSystemVariable "SNAPMODE" 1)) ; F9
        ((= $_char 21) ($_flipSystemVariable "AUTOSNAP" 8)) ; F10
        ((= $_char 151) ($_flipSystemVariable "AUTOSNAP" 16)) ; F11
        ((= $_char 31) (setvar "DYNMODE" (- (getvar "DYNMODE"))) nil) ; F12
        ((= $_char 8)                   ; BackSpace
         (prompt (chr $_char))
         (setq $_keyBuffer (cdr $_keyBuffer))
         nil
        )
        ((= $_char 9)                   ; TAB
         (setq $_osnapOrder
                (1+ $_osnapOrder)
               $_stillness T
         )
         nil
        )
        (T (prompt (chr $_char)) (setq $_keyBuffer (cons $_char $_keyBuffer)) nil)
  )
)

以上で、カスタマイズ可能な入力関数の土台ができました。次は、これを具体的にカスタマイズして一つの入力関数として完成させます。足りない部分は、マウスカーソルが動いた際のイベントに対応する関数と左クリックされた際のイベントに対応する関数を用意することです。

カスタム入力関数の作成 2

ここでは、指定した点を中心に星形を描きハッチで塗りつぶす、前出のコマンドを改良しようとしているとします。サポート関数を除いた、コマンドのメイン関数は以下のようなものでした。

(defun c:star2 (/ radius center CMDECHO BLIPMODE OSMODE *error*)
  (if
    (and (setq center (progn (initget (+ 1 2 4)) (getpoint "\n中心を指定 : ")))
         (setq radius (progn (initget (+ 1 2 4 64)) (getdist center "\n半径を入力 : ")))
    )
     (progn (setq CMDECHO  (getvar "CMDECHO")
                  BLIPMODE (getvar "BLIPMODE")
                  OSMODE   (getvar "OSMODE")
                  *error*  star:error
            )
            (setvar "CMDECHO" 0)
            (setvar "BLIPMODE" 0)
            (setvar "OSMODE" 0)
            (command "._undo" "_be")
            ;;
            (drawPolyline (StarData center radius) T)
            (command "._HATCH" "_S" "_S" (entlast) "")
            ;;
            (command "._undo" "_e")
            (setvar "CMDECHO" CMDECHO)
            (setvar "BLIPMODE" BLIPMODE)
            (setvar "OSMODE" OSMODE)
     )
  )
  (princ)
)

ここで、半径をユーザーが指定する際に、星形をリアルタイムで表示すると結果が予想しやすくなります。そのため、c:star2 関数の半径を取得している getdist 関数を置き換えるものを作成します。ただし、 initget 関数によるキーワード指定のような機能は今回は見送るものとします。

autolisp getdist

前節までのひな形関数を用いて、星形をリアルタイムで描画するようにしたカスタム入力関数は次の通りになります。匿名の関数を与えることで、マウス移動時の描画と、クリックされた場合の座標から距離の計算を定義します。マウス移動時の $_onMouseMove として実行される匿名の関数では、戻り値を nil として getPointEventHandler 関数内のループを継続させています。ここで何らかの場合に nil 以外を返すようにすると、状況によってはループをブレークすることができます。

(defun getStarRadius (center string /)
  (getPointEventHandler
    center
    string
    (function
      (lambda (pt)                      ; on MouseMove
        (grvecs (grvecs:DrawData (list center pt) 9 nil))
        (grvecs (grvecs:DrawData (PolarPolygon center (distance center pt) 0 24) 9 T))
        (grvecs (grvecs:DrawData (StarData center (distance center pt)) acWhite T))
		nil
      )
    )
    (function (lambda (pt) (distance center pt))) ; on LeftClick
  )
)

このカスタム入力関数を使用した新しいコマンド、star3 関数は次のようになります。既存の getdist 関数の所を getStarRadius 関数に置き換えます。また、入力関数の注意点で説明した空入力や例外に対応した getException 関数を導入します。getException 関数で例外が起こった際の処理を「'(progn (redraw) nil)」として、エラーの際には画面描画をクリアします。

(defun c:star3 (/ radius center CMDECHO BLIPMODE OSMODE *error*)
  (if (and (setq center (progn (initget (+ 1 2 4) "")
                               (getException 'getpoint (list "中心を指定 : ") nil nil)
                        )
           )
           (setq radius (getException
                          'getStarRadius
                          (list center "半径を入力 : ")
                          nil
                          '(progn (redraw) nil)
                        )
           )
      )
    (progn (setq CMDECHO  (getvar "CMDECHO")
                 BLIPMODE (getvar "BLIPMODE")
                 OSMODE   (getvar "OSMODE")
                 *error*  c:star:error
           )
           (setvar "CMDECHO" 0)
           (setvar "BLIPMODE" 0)
           (setvar "OSMODE" 0)
           (command "._undo" "_be")
           ;;
           (drawPolyline (StarData center radius) T)
           (command "._HATCH" "_S" "_S" (entlast) "")
           ;;
           (command "._undo" "_e")
           (setvar "CMDECHO" CMDECHO)
           (setvar "BLIPMODE" BLIPMODE)
           (setvar "OSMODE" OSMODE)
    )
  )
  (princ)
)

今回のサンプルコードは各所に散らばっているので、こちらにまとめました。コピーアンドペーストして試してみてください。

カスタム用の入力関数は、キー入力はまだ不完全です。そしてグリッドスナップや直行モードなど、まだ機能的に不足するものがあります。しかし、一度作ってしまえば、イベントごとの関数を差し替えるだけで、いろんな状況や図形に対応した入力関数が作成できるようになります。

カスタム入力関数の作成 3

カスタム入力関数がひな形から簡単に作成できるのを示すため、もう一例作成して簡単に紹介します。今回はライトウェイトポリラインを描く関数ですが、クリックした点が指定した半径の範囲でランダムにずれるという入力関数です。

autolisp getpoint

サポート関数は先の例で使ったものを使用しますが、追加では、次のように乱数を生成する関数を用意します。

(setq *INT_MAX*  2147483647
      *INT_MIN*  -2147483648
      *INT_MAXf* (float *INT_MAX*)
      *INT_MINf* (float *INT_MIN*)
)

(setq *randomSeed* 0)

;;;	INT_MIN~INT_MAXの間の整数の乱数

(defun irand () (setq *randomSeed* (1+ (* *randomSeed* 69069))))

;;;	0.0~1.0 の間の実数の乱数

(defun rnd () (/ (- (irand) *INT_MINf*) (- *INT_MAXf* *INT_MINf*)))

新しいカスタム入力関数は次のとおりです。マウス移動のイベントでは、ずれの範囲を表す半径の円を描き、またそれまでの確定した折れ線を描きます。そして現在ずれを起こした新しい点まで線を描きます。新しいずれ点はシンボル dPoint に保存して、左クリックの確定の際にその値を返します。今回のコマンドの場合、オブジェクトスナップした点は使っていません。

(defun getDrunkardPoint (plist radius string / dPoint)
  (getPointEventHandler
    string
    (function
      (lambda (pt)                      ; on MouseMove
        (grvecs (gravecs:DrawData (PolarPolygon pt radius 0 24) acRed T))
        (if (<= 2 (length plist))
          (grvecs (gravecs:DrawData plist acWhite nil))
        )
        (grvecs
          (gravecs:DrawData
            (list (last plist)
                  (setq dPoint (polar pt
                                      (* 2 PI (rnd))
                                      (* radius (rnd))
                               )
                  )
            )
            9
            nil
          )
        )
      )
    )
    (function (lambda (pt)              ; on LeftClick
                dPoint
              )
    )
  )
)

コマンドのメイン関数は次のとおりです。コマンド名は DRANKARD としました。

(defun drunkard:error (msg)
  (if command-s
    (progn (command-s "._undo" "_e") (command-s "._U"))
    (progn (command "._undo" "_e") (command "._U"))
  )
  (setvar "CMDECHO" CMDECHO)
  (setvar "BLIPMODE" BLIPMODE)
  (setvar "OSMODE" OSMODE)
  (princ)
)

(defun c:drunkard (/ radius plist point CMDECHO BLIPMODE OSMODE *error*)
  (if (and (setq radius (progn (initget (+ 1 2 4) "")
                               (getException 'getdist (list "\n半径を入力 : ") nil nil)
                        )
           )
           (setq start (progn (initget (+ 1 2 4) "")
                              (getException 'getpoint (list "\n始点を指定 : ") nil nil)
                       )
           )
           (setq plist (progn (setq plist (list start))
                              (while (setq point (getException
                                                   'getDrunkardPoint
                                                   (list plist radius "\n次の点を指定 : ")
                                                   nil
                                                   '(progn (redraw) nil)
                                                 )
                                     )
                                (setq plist (cons point plist))
                              )
                              (reverse plist)
                       )
           )
           (< 1 (length plist))
      )
    (progn (setq CMDECHO  (getvar "CMDECHO")
                 BLIPMODE (getvar "BLIPMODE")
                 OSMODE   (getvar "OSMODE")
                 *error*  drunkard:error
           )
           (setvar "CMDECHO" 0)
           (setvar "BLIPMODE" 0)
           (setvar "OSMODE" 0)
           (command "._undo" "_be")
           ;;
           (drawPolyline plist nil)
           ;;
           (command "._undo" "_e")
           (setvar "CMDECHO" CMDECHO)
           (setvar "BLIPMODE" BLIPMODE)
           (setvar "OSMODE" OSMODE)
    )
  )
  (princ)
)

このように、ひな形から最低限のコードで簡単にカスタム入力関数が作成できます。

このコマンドで使用するコードは、こちらに STAR コマンドとの重複含めてひとまとめとしてあります。コピーアンドペーストして試してみてください。