手冊實戰(zhàn):從數(shù)學運算到選擇集操作的核心技巧)
簡介《LISP函數(shù)大全分類手冊》是一份面向AutoLISP初學者與CAD二次開發(fā)者的函數(shù)速查資料以PDF形式系統(tǒng)梳理了AutoLISP常用函數(shù)幫助讀者在編寫和調(diào)試LISP程序時快速定位語法與用途。資源包內(nèi)僅含1個PDF文件大小約47KB輕量便攜適合隨時查閱。手冊按功能劃分為數(shù)學運算、檢驗與邏輯運算、轉(zhuǎn)換運算、列表處理等模塊逐條列出函數(shù)原型與返回值說明例如數(shù)學運算中的加減乘除、絕對值、三角函數(shù)與最大公約數(shù)列表處理中的car、cadr、cons、mapcar、nth等并延伸至字符串、文件、等待輸入及幾何運算等常用函數(shù)。內(nèi)容預覽顯示其條目清晰、示例直接便于對照記憶。目前已有765人學習下載適合需要系統(tǒng)掌握AutoLISP函數(shù)體系、提升CAD編程效率的開發(fā)者作為案頭參考。1. 從一份函數(shù)清單說起AutoLISP 手冊到底能解決什么問題如果你在 CAD 二次開發(fā)這條路上待過一陣大概率遇到過這種場景想寫個批量改圖層的小工具腦子里知道大概要遍歷選擇集、改對象屬性但具體用哪個函數(shù)、參數(shù)順序是什么、返回值是列表還是點對全得靠翻舊筆記或者去搜零散帖子。手邊那份《LISP函數(shù)大全分類手冊》就是干這個用的——它不是教程是一份按功能域歸類的 AutoLISP 函數(shù)速查表覆蓋數(shù)學運算、邏輯判斷、類型轉(zhuǎn)換、列表處理、字符串與文件、交互輸入、幾何運算、對象與選擇集操作、符號表、系統(tǒng)變量控制、流程控制、錯誤處理、顯示打印、內(nèi)存管理一直到 ADS/ARX 外部函數(shù)和 3D 渲染相關調(diào)用粗算下來十九個大類、三百多個函數(shù)條目。這份手冊適合兩類人一類是剛接觸 AutoLISP、需要一份能按功能查的索引來建立函數(shù)地圖的新手另一類是有一定經(jīng)驗、但記不全冷門函數(shù)比如boole的位運算參數(shù)、trans的坐標系統(tǒng)轉(zhuǎn)換、nentsel返回的嵌套實體信息結(jié)構的老手。它不教你編程思想但能讓你在寫代碼時少翻幾次文檔。下面我按「怎么把這份清單用起來」的思路拆幾個關鍵模塊的實操方式和踩坑點。2. 數(shù)學與邏輯函數(shù)別被「簡單」兩個字騙了2.1 數(shù)學運算函數(shù)的參數(shù)陷阱手冊里數(shù)學部分列了二十來個函數(shù)看起來最沒技術含量但實際寫幾何計算時翻車最多的恰恰是這里。先看基礎四則和取整;; 基礎數(shù)學運算示例 (setq a 10.0) (setq b 3) ;; 除法AutoLISP 的 / 是實數(shù)除法不是整數(shù)截斷 (/ a b) ; 返回 3.33333 (/ 10 3) ; 返回 3兩個整數(shù)相除結(jié)果為整數(shù) (/ 10.0 3) ; 返回 3.33333 ;; fix 截斷取整不是四舍五入 (fix 3.9) ; 返回 3 (fix -3.9) ; 返回 -3 ;; rem 取余符號跟隨被除數(shù) (rem 10 3) ; 返回 1 (rem -10 3) ; 返回 -1 ;; gcd 最大公約數(shù) (gcd 48 36) ; 返回 12這里第一個坑就是整數(shù)除法和實數(shù)除法的區(qū)別。(/ 10 3)返回 3 而不是 3.333因為兩個參數(shù)都是整數(shù)AutoLISP 會做整數(shù)除法。很多人在算比例、算間距的時候忘了這一點結(jié)果精度直接丟掉。解決辦法很簡單至少讓一個操作數(shù)是實數(shù)寫成(/ 10.0 3)或者(/ 10 3.0)。第二個坑是fix的行為。它不是四舍五入是直接截斷小數(shù)部分。(fix 3.9)得 3(fix -3.9)得 -3。如果你需要四舍五入得自己寫(fix ( x 0.5))但注意負數(shù)情況下這個公式會出問題負數(shù)要寫成(fix (- x 0.5))。第三個坑是三角函數(shù)的角度單位。手冊里寫得很清楚sin、cos、atan的參數(shù)和返回值都是弧度不是角度。但實際畫圖時用戶輸入的角度往往是度數(shù)所以中間必須做轉(zhuǎn)換;; 角度轉(zhuǎn)弧度 (defun dtr (deg) (* deg (/ pi 180.0)) ) ;; 弧度轉(zhuǎn)角度 (defun rtd (rad) (* rad (/ 180.0 pi)) ) ;; 使用示例求 30 度的正弦值 (sin (dtr 30)) ; 返回 0.5dtr和rtd這兩個轉(zhuǎn)換函數(shù)幾乎是每個 AutoLISP 項目的標配建議直接放進你的工具函數(shù)庫里。pi是內(nèi)置常數(shù)不需要自己定義直接用就行。2.2 邏輯比較函數(shù)的返回值語義邏輯函數(shù)這塊手冊列了、/、、、、、and、or、not、eq、equal等??雌饋矶际潜容^但返回值語義有細微差別用錯了會導致條件判斷走錯分支。;; 數(shù)值比較 ( 1 1) ; 返回 T ( 1 1.0) ; 返回 T數(shù)值相等類型不同也返回 T ( abc abc) ; 返回 T字符串比較 ( abc ABC) ; 返回 nil字符串比較區(qū)分大小寫 ;; eq 和 equal 的區(qū)別 (setq list1 (1 2 3)) (setq list2 (1 2 3)) (eq list1 list2) ; 返回 nileq 比較的是同一對象引用 (equal list1 list2) ; 返回 Tequal 比較的是內(nèi)容 ;; equal 可以帶差量參數(shù)用于浮點數(shù)比較 (equal 1.0 1.0001) ; 返回 nil (equal 1.0 1.0001 0.001) ; 返回 T差量在 0.001 以內(nèi)視為相等eq和equal的區(qū)別是新手最容易混淆的地方。eq比較的是兩個表達式是否指向同一個對象引用相等equal比較的是內(nèi)容是否相同值相等。對于列表和字符串幾乎總是應該用equal。浮點數(shù)比較更是必須用equal帶差量參數(shù)因為浮點運算的精度誤差會讓返回意料之外的結(jié)果。and和or的短路特性也值得注意。(and a b c)會從左到右求值遇到第一個 nil 就停止并返回 nil(or a b c)遇到第一個非 nil 就停止并返回那個值。這意味著你可以把開銷大的判斷放在后面利用短路來優(yōu)化性能。3. 列表處理與類型轉(zhuǎn)換AutoLISP 的核心操作3.1 列表處理函數(shù)的組合用法AutoLISP 的名字里就帶著 List列表處理是這門語言最核心的部分。手冊第四章列了car、cadr、caddr、cdr、cons、list、append、length、nth、member、assoc、subst、reverse、mapcar、foreach這些函數(shù)。單獨看每個都不難但組合起來能做的事情很多。;; 構建和拆解列表 (setq pts (list (0 0) (10 0) (10 10) (0 10))) ;; car 取第一個元素cdr 取剩余部分 (car pts) ; 返回 (0 0) (cdr pts) ; 返回 ((10 0) (10 10) (0 10)) ;; cadr 等價于 (car (cdr ...))取第二個元素 (cadr pts) ; 返回 (10 0) ;; caddr 取第三個元素 (caddr pts) ; 返回 (10 10) ;; nth 按索引取元素索引從 0 開始 (nth 2 pts) ; 返回 (10 10) ;; length 取列表長度 (length pts) ; 返回 4 ;; append 合并列表 (append (1 2) (3 4)) ; 返回 (1 2 3 4) ;; cons 將元素添加到列表頭部 (cons (5 5) pts) ; 返回 ((5 5) (0 0) (10 0) (10 10) (0 10)) ;; reverse 反轉(zhuǎn)列表 (reverse pts) ; 返回 ((0 10) (10 10) (10 0) (0 0))這里有個容易踩的坑append和cons的區(qū)別。append把兩個列表拼接成一個新列表cons把單個元素加到列表頭部。如果你用(cons (1 2) (3 4))得到的是((1 2) 3 4)而不是(1 2 3 4)。想要拼接效果必須用append。另一個坑是nth的索引從 0 開始不是從 1 開始。寫循環(huán)的時候如果忘了這一點會漏掉第一個元素或者越界。mapcar是列表處理里最強大的函數(shù)之一它把某個函數(shù)應用到多個列表的對應元素上返回結(jié)果列表;; 對列表中每個元素加 1 (mapcar 1 (1 2 3 4)) ; 返回 (2 3 4 5) ;; 兩個列表對應元素相加 (mapcar (1 2 3) (10 20 30)) ; 返回 (11 22 33) ;; 提取點列表中的所有 X 坐標 (setq xs (mapcar car pts)) ; 返回 (0 10 10 0) ;; 提取所有 Y 坐標 (setq ys (mapcar cadr pts)) ; 返回 (0 0 10 10)用mapcar配合car/cadr來批量提取坐標是處理點列表的標準操作。比手寫foreach循環(huán)簡潔得多而且不容易出錯。3.2 類型轉(zhuǎn)換函數(shù)的精度與格式控制手冊第三章的轉(zhuǎn)換函數(shù)在實際項目中出場率極高尤其是rtos、atof、itoa、angtos這幾個。CAD 開發(fā)中經(jīng)常需要在數(shù)值和字符串之間來回轉(zhuǎn)換——比如把計算結(jié)果寫到圖紙標注里或者從用戶輸入的字符串里解析出數(shù)值。;; 實數(shù)轉(zhuǎn)字符串控制小數(shù)位數(shù) (rtos 3.1415926 2 2) ; 返回 3.14模式 2 為十進制精度 2 位小數(shù) (rtos 3.1415926 2 4) ; 返回 3.1416 (rtos 100.0 2 0) ; 返回 100 ;; 字符串轉(zhuǎn)實數(shù) (atof 3.14) ; 返回 3.14 (atof abc) ; 返回 0.0無法解析時返回 0 ;; 字符串轉(zhuǎn)整數(shù) (atoi 42) ; 返回 42 (atoi 3.14) ; 返回 3截斷小數(shù)部分 ;; 整數(shù)轉(zhuǎn)字符串 (itoa 42) ; 返回 42 ;; 角度轉(zhuǎn)字符串 (angtos 1.5708 0 2) ; 返回 90.00模式 0 為十進制度數(shù) (angtos 1.5708 1 4) ; 返回 90d00\模式 1 為度分秒rtos的模式參數(shù)決定了輸出格式模式 1 是科學計數(shù)法模式 2 是十進制模式 3 是工程格式模式 4 是建筑格式。精度參數(shù)控制小數(shù)位數(shù)。這兩個參數(shù)配合使用可以精確控制輸出字符串的樣子。atof和atoi在解析失敗時不會報錯而是返回 0。這個行為有時候是便利有時候是隱患。如果你需要嚴格校驗用戶輸入不能只靠atof的返回值來判斷得先用distof或者自己寫正則檢查。trans函數(shù)是坐標轉(zhuǎn)換的關鍵手冊里只給了一行說明但實際用起來參數(shù)含義需要仔細理解;; 將用戶坐標系中的點轉(zhuǎn)換到世界坐標系 (trans (10 20 0) 1 0) ; 從 UCS 轉(zhuǎn)到 WCS ;; 將世界坐標系中的點轉(zhuǎn)換到當前用戶坐標系 (trans (10 20 0) 0 1) ; 從 WCS 轉(zhuǎn)到 UCS ;; 將點從當前坐標系轉(zhuǎn)到指定視口坐標系 (trans pt 1 2) ; 從 UCS 轉(zhuǎn)到 DCS顯示坐標系trans的第二個參數(shù)是原坐標系代碼第三個參數(shù)是新坐標系代碼。常用的代碼有0 表示 WCS世界坐標系1 表示當前 UCS用戶坐標系2 表示 DCS顯示坐標系。寫涉及坐標拾取和轉(zhuǎn)換的代碼時如果不做trans轉(zhuǎn)換拿到的點可能跟你預期的不一樣尤其是在用戶旋轉(zhuǎn)了 UCS 之后。4. 對象操作與選擇集從「拿到對象」到「改對屬性」4.1 選擇集構建的過濾技巧手冊第九章的選擇集函數(shù)是 CAD 批量操作的基礎。ssget是最核心的一個它的過濾列表參數(shù)決定了你能多精確地篩選目標對象。;; 選取所有實體 (setq ss (ssget X)) ;; 選取所有圓 (setq ss (ssget X ((0 . CIRCLE)))) ;; 選取圖層 WALL 上的所有直線 (setq ss (ssget X ((0 . LINE) (8 . WALL)))) ;; 選取半徑大于 50 的圓 (setq ss (ssget X ((0 . CIRCLE) (-4 . ) (40 . 50.0)))) ;; 選取顏色為紅色的對象顏色號 1 (setq ss (ssget X ((62 . 1)))) ;; 組合條件圖層為 WALL 或 DOOR 上的直線 (setq ss (ssget X ((0 . LINE) (-4 . OR) (8 . WALL) (8 . DOOR) (-4 . ENDOR))))過濾列表的格式是點對(組碼 . 值)。組碼 0 是對象類型8 是圖層名62 是顏色號40 是圓的半徑。邏輯運算符用-4組碼配合字符串表示OR、AND、NOT、、、等。這些運算符可以嵌套但要注意配對閉合。一個常見的坑是ssget的過濾條件對不同類型的對象行為不一致。比如組碼 40 對圓是半徑對文字是字高對多段線是全局寬度。如果你寫了一個過濾條件但沒指定對象類型可能會選到意料之外的東西。所以過濾列表里最好總是帶上(0 . 類型)。遍歷選擇集的標準寫法;; 遍歷選擇集統(tǒng)計對象數(shù)量 (setq ss (ssget X ((0 . LINE)))) (setq count (sslength ss)) (setq i 0) (while ( i count) (setq ent (ssname ss i)) (setq edata (entget ent)) ;; 在這里處理每個對象 (setq i (1 i)) )sslength返回選擇集中的對象數(shù)量ssname按索引取出對象名索引從 0 開始entget取出對象的屬性列表。這個三段式循環(huán)是 AutoLISP 批量處理的標準骨架幾乎每個工具都會用到。4.2 實體屬性修改的正確姿勢拿到實體數(shù)據(jù)列表之后修改屬性的流程是用entget取出數(shù)據(jù)用subst替換要改的組碼值用entmod寫回最后用entupd刷新顯示。;; 修改選中直線的圖層 (defun c:chglayer ( / ent edata newdata) (setq ent (car (entsel \n選擇要修改的直線: ))) (setq edata (entget ent)) ;; 替換組碼 8圖層名的值 (setq newdata (subst (cons 8 NEWLAYER) (assoc 8 edata) edata)) ;; 寫回修改后的數(shù)據(jù) (entmod newdata) ;; 刷新實體顯示 (entupd ent) (princ \n圖層已修改。) (princ) )這段代碼里subst的用法是關鍵(subst 新項 舊項 列表)會在列表中查找舊項并替換為新項。assoc用來找到組碼對應的點對。順序不能反subst的第一個參數(shù)是新值第二個是舊值。entmod修改數(shù)據(jù)后屏幕上的圖形不一定會立即刷新尤其是修改了頂點坐標或者文字內(nèi)容的情況。entupd強制刷新指定實體的顯示。如果修改的是塊定義內(nèi)部的實體還需要用entupd傳入塊名來刷新整個塊。另一個坑是entmod不能修改某些只讀屬性比如實體的句柄組碼 5和某些擴展數(shù)據(jù)。嘗試修改這些屬性會靜默失敗不報錯但也不生效。遇到修改不生效的情況先檢查是不是動了不該動的組碼。5. 避坑與排查那些手冊不會告訴你的翻車現(xiàn)場5.1 變量未聲明導致的「玄學」行為AutoLISP 的變量默認是全局的除非你在defun的變量列表里用/分隔聲明為局部變量。不聲明局部變量的后果是函數(shù)執(zhí)行完后變量還留在內(nèi)存里下次調(diào)用時可能帶著上次的殘留值導致結(jié)果時對時錯。;; 錯誤寫法i 和 ent 是全局變量 (defun c:badfunc () (setq i 0) (while ( i 10) (setq ent (ssname ss i)) (setq i (1 i)) ) ) ;; 正確寫法i 和 ent 聲明為局部變量 (defun c:goodfunc ( / i ent) (setq i 0) (while ( i 10) (setq ent (ssname ss i)) (setq i (1 i)) ) )現(xiàn)象是函數(shù)第一次運行正常第二次運行結(jié)果不對。原因是第一次運行后i的值留在了內(nèi)存里第二次運行沒有重新初始化。解決辦法就是在defun的變量列表里用/把局部變量列出來。這個坑幾乎每個 AutoLISP 新手都會踩一次而且因為現(xiàn)象不穩(wěn)定排查起來很費時間。5.2 選擇集為空時的靜默失敗ssget在沒有選中任何對象時返回nil但后續(xù)的sslength和ssname對nil操作不會報錯而是返回 0 或者nil。這導致代碼看起來在正常運行實際上什么都沒做。;; 有問題的寫法沒有檢查 ssget 返回值 (setq ss (ssget X ((0 . CIRCLE)))) (setq n (sslength ss)) ; ss 為 nil 時返回 0 (princ (strcat \n找到 (itoa n) 個圓。)) ;; 穩(wěn)妥的寫法先判斷選擇集是否有效 (setq ss (ssget X ((0 . CIRCLE)))) (if ss (progn (setq n (sslength ss)) (princ (strcat \n找到 (itoa n) 個圓。)) ) (princ \n沒有找到符合條件的圓。) )現(xiàn)象是程序不報錯但也不產(chǎn)生任何效果。原因就是選擇集為空后續(xù)循環(huán)一次都沒執(zhí)行。解決辦法是在ssget之后立即用if判斷返回值為空時給出提示或者提前返回。5.3 坐標轉(zhuǎn)換遺漏導致的偏移在用戶自定義坐標系下拾取的點如果不做trans轉(zhuǎn)換就直接用于entmake或者command調(diào)用畫出來的東西會偏離預期位置。;; 有問題的寫法直接使用 getpoint 的返回值 (defun c:badcircle ( / pt) (setq pt (getpoint \n指定圓心: )) (command CIRCLE pt 50) ; UCS 旋轉(zhuǎn)時位置會偏 ) ;; 正確的寫法轉(zhuǎn)換到 WCS 再使用 (defun c:goodcircle ( / pt) (setq pt (getpoint \n指定圓心: )) (setq pt (trans pt 1 0)) ; 從 UCS 轉(zhuǎn)到 WCS (command CIRCLE pt 50) )現(xiàn)象是在默認 WCS 下運行正常用戶一旦旋轉(zhuǎn)了 UCS畫出來的圓就跑到別的地方去了。原因是getpoint返回的是當前 UCS 下的坐標而command調(diào)用的CIRCLE命令在某些情況下期望的是 WCS 坐標。解決辦法是在拾取點之后、使用之前統(tǒng)一用(trans pt 1 0)轉(zhuǎn)到 WCS。5.4 字符串比較的大小寫陷阱用于字符串比較時區(qū)分大小寫但用戶輸入往往大小寫不固定。用直接比較用戶輸入和預設字符串很容易因為大小寫不一致而判斷失敗。;; 有問題的寫法區(qū)分大小寫 (if ( (getstring \n輸入選項(Y/N): ) Y) (princ \n你選擇了是。) (princ \n你選擇了否。) ) ;; 穩(wěn)妥的寫法統(tǒng)一轉(zhuǎn)大寫再比較 (if ( (strcase (getstring \n輸入選項(Y/N): )) Y) (princ \n你選擇了是。) (princ \n你選擇了否。) )現(xiàn)象是用戶明明輸入了 Y程序卻走了否的分支。原因是用戶輸入的是小寫 y而比較時 y 不等于 Y。解決辦法是用strcase把輸入統(tǒng)一轉(zhuǎn)成大寫或小寫再比較。strcase的第二個參數(shù)傳T轉(zhuǎn)小寫不傳或傳nil轉(zhuǎn)大寫。5.5 文件句柄未關閉導致的鎖定用open打開文件讀寫后如果中間發(fā)生錯誤導致close沒執(zhí)行文件會一直被鎖定后續(xù)操作無法訪問。;; 有風險的寫法出錯時文件不會關閉 (setq f (open data.txt w)) (write-line some data f) (close f) ;; 穩(wěn)妥的寫法用錯誤處理確保文件關閉 (defun c:writefile ( / f) (setq f (open data.txt w)) (if f (progn (write-line some data f) (close f) ) (princ \n無法打開文件。) ) )現(xiàn)象是程序運行一次后再次運行時報錯說文件無法訪問。原因是上一次運行中文件句柄沒有正常關閉。解決辦法是在打開文件后立即判斷句柄是否有效并在所有可能的退出路徑上都執(zhí)行close。更嚴謹?shù)淖龇ㄊ怯?error*自定義錯誤處理函數(shù)在出錯時關閉文件。6. 進階技巧用 mapcar 和 apply 寫出更緊湊的代碼6.1 mapcar 配合 lambda 實現(xiàn)匿名函數(shù)mapcar的第一個參數(shù)可以是函數(shù)名也可以是一個lambda表達式。用lambda可以避免為了一次性操作單獨定義命名函數(shù)。;; 用 lambda 對列表每個元素做平方 (mapcar (lambda (x) (* x x)) (1 2 3 4 5)) ; 返回 (1 4 9 16 25) ;; 用 lambda 提取點列表中的 Z 坐標 (setq pts ((0 0 0) (10 0 5) (10 10 3))) (mapcar (lambda (p) (caddr p)) pts) ; 返回 (0 5 3) ;; 用 lambda 對兩個列表做自定義運算 (mapcar (lambda (a b) ( (* a 2) b)) (1 2 3) (10 20 30)) ; 返回 (12 24 36)lambda的寫法是(lambda (參數(shù)) 表達式)前面的單引號不能少否則 AutoLISP 會試圖求值。這個技巧在需要對列表做一次性轉(zhuǎn)換時特別有用代碼量能減少一半。6.2 apply 將列表展開為參數(shù)apply把函數(shù)應用到參數(shù)列表上相當于把列表「展開」成函數(shù)的多個參數(shù)。;; 用 apply 求和 (apply (1 2 3 4 5)) ; 返回 15 ;; 用 apply 求最大值 (apply max (3 1 4 1 5 9)) ; 返回 9 ;; 用 apply 調(diào)用自定義函數(shù) (defun my-add (a b c) ( a b c)) (apply my-add (10 20 30)) ; 返回 60apply和mapcar的區(qū)別mapcar對列表的每個元素分別應用函數(shù)返回結(jié)果列表apply把整個列表作為參數(shù)傳給函數(shù)返回單個結(jié)果。當你有一個參數(shù)列表需要傳給一個接受多個參數(shù)的函數(shù)時apply就是正確的工具。6.3 用 assoc 和 subst 做數(shù)據(jù)驅(qū)動的屬性修改結(jié)合assoc、subst和mapcar可以寫出很緊湊的批量屬性修改代碼;; 批量修改選擇集中所有對象的顏色 (defun c:batchcolor ( / ss i ent edata) (setq ss (ssget X ((0 . LINE,CIRCLE,ARC)))) (if ss (progn (setq i 0) (while ( i (sslength ss)) (setq ent (ssname ss i)) (setq edata (entget ent)) ;; 如果已有顏色組碼則替換沒有則添加 (if (assoc 62 edata) (setq edata (subst (cons 62 1) (assoc 62 edata) edata)) (setq edata (append edata (list (cons 62 1)))) ) (entmod edata) (setq i (1 i)) ) (princ (strcat \n已修改 (itoa (sslength ss)) 個對象的顏色。)) ) (princ \n沒有找到符合條件的對象。) ) (princ) )這段代碼里有個細節(jié)值得注意如果實體本身沒有顏色屬性組碼 62assoc返回nil此時不能用subst替換而應該用append把新的點對追加到數(shù)據(jù)列表末尾。這個判斷分支是很多批量修改工具容易忽略的地方——對于「默認顏色」的實體直接subst會失敗因為找不到要替換的舊項。從那以后我每次寫批量修改屬性的代碼都會先檢查目標組碼是否存在存在就subst不存在就append這個習慣幫我省了不少調(diào)試時間。希望幫到你。本文還有配套的精品資源點擊獲取