2014年7月14日 星期一

MIRROR 指令修改



    以前看過有人問過一個問題,MIRROR 的指令很難用,有沒有辦法簡單一點,我們就先來看看 MIRROR 的指令有哪些步驟,輸入 MIRROR

   選取物件 (含確定動作)
   選取第一鏡射點 (含鎖點及確認)
   選取第一鏡射點 (含鎖點及確認)
   決定要不要刪除原物件 (輸入 “Y” “N”)

    以土木而言,常用的鏡射不外乎水平或垂直鏡射,如果再將要不要保留原物件組合,哪麼 MIRROR 指令可拆成 4 種指令,排列組合一下

    水平鏡射,保留原物件
    水平鏡射,刪除原物件
    垂直鏡射,保留原物件
    垂直鏡射,刪除原物件

   這種問題不用動用到 LISP,直接改寫 MIRROR 指令即可,例如水平鏡射,保留原物件 的指令就是

(command "MIRROR" (ssget) "" PAUSE "@0,100000" "N")

(ssget) 讓你選取要鏡射的物件,後面兩個雙引號幫你按下確認鍵,PAUSE 讓你選取垂直基準線的第一點,"@0,100000" 則幫你定義垂直基準線的第二點,"N" 當然是 MIRROR 指令最後刪除與否選項。

    簡單吧!

2014年7月4日 星期五

一窺 AutoCAD 群碼的鑰匙

你想過在 AutoCAD 一個個繪出的圖元,是怎樣記錄在 AutoCAD 檔案內的嗎? 如果你在 AutoCAD 中寫下一個字,就 TEST 吧,我問你如何描述這的圖元,你會說這個包含有

1.    文字的位置
2.    文字的內容 ( TEST)
3.    文字的圖層 (假設是在 0 )
4.    文字的字形 ….

    是的,AutoCAD 也是這麼記錄下來的,而且比你想像的資料還要多,那如何知道這些資訊,用 LIST 指令 !! 現在應該沒有人會用這種老古董指令,用性質  _properties指令就可以將文字性質一覽無遺,不過這是經過包裝的,在 AutoCAD LISP 內要有更嚴謹的作法

    (entget(car(entsel)))

   這是三個指令串起來的指令 (不愧為 LISP),在命令列中執行它,CAD會要求你選取一個圖元,然後將圖元的資料傾瀉而出,不廢話就取選取 "TEST" 這個文字,然後 ….

((-1 . <圖元名稱: 7ef62298>) (0 . "TEXT") (330 . <圖元名稱:
7efddcc0>) (5 . "1373") (100 . "AcDbEntity") (67 . 0) (410 . "Model") (8 . "0")
(100 . "AcDbText") (10 352.068 167.112 0.0) (40 . 90.0) (1 . "TEST") (50 . 0.0)
(41 . 0.8) (51 . 0.0) (7 . "C") (71 . 0) (72 . 0) (11 0.0 0.0 0.0) (210 0.0 0.0
1.0) (100 . "AcDbText") (73 . 0))

   是的,這是 "TEST" 的本尊,其中組成元件有圖元名稱 (編號),圖元分類 (TEXT),圖元所在圖層(8 . "0"),文字基準點 (10 352.068 167.112 0.0) …,接下來就是如何取出資料,運算,修改更新的動作了。

2014年6月16日 星期一

Visual Lisp 的點資料

   既然是 AutoCAD,必然牽涉到點的輸出入,程式或使用者給你一個點資料,如何取出點的 x, y , z 三軸座標數值;程式計算出的數值,如何交回給 CAD 讓它畫出來。
    先來看怎們取出點資料,如果你在命令列輸入 (getpoint),再螢幕上點選一點,這時程式會回應一個像下面的資料 (串列)

    (982.466 568.978 0.0)

    直覺的,它一定是 x, y , z 值,那怎麼拿出來,先得講 LISP 如何操作串列,基本上先認識 (car) (cadr) (caddr) 三個兄弟函數,老大 car 取出串列第一個內容,所以一個點 pt 的內容為 (1 2 3),那麼 (car pt) 便傳回 1,同理 (cadr pt) 傳回 2,同理 (caddr pt) 傳回 3,此時再設定變數,便可以得到 x, y ,z 參數

   (setq x (car pt)
       y (cadr pt)
       z (caddr pt)
   )

    便可對 x, y, z 三個變數進行運算,但要說明的是在 LISP 語言內不只以上三個函數,例如 (cdr pt) 傳回 (2 3),很明顯的是傳回第二個到最後一個串列內容;(nth 2 pt) 傳回 3(nth) 函數則依據 index 2 傳回內容 3,當然串列的第一個 index 0


    當有了新的 x y z 值,要將其組合成一個點資料傳給函數,一樣是 LISP 語言內串列的操作函數 (list),沒啥技巧

(list x y z)

   就可以了,如果在 2D 環境繪圖,那麼 z 可以省略,不需再設定。

   下面一個例子,程式要求使用者輸入一點,然後程式劃出由輸入點開始長度 10030 度角的直線

(defun c:aline( / pt x y _osmode)
    (setq _osmode (getvar "OSMODE"))
    (setvar "OSMODE" 0)
 
    // 取得起始點
    (setq pt (getpoint "輸入起始點\n")
          x (car pt)
          y (cadr pt)
    )
    // 計算終點
    (setq x (+ x (* 100 (cos (/ pi 6))))
        y (+ y (* 100 (sin (/ pi 6))))
    )
    // 繪出
    (command "LINE" pt (list x y) "")

    (setvar "OSMODE" _osmode) 
)

當然,實際程式設計時會使用 (polar) 函數
    (command  "LINE" pt (polar pt (/ pi 6) 100) "")

2014年6月13日 星期五

繪圖前的預備動作

     LISP 繪圖前要有還要有預備動作?? 當然不是作作熱身操上上廁所種,記得 Visual LISP 可以使用 (command) 指令嗎 ? 雖然很方便,只要會操作 AutoCAD 就會使用這個指令,但是,CAD 繪圖好像不止只有繪圖動作,還有環境設定的問題,例如圖層,鎖點,標註比例,文字尺寸等等等...,當你使用 command 指令時,你已經承認目前 AutoCAD 的環境設定就你()想要的,例如

    (command "LINE" pt1 pt2 "")

    如果 CAD 現在圖層是 "0",線形式 "Continues",那麼這條線的預設值便已訂好,如果你要別的圖層,那要先更改圖層,例如先執行

    (setvar "CLAYER" "LINE")

   把預設的圖層改為 "LINE",下一個物件若不是在 "LINE" 圖層上,那麼請再換一次,畢竟 CAD 不是你肚子裡的蛔蟲。

   還有什麼問題,鎖點,若在繪圖時鎖點模式打開,那麼鎖點會取代程式輸入值,明明畫的點應該形成四邊形,螢幕上卻跑出四不像,所以繪圖前先把鎖點關閉 (當然不是按 F9)

    (setvar "OSMODE" 0)

   那麼程式完成時呢?抱歉,鎖點就被關閉了,如果還想恢回覆繪圖前狀態,先把鎖點的參數記下來,最後在設定一次

    (setvar _osmode (getvar "OSMODE"))
    (setvar "OSMODE" 0)

    ..... (繪圖指令)....

    (setvar "OSMODE" _osmode)

    還有問題,是關於對話盒的問題,例如開啟舊檔對話盒,圖塊屬性對話盒,若在 LISP 執行過程跳出,則程式會中斷,等待使用者輸入並關閉對話盒後才會繼續執行,若不想被打斷,那麼 "FILEDIA" "ATTDIA" 也記得要關。

    若對指令欄中一連串輸出厭煩 (或是不想讓使用者知道執行過程),先設定

    (setvar "CMDECHO" 0)

   上面這些拉拉雜雜的問題都是跟 (command) 指令有關,所以 Visual LISP 內還有一個 (entmake) 指令,以設定群碼內容方式繪圖,就沒有上面的問題 (但是並如容易掌控)

    最後有一個動作也是值得設定的,就是 UNDO 指令,在所有動作開始前設定

    (command "UNDO" "M")

   若程式執行結果不如預期,例如畫出的圖形與現有圖形重疊,而你的程式已經執行了幾百個動作,又無法放棄不存檔,那心中的無奈跟手指按 CTRL+Z 的痛苦可是無以覆加
   如果程式先設定 UNOD MARK 位置,那麼此時只要執行 UNDO/B,就可回到 LISP 執行前狀態,當然付出的代價是程式執行速度,不過,還是那句話,我輩能寫的出的程式,通常不會有這個問題滴。

2014年5月10日 星期六

Viusal Lisp 輸入 與 EXCEL 溝通

     AutoCAD 跟 EXECEL 溝通應該是很多工程師的夢想,直接由活頁簿抓取資料或是將圖檔中特定資訊寫入 EXCEL 中是相當便利的,用傳統 VISUAL LISP 函數是無法達成 Windows 程式間的溝通的動作,後期 AutoCAD 的 valx- 開頭函數就可以達成,怎麼做,事實上有好心人已經寫好整套函數放在網路上讓人下載,路徑如下

     http://web2.airmail.net/terrycad/LISP/GetExcel.lsp

有緣人得之,我改寫部份程式碼符合我的需求,測試後可正常運作。



2014年5月5日 星期一

我的程式集 型鋼彎矩接頭詳圖

     我們這一行的常常需要畫一些鋼結構接頭,一般會有接頭標準圖使用,有人會自己蒐集成資料庫,我直接寫成 LISP 程式;這個程式就是梁彎矩接頭的詳圖繪製,使用很簡單,點選型鋼號碼後點選擇放置點後就可以了。




2014年4月24日 星期四

Visual Lisp 的輸入 對話盒範例二

    雖然又是個簡單的例子,但是牽扯東西很多,先來談談 LISP,LISP 是個古老的語言 (噢,又說了一次),他是 LIST PROCESSOR 的縮寫,LIST 是什麼 ? LIST 直翻成串列,簡單講,就是將一串東西  (資料) 串起來,現實中可能馬上浮現日本的麻糬,魚丸...,等等,但 LISP 的資料更像烤肉串,上面可能有小香腸、青椒、培根等等美食 (不同種類資料),而香腸+大蒜也是這一串食物中的一小串,OK,簡單講,LISP 主要將不同型態的資料串接形成一串 (一筆資料),你也許會說,我記得 C 好像也可以,我要說的是,那個年代,這可是革命性的概念。


      那要這種混和形式的資料幹什麼呢 ?  講到 LISP  又得再提到一樣東西  A.I. ,你的腦袋中是否又浮起一個小男孩的畫面,當初開發 LISP 主要就是要處理人工智慧的問題,這時要處理的就不只是數字文字而已,而是要處理現實世界各式各項的資訊,幾十年過去了,LISP 不再是主流語言,甚至於在 A.I. 領域中也不在獨尊,而因為 Autodesk 選擇開發語言一個決定,讓一個搞土木的竟然也跟  LISP 發生關係,再回頭看,用 LISP 處理電腦輔助設計問題,又何嘗不是另一個意外。


     好吧!來看看串列的資料長的什麼樣子,假設有個串列,裡面有三個資料,"A" "B" "C" 三個字元形成一個串列就叫 CHAR

     (setq CHAR '("A" "B" "C"))

讓它執行 (例如在 AUTOCAD VLIDE 中的主控台),傳回

    ("A" "B" "C")

     又如何,很像一維矩陣啊
     記不記得 LISP 的一項特徵 S 表示式 (S-expression),例如 1 + 2 就寫成 (+ 1 2),事實上這個格式跟串列 (資料) 是同一型式,LISP 靠此達成程式碼跟資料格式統一。 

    換句話說,如果把 (+ 1 2),視為一個串列,第一個元素 + 就是函數名稱,第二個元素 1、第三個元素 2 就是輸入參數;程式在運作時,只要參數沒取完,就一值執行(累加),所以在 LISP 中 1 + 2 + 3 + 4 + 5 不是寫成

    (+ 1 (+ 2 (+ 3 (+ 4 5))))

而是

    (+ 1 2 3 4 5)

反而比傳統方式節省字元空間 (雖然不重要)

      另外一個特色就是,傳入函數的變數是什麼形式,不就是變數嗎? LISP 可以將函數當變數傳入函數,到這裡,你應該已經頭昏腦脹了?先停下來,來看看今天的主題 (是的),這才是這篇文章要講的,在視窗對話盒中有一個元件叫 popup_list (例如我的程式集中 型鋼剖面程式內型鋼型號選取)


    這些元件中有預設的選項,按下它後便可選取預設的項目,那麼程式怎麼知道預設的
項目是哪些呢?當然是要程式中給的,怎們給?

        (setq CHAR '("A" "B" "C"))
        (start_list
"popup_list_char")
        (mapcar 'add_list CHAR)
        (end_list)
 
    第三行 (mpacar) 就是一個例子,這個函數第一個變數 add_list  就是一個函數 (注意在這裡是用 '不是用括號),他將一個項目加到 "popup_list_char" 中,但是 CHAR 中有三個項目,別的語言要怎麼做,首先要知道 CHAR 中有幾個元素,再來個迴圈執行 add_list,但是在 LISP 中就這一行,很多高手出手時就喜歡用這個函數,看的人一時間天旋地轉,日月無光。

    再舉個例子,有個串列(setq A '(1 2 3 4 )),想要將每個元素加一,LISP 執行就

     (mapcar '1+ A)       A  變成   (2 3 4 5)

    一行搞定 ,(mapcar) 就是將 A 這個串列中每一個元素 1 2 3 4 ,去執行 1+ 這個函數,這算是最淺顯的例子,再講下去我就要露出馬腳了,LISP 程式名稱來由也算名符其實,最後附上這個例子程式碼
   (PS  要知道使用者選那個選項,使用
           (get_tile "popup_list_char")
           傳回 "0" 第一項"1" 第二項,以此類推)


 //---------------------- Popup_list.lst
(defun C:Popup_list( / dcl_ID)
    ; 載入對話盒
    (setq dcl_ID (load_dialog "Popup_list.dcl"))
    (if( not (new_dialog "Popuplist" dcl_ID))
        (progn
        (alert "PopuList 對話盒無法載入!")
        (exit)
    )
    )
   
    (setq CHAR '("A" "B" "C"))
    (start_list "popup_list_char")
    (mapcar 'add_list CHAR)
    (end_list)
 
    ; 按下 "確定" 時離開
    (action_tile "accept" "(done_dialog 1)")
    ; 啟動對話盒
 
    (start_dialog)
    (unload_dialog dcl_ID)

    (gc)
)

 //---------------------- Popup_list.dcl
Popuplist : dialog {
    key = "popuplist";
        label = "Example" ;
    spacer_1;
    :popup_list {
        key = "popup_list_char";
        label = "字元:";
        width = 20;
    }
        spacer_1;
    ok_cancel;       
}

2014年4月21日 星期一

Visual Lisp 輸入 含對話盒程式例子


   這是一個有對話盒的 Visual Lisp 程式例子,簡單說,這是一個讓使用者可以輸入密碼,程式核對後確認是否通過的密碼對話盒,為了變點花樣,程式讓使用者可以輸入三次錯誤密碼的機會,超過三次便拒絕輸入。
    先然看看 DCL 檔案內容,就叫它 Password.DCL

PassWord : dialog {
            key = "password";
            label = "輸入密碼" ;
            : boxed_column{
                    spacer_1;
                :edit_box {
                        key = "edbox_pw";
                        label = "密碼:";
                        edit_width = 20;
                        password_char = "*";
                }
                spacer_1;
        }
            spacer_1;
        ok_cancel;
}

先從簡單的講
spacer_1;                對話盒內空一行
ok_cancel;              標準的確定與取消按鈕
:boxed_column{  }  劃出方框,無標題 (通常將相關聯之元件綁到一起)
:edit_box{ }             編輯盒 (是的,就是 Windows 中讓人輸入的方框)

再來於 { } 元件內特性定義,如 edit_box
        key = "edbox_pw";        元件關鍵字,供程式辨認
        label = "密碼:";            顯示予使用者之提示
        edit_width = 20;             編輯框之寬度
       password_char = "*";     密碼輸入使用,定義 "*" 元為顯示之符號 (輸入密碼的標準動作)
  另外注意末端 ; { } 成對等小細節。

那麼 ok_cancel; 的性質呢 ? 它是預設的按鈕,會顯示 "確定" "取消" 兩個按鈕,"確定" key = "accept"; "取消" key = "cancel"

DCL 檔案最前頭
PassWord : dialog {
                key = "password";
                label = "輸入密碼" ;

PassWord Lisp 呼叫辨認的
key = "password";         當然就是整個對話盒的關鍵字
label = "輸入密碼" ;       整個對話框的 TITLE

執行前 VLIDE  提供預覽功能,工具 / 介面工具(I) / 預覽編輯器的 DCL(E)



就可以看到對話盒樣子 (很陽春吧)



OK,再來看 LISP 程式碼部分

(defun C:Password( / dcl_ID)
    ; 載入對話盒
    (setq dcl_ID (load_dialog "password.dcl"))
    (if( not (new_dialog "PassWord" dcl_ID))
        (progn
            (alert "PassWord 對話盒無法載入!")
            (exit)
        )  
    )
    ; nCount 計算錯誤之次數
    ; sPw 正確之密碼
    (setq nCount 0
          sPW "Hello World")
    ; 設定對話方框輸入後之動作 (檢查密碼)
    (action_tile "accept" "(CheckPW)")
    (action_tile "cancel" "(done_dialog 0)")
    ; 啟動對話盒

    (start_dialog)
    (unload_dialog dcl_ID)

    (gc)
)
; 檢查密碼對話盒函數
(defun CheckPW ( / input)
     (setq input (get_tile "edbox_pw"))
     (if (= input sPW)
         (progn
             (alert "密碼正確")
             (done_dialog 1) ; 關閉對話盒
         )  
         (progn
             (setq nCount (1+ nCount))
             (set_tile "password" (strcat "密碼錯誤 "
                                          (itoa nCount)
                                          " "))
             (alert (strcat "密碼錯誤!\n輸入密碼為:" input))
             (if (>= nCount 3) ; 錯誤三次封鎖對話盒
                 (progn
                     (alert "密碼錯誤 3 \n拒絕接受輸入")
                     (mode_tile "accept" 1)
                     (mode_tile "edbox_pw" 1)
                 )  
             )
         )  
     )  
)

    標準呼叫程序就不說了,來看看
(action_tile "accept" "(CheckPW)")
(action_tile "cancel" "(done_dialog 0)")

    (action_tile) 函數即是設定對話盒受到 "刺激" 後之反應,(action_tile "accept" "(CheckPW)") 便是按下 "確定" 按鈕後要產生之反應,是的,就是執行 (CheckPW) 函數檢查密碼,(action_tile "cancel" "(done_dialog 0)"),按下 "取消" 後便關閉對話盒,並傳回失敗之訊息 0

    (CheckPW) 是自訂函數,執行檢查動作之函數,每次按下 "確定" 便執行一次,函數執行首先取得編輯盒中之文字,並儲存於 input 之變數中
(setq input (get_tile "edbox_pw"))

若輸入之密碼與內存之密碼相符 (sPW, "Hello Wrold"),顯示密碼正確,再跳出對話盒 (done_dialog 1) 傳回正確訊息。
(alert "密碼正確")
(done_dialog 1)



若錯誤,則先將錯誤次數累加一次 (nCount 是整體變數)
(setq nCount (1+ nCount))

再來,將錯誤次數顯示在以 "password" 為關鍵字之元件,是什麼呢?,回頭查查 DCL 之定義,就是整個對話盒的標題
(set_tile "password" (strcat "密碼錯誤 "
                                          (itoa nCount)
                                          " "))
 
並提出訊息告訴使用者密碼錯誤,並將錯誤之密碼提示與使用者。
(alert (strcat "密碼錯誤!\n輸入密碼為:" input))


最後檢查錯誤是否超過三次
(if (>= nCount 3)
….
)
若超過,先提示使用者錯誤已超過三次,無法再輸入。
(alert "密碼錯誤 3 \n拒絕接受輸入") 




      並將 "密碼" 編輯盒與 "確定" 之按鈕鎖住 (無法使用),只能按下 "取消" 按鈕。
(mode_tile "accept" 1)
(mode_tile "edbox_pw" 1)
(注意,參數 1 才是 disable)




這雖然是個小小小程式,但是 DCL 輸入該說的東西已有十之七八了。