|
|
马上注册,结识高手,享用更多资源,轻松玩转三维网社区。
您需要 登录 才可以下载或查看,没有帐号?注册
x
Autolisp编程心得
0 t7 ]; p% A+ I2 i1.养成良好的书写习惯
: y0 S. x# ^! k B$ t; P2 y 众所周知,Lisp是一种表结构语言,括号必须成对出现,在调试时往往为遗漏了一个括号大费周折,所以,养成一个好的编程习惯是学好Lisp所必须的。
7 L- @; s/ U2 i6 R, E: K: m ⑴选择一个较好的编辑器,这是一个基本条件,建议使用Visual Lisp编辑器或Lisplink等专用编辑器,此类编辑器可以对函数突出显示。
3 x2 e! l4 c% s' @5 _ ⑵按Lisp(DCL)专有格式书写,并经常对程序进行“格式化”,及时发现语法错误,并有利于调试是查找错误。( A0 p* A+ k' B4 E, I9 L
⑶使用自定义函数,并辅助以适当得注释,在较大程序中按功能使用自定义函数可以使得程序条理化。
$ w% L6 |, S( I2 X
1 }: s% Q* y7 C* x4 I2.函数中循环与转移的使用5 u3 ~! f7 `, `( E2 e: j
在高级语言中一般有类似“goto”的语句实现转移,在AutoLisp中没有转移的函数。我们可以使用自定义函数实现转移,用if及cond辅助实现条件转移。/ {2 U: a" j6 Z0 U5 y
当我们需要实现在满足一定条件时进行循环的功能,一般使用while函数,但有时需要判断的条件较复杂时,使用while函数往往不能实现或使得程序不够简洁。这时我们可以使用“转移”,将需要实现的功能作为(子)函数,使用恰当,可以在程序中任意“转移”。
0 Y- k- C( D2 C1 v 一般认为,当一段代码在不同处重复使用时,我们才会使用子函数定义,其实,利用函数的更大的优点是使得程序更加结构化。这就使得我们不必拘泥于程序中的循环语句,而使用函数的循环调用,辅助适当的判断,实现“转移”,如A->B->A。当然也可以进行自身调用,构成一个“标准的”循环。
u: p6 @( |5 K: P& s7 Y' t 如例一中,“程序执行完毕返回”与“空选返回”两种情况如果使用循环语句,其条件是完全不同的,而将函数本身作为子函数调用,程序简洁明了。
* H& g. [: j( n: [6 M% _# l0 ^/ K/ C# H+ [* i/ R( T
3.initget函数中关键字“ ”(空格)的使用
( O) V/ n; X" H- `6 ^. `$ T. @ 空格可以被用作关键字,一般多用来定义鼠标右键退出。! {- m }4 G$ \& |8 v- E* O( E
⑴当用户输入函数不支持控制位(如entsel、nentsel、nentselp)时,可直接使用“(initget " " )”。3 W S6 K, M2 j: O" p2 e
⑵当用户输入函数支持控制位(如getpoint等)时,可使用“(initget 1 " " )”禁止空输入,而将回车等空输入作为关键字使用。
7 t' {6 y3 m0 @' K$ ], M ⑶当同时使用其它关键字时,应该将空格作为一系列关键字的最后一个,用“(initget "C " )”(两个空格)调用,否则无效。9 n; P" V, I0 d5 s7 D+ |
见例一。
1 m3 q" P; m7 S6 b/ W0 u. V8 i************************************************
& D# Z: S+ f$ ~2 B8 r;;例一- (defun ett_ct()2 @9 I% ^5 N7 v! F0 Y
- (initget "C " ) ;关键字“C”及空格 ?1 E+ J! P2 \( [9 J
- (setq s0 (entsel "\n设置颜色C / 选取文本:" ))
5 P! k/ F6 B) V9 B3 c: C6 u) ^) I - (cond ( (= s0 "C" ) (ett_col)) ;转设置颜色子函数" q1 L" M4 R Q; y! c
- ( (= s0 "" ) nil) ;空格退出
1 S) g8 z }- K1 n. h - ( (and (= (type s0) 'LIST) ;选择实体5 t. R: }& m8 d. N- a
- (= (cdr (assoc 0 (entget (car s0)))) "TEXT" ) ;判别文本7 Q+ c. r) Z8 `8 l1 A
- )
8 e Y9 f2 W. ^; j1 ]" V, c - ... ;操作内容
3 m1 ]! o# |' A" R8 k- c; Q: k - (ett_ct) ;编辑后返回选择! k0 J/ a6 ]. e2 h% t2 r
- )7 `0 W4 P" K7 [) M" A+ l
- (t (ett_ct)) ;空选返回选择) P) R* W; |6 h
- )
" o( A% F$ f, F& I - )
复制代码 ************************************************
$ j- a3 n3 ~% V! x 有时需要进行复杂的判断,使用如“(= s0 "" )”语句可能不能准确判别输入的空格关键字与空选择,可以使用“(= (type s0) 'STR)”语句。
0 F8 I, T+ @! o6 ]3 P4 P4 J
/ @) m# N0 D ?; }& T* b4.Lisp的暂停与while的特殊使用* D. O9 C8 S6 O0 z! ^ V6 y
Lisp一般在交互输入时才会暂停,如果只需要实现屏幕显示暂停,可使用grread函数,grread函数对所有合法的输入设备均会作出反应,有时我们只希望对键盘有反应,可使用while函数进行循环。
, |8 r! R/ g A' L*******************- (princ "\nPress ENTER to continue:" )- \ r% ~$ c( S
- (while (/= (car (grread)) 2))
复制代码 *******************& v7 g! |/ b! k3 j# b! |4 @5 _
while用于满足一定条件的循环,其标准语法为:+ Q4 X, z5 K5 m! H- B
(while testexpr [expr...])
) {5 s# I0 ^, b; k3 I: ? 其中expr解释为“在 testexpr 为 nil 之前要求值的一个或多个表达式”,为可选项(在R14之前没有方括号,但仍为可选项)。
- u! X5 S, h- j5 Y1 e D 正常我们使用while时,总会有expr项,更多的时候,我们是为了expr项才会使用这种循环语句,所有我们往往有expr项是不可缺少的感觉。这里我们使用while函数的语法是while函数的特例,即没有expr项的情况。6 y( _8 {$ H1 \: S4 |* ~" @% I) J
如果希望对鼠标右键同时反应,可以使用:
7 x6 E. S. F+ s, v4 u3 c% Z2 M*******************- (princ "\nPress ENTER to continue:" )' Z7 ~, }# a" f" v1 w* B
- (while (and (/= (setq a(car (grread))) 2) ;键盘
$ o; b) P3 E% F+ d4 a4 i( S0 y4 ^ - (/= a 11) ;鼠标右键& T, t1 n. e) }5 ~7 h0 n7 M
- (SHORTCUTMENU=0)
8 }" U* k7 G& e0 I( P2 \ - (/= a 25) ;鼠标右键
! v. t0 J) @) T! s - (SHORTCUTMENU≠0)
* I9 _8 L, }. b - )( X3 P' m3 |2 A/ r3 f' b( y& g
- )
复制代码 *******************
- ^7 q1 Z2 f+ V3 t( |- S5 Z: m u
2 p; ~& w4 N) k5.输入距离+ U1 b/ V% u9 c
Lisp语言中输入距离的函数为getdist,但我们有时需要输入负值,有时需要在输入距离的同时得到角度,使用getdist函数就显得无能为力,这时,我们可以灵活使用其它交互输入函数如getpoint、getcorner等,通过计算得到我们所需要的值。+ [0 h+ w! B4 h/ ?- F
例二是一段输入长度的同时得到默认角度的代码,使用getpoint函数。
$ h6 t/ ]$ s/ m F6 U************************************************+ O( T: Z% S" f/ R; v. M
;;例二- (setq pt0 (getpoint "\n直线基点: " )0 ^" d! f# t8 @8 z
- pt1 (getpoint pt0 "\n直线长度: " ) ;长度及角度可用键盘或鼠标定位
( }! ?# ^8 p& J6 }) O/ r. M) Z% m - dst (distance pt0 pt1) ;计算长度
8 q7 N5 O. w9 d! A3 \ T/ f$ s - ang (angle pt0 pt1) ;计算默认角度
. h& a1 P* s# Y, b6 R$ b - ang1 (getangle pt0 (strcat "\n直线方向<" (angtos ang 1) ">: " ))* e* P" s2 l$ P2 V* t$ { K
- )
复制代码 ************************************************
; u, G* x4 b4 l 例三是可以按阵列方式输入行列间距的代码,输入距离为正值,修改部分代码可输入负值,使用getcorner函数,同时使用initget的控制位128。: j( G; P" D; O/ L0 \$ i
************************************************
9 R( a5 M5 Q3 n;;例三- (defun lc_dist ()
6 `/ y! I2 f0 {; X h - (initget 128) ;允许任意输入
$ _; ?' B3 f# G3 z( ~$ g* B, V9 T - (setq disr (getpoint "\n指定单位单元或输入行间距: " ))
/ D" `7 _5 @2 r- ?* J - (if (= (type disr) 'LIST) ;鼠标输入: n$ Z. S4 h; H% q: Q2 T
- (progn: f f/ J% Q7 Z0 S8 x: h
- (initget 1)
# q! D9 |) j O) R - (setq dis (getcorner disr "\n指定对角点: " ) ;鼠标输入对角
8 l" v0 T- N# Q, e0 _% G - disc (abs (- (car dis) (car disr))) ;正值行距
: O, p. ]. M# q - disr (abs (- (cadr dis) (cadr disr))) ;正值列距& d& @# S; p, S* O8 H
- ) ;计算行列间距2 \/ ]0 N/ a( z5 U+ y
- )
4 m$ L/ g; Q. e8 Q0 Y8 i - (if (= (type disr) 'STR) ;键盘输入行距
5 T) h* C2 E8 o- L! l8 i2 _: ?) [ - (if (setq dis (distof disr)) ;判断输入的是否距离
$ O* y. d8 M% z* y9 W; H" X6 _ - (progn
\4 u+ v& X! Q1 F, \: [ - (initget 6)4 j" H9 @3 V6 }& J
- (setq disc (getdist "\n输入列间距: " )) ;输入列距& r2 x6 D. p. J P
- )9 X. m$ r" r$ W$ K
- (progn ;键盘输入格式不符返回
) _: P9 ]* C8 Y0 U: @; y& O; V7 O - (princ "\n需要正数值或两个二维角点。" )4 H6 x6 Q7 D! h H
- (lc_dist)
5 c5 e% t, R/ l4 a9 g l - )
' J1 q( {/ D5 t8 k+ b$ t4 C" y - )
9 {0 A2 E' m1 q% f1 Z - (progn ;空输入返回* W$ b: u8 {' g$ n% O& |! v, I% H
- (princ "\n需要正数值或两个二维角点。" )" A9 z5 \* p' f- U
- (lc_dist)
1 f, W; I }' \8 f9 q% Z! i, s - )
7 `2 v* |8 J+ W3 O - )
; Y: I) O; J9 g" Y' G- P( g - )# J4 n$ o4 ]; x: c* \/ P1 H) k
- )
复制代码 ************************************************
2 c7 d7 V, g0 j0 K+ y( ^2 I' O; T8 B5 Q0 O4 q, S' `/ Q
6.数学运算函数的数量界限, `+ n3 ~7 \8 W; g
在Lisp中对表中数据进行求和、求最大值等数学运算时,往往直观的对表直接赋予运算函数,使用语句如“(eval (cons 'MAX numlist))”,一般都可以进行计算,但当表中数据数量大于255时,将会出现错误“Bad argument value: does not fit in byte: 256”。 对于这种情况,我们不必对数据表进行分段,可以直接使用函数apply,语法更简单:(apply 'MAX numlist)。apply可将数据表传送给指定的函数进行求值而不受数据数量的影响。- ~' z, n# I8 q
受表中数据数量影响的数学运算函数有:+、-、*、/、max、min、logand及logior。
( S. Q) P' L' T+ o( G9 V, }3 |, ~4 W$ t
7.选择集与表6 v7 k8 D2 F* m/ J" M. ^4 o
选择集是一种特殊结构的表,只能通过特定的函数进行操作,但这些函数对大量重复的操作只能通过循环实现,显得力不从心,不能体现Lisp语言表结构的优越性。
6 l j1 a9 h. X \ 其实我们只要通过存取实体名或实体句柄,将它们存为一个普通结构的表,完全可以通过常规表操作函数实现对实体的操作。+ L+ T% B: L8 }5 ?, H
例四是一段使用apply、mapcar函数联合求文本选择集中文本基点最大y值得代码,只是一个示例,如果结合VL-sort函数,可轻松实现对文本的排序。- q7 m7 S; ^% C8 h" Z6 A" L
************************************************' V/ o- B+ S1 T3 K c/ x
;;例四- (setq sl nil i -1)
6 ^. ~9 A$ k/ I0 k% D4 g - (repeat (sslength (setq ss (ssget '((0 . "TEXT" ))))) ;选择文本7 E' R) O$ C: ]( ]3 J% e
- (setq i (1+ i)" d4 n3 u/ E" S3 V9 h
- en (ssname ss i) ;从选择集中取出文本
; M5 F0 V* k, f+ {6 ]: B1 a - sl (cons en sl) ;构造包含实体名的表
9 X& X+ |# F) J' Y4 g; l - )
0 \; V( @. a0 d: _: E3 n. s - )
- L, n, ?' e/ Y. M5 T - (setq maxy (apply 'max ;求文本基点最大y值
0 W0 ?0 Z/ n M( _# A# n - (mapcar
! y8 t9 E& M# P- } - '(lambda (x)# V% p! A3 H% c6 \
- (caddr (assoc 10 (entget x))) ;提取y值
6 h3 `4 j6 i$ s: w! q - )) L8 n" b& O+ U3 w' Y
- sl
! U! p* i9 a2 z, w1 n8 _: P9 ]/ K - )
7 G4 H, J* V$ M4 z, t: Y0 I8 m% R - )
9 T0 B2 Z1 n1 V# { - )
复制代码 ************************************************
5 t$ @. d" g1 _' l6 A 当然,选择集也有其优势的一面,比如对选择集中实体的删除操作非常简单、选择集中的实体不会重复及选择集可以与Acad命令交互使用等特征是一般表所不具备的,所以,编程时应根据程序要求,灵活运用。% ?3 b2 h; j) s7 _$ a/ h ]7 z
' H. W: \+ s7 l" X8 b" W8.cal的使用与加载
0 W7 E) F* }# W% B6 J Acad随机附带了一些外部定义命令,其中cal(计算器)命令是最常用的命令之一,在加载gromcal.arx后cal可以在Lisp程序中像其它函数一样使用,这就使得我们在程序中对文本的四则运算处理变得简单,如“(cal "1+2/3" )”,其中字符串"1+2/3"可以从图形的文本中提取,也可以是符合cal要求格式的任一字符串(详见Acad联机帮助)。$ A' Z/ e$ e' b2 G8 A
需要注意的是,在Acad中gromcal.arx只能加载一次,重复加载将使Acad以外退出(无提示)。需要使用cal函数的Lisp程序,应在程序尾部加上以下代码:* j- p6 s$ S& R, Y9 }1 @2 ?+ ~
*******************- (if (or (= (type c:cal) 'LIST) ;R14使用
& F- Y5 v2 l) N. f - (= (type c:cal) 'SUBR) ;R2000+使用, {( U1 X3 ?) b4 A
- )2 _+ K3 |7 n9 h6 i
- (arxload "geomcal.arx" ))
复制代码 *******************
d5 e: |# r( t) ^& `! Y0 z
. y4 E; E; k$ ~) T5 P9.Undo处理$ P8 O: f" `9 ?* f1 `& A
一个完善的程序应该有较好的出错处理,这是在所有Lisp教材上都提及的,但程序的Undo处理就说得很少或没有提及。% `$ k9 `% \; w
其实Undo处理对程序来说也是非常重要的,尤其对有较多输出的复杂程序而言,不能解决Undo问题,使用起来会极不方便。/ T! G9 L0 f, [6 {! v p
对于Undo问题的解决,一种方法是尽量少用或不用command函数,即不调用原始命令,这是一种较好的方法,但必须注意的是,一段程序必须至少有一次调用command函数,否则Undo命令将取消程序运行前的前一次命令,解决的方法是在程序运行的起始位置加一个无谓的command,如“(command "color" "" )”。
& b0 Q* s" n( j* F# A2 g& F( Z 有时不使用command函数不能达到我们要求的一些功能,或使得程序过于复杂,我们可能需要使用一些command函数(原始命令),这是就应该在程序中进行Undo处理,即使用Undo命令的编组功能。
- ~( o% r) E0 p3 n8 r2 l 例五是一段程序出错函数与Undo处理的示例。
: p2 g2 u* Q0 X************************************************
* x7 C$ k7 w. ^9 u, p0 }* ];;例五- (defun newerr (s) ;出错函数: g* C# w. }8 r
- (if s {4 G$ E0 g- b3 P
- (progn
. ^* T* V( H0 a6 e - (term_dialog) ;使用对话框时使用
$ g( j5 r6 M$ K' H: M7 {! K0 z - (if olderr (setq *error* olderr)) ;出错函数恢复4 L) g# ^) z. r5 j0 j+ n9 ^
- (if oldvar (setvar ... oldvar)) ;系统变量恢复4 b$ N+ |, i# g8 N. t/ @. Q9 }
- (if olderr (setq *error* olderr)) ;出错函数恢复3 l1 f [1 m8 m, ]' ~ R* s# e1 D
- (command "_.undo" "_e" ) ;Undo编组结束
( I0 O1 b/ h! z' v4 R - )
- F1 ]3 E1 I9 f - ); ^( Z2 K" p) C* E" o3 z& F, j
- (princ)
3 K5 Z# k/ X6 X& G% k K - )
- N0 g2 {6 \( J0 M" d - 2 b% q6 A" G3 ?7 l% O
- (defun c:my(/ ...) ;主程序(主函数)+ V7 ~8 Q0 Z B' G. W
- (setvar "cmdecho" 0) ;取消命令回显提示
/ |# S k" q% N8 N - (command "_.undo" "_BE" ) ;Undo编组开始' V5 H; S( `# n4 h) P
- (setq olderr *error* *error* newerr) ;调用自定义出错函数+ e" \. K7 j0 [4 q# N) }
- (setq oldvar (getvar ...)) ;保存相关系统变量
' y& ?- ^8 j. F& E& n - (setvar ... ;设置系统变量2 M2 r# g1 @6 |' a7 d
- ... ;程序段
+ c/ y" l: P% \1 ] - ...; T4 r' n7 F T& Y
- (setvar ... oldvar) ;恢复系统变量9 W( i: B4 \# D2 z9 ^! A4 }
- (setq *error* olderr) ;恢复出错函数
' m% X, F: P' q- @. t, q - (command "_.undo" "_E" ) ;结束Undo命令编组: Q+ `8 o! D* Q/ E8 k, b1 D y7 ^
- (princ) ;取消程序返回值; a5 x! o B# h4 v# i
- )
复制代码 ************************************************
( X" Z: D( `: Z( b9 H: N) q
* U: E& B6 c- X; X N. O$ z10.程序调试是块注释的使用
8 V/ ^9 A& |3 B" } 我们经常会加上或屏蔽一段代码辅助程序调试,此时最常用的是在需要暂时屏蔽的代码前使用行注释符号“;”,对于较多的代码就需要使用块注释“;|——|;”,如果一段代码需要频繁屏蔽,将行注释与块注释组合使用,可以带来极大方便。
8 d6 Q- b+ }( h2 Q% F% b
. \ K" |& y$ Q+ G[ 本帖最后由 woaishuijia 于 2008-10-8 13:07 编辑 ] |
|