|
|
马上注册,结识高手,享用更多资源,轻松玩转三维网社区。
您需要 登录 才可以下载或查看,没有帐号?注册
x
Autolisp编程心得
, U9 m: X- [9 G' K$ B1 u( B' ?1.养成良好的书写习惯- U, y8 X( _7 y% F8 u" O) O& y+ R
众所周知,Lisp是一种表结构语言,括号必须成对出现,在调试时往往为遗漏了一个括号大费周折,所以,养成一个好的编程习惯是学好Lisp所必须的。% D) G3 P8 Y3 y( Q# B0 ~2 J. C
⑴选择一个较好的编辑器,这是一个基本条件,建议使用Visual Lisp编辑器或Lisplink等专用编辑器,此类编辑器可以对函数突出显示。7 p+ o. B, q O" F1 Y6 Y% S
⑵按Lisp(DCL)专有格式书写,并经常对程序进行“格式化”,及时发现语法错误,并有利于调试是查找错误。" p4 @% b4 L" w t% Z3 l5 {( N& K
⑶使用自定义函数,并辅助以适当得注释,在较大程序中按功能使用自定义函数可以使得程序条理化。
4 ~% i1 U7 f9 U: g3 g# a. p% e+ m w6 A
2.函数中循环与转移的使用( q. o5 x$ Y6 f$ a& {# W
在高级语言中一般有类似“goto”的语句实现转移,在AutoLisp中没有转移的函数。我们可以使用自定义函数实现转移,用if及cond辅助实现条件转移。0 Z# D* g0 `( A' G, z
当我们需要实现在满足一定条件时进行循环的功能,一般使用while函数,但有时需要判断的条件较复杂时,使用while函数往往不能实现或使得程序不够简洁。这时我们可以使用“转移”,将需要实现的功能作为(子)函数,使用恰当,可以在程序中任意“转移”。, V5 I) p1 S- \7 ?0 p! J
一般认为,当一段代码在不同处重复使用时,我们才会使用子函数定义,其实,利用函数的更大的优点是使得程序更加结构化。这就使得我们不必拘泥于程序中的循环语句,而使用函数的循环调用,辅助适当的判断,实现“转移”,如A->B->A。当然也可以进行自身调用,构成一个“标准的”循环。) M% y0 o; z- A% ~+ x2 u$ @
如例一中,“程序执行完毕返回”与“空选返回”两种情况如果使用循环语句,其条件是完全不同的,而将函数本身作为子函数调用,程序简洁明了。" A: d9 F& R% u' |" ?0 j
8 L2 ?" m# W5 G1 Q" [* [$ A0 F ^3 k
3.initget函数中关键字“ ”(空格)的使用
+ O% n, ^$ }9 @1 @ 空格可以被用作关键字,一般多用来定义鼠标右键退出。
Q) |% O: s6 y, J$ w ⑴当用户输入函数不支持控制位(如entsel、nentsel、nentselp)时,可直接使用“(initget " " )”。. R J8 N/ ]" V7 I1 W3 s( N
⑵当用户输入函数支持控制位(如getpoint等)时,可使用“(initget 1 " " )”禁止空输入,而将回车等空输入作为关键字使用。) U4 y5 h3 y$ m1 m
⑶当同时使用其它关键字时,应该将空格作为一系列关键字的最后一个,用“(initget "C " )”(两个空格)调用,否则无效。
8 B" W6 L% N3 B8 { 见例一。% g( m, F/ s5 M5 ?# P' O0 \. ?7 a6 n
************************************************, z U0 p- e/ N% h
;;例一- (defun ett_ct()
0 E5 g" W0 |9 e5 ]) e - (initget "C " ) ;关键字“C”及空格
, Z/ G9 f! h4 \; Y, c7 V - (setq s0 (entsel "\n设置颜色C / 选取文本:" )), u3 c- P/ A3 T; D9 }
- (cond ( (= s0 "C" ) (ett_col)) ;转设置颜色子函数$ {& P/ F6 J& d6 v9 l
- ( (= s0 "" ) nil) ;空格退出
7 N+ o7 N) Y1 \/ M. ]$ Z* M - ( (and (= (type s0) 'LIST) ;选择实体6 y: u# U2 d" G9 d1 N. U, a
- (= (cdr (assoc 0 (entget (car s0)))) "TEXT" ) ;判别文本
, G! B- E/ h# O: O1 y9 q - )
) P/ X% r) ?6 @1 m$ L( h2 S* C - ... ;操作内容
/ h- `, K n& Z |5 D& k8 F: D) c - (ett_ct) ;编辑后返回选择
; `, k2 E/ `# A2 x8 @0 K - ), y9 `* p0 ]0 t# |! ^$ U4 l9 T
- (t (ett_ct)) ;空选返回选择9 z3 n; g% M5 B J7 w3 O4 |
- )
U. Q/ n. v* H0 S( U# a - )
复制代码 ************************************************
9 L9 d' X2 ~* z& k 有时需要进行复杂的判断,使用如“(= s0 "" )”语句可能不能准确判别输入的空格关键字与空选择,可以使用“(= (type s0) 'STR)”语句。
/ @7 B1 J2 l- D4 {
' I: B# Y; L; D$ W3 I' t4.Lisp的暂停与while的特殊使用/ w7 g/ ^1 ]) R" \ `5 S
Lisp一般在交互输入时才会暂停,如果只需要实现屏幕显示暂停,可使用grread函数,grread函数对所有合法的输入设备均会作出反应,有时我们只希望对键盘有反应,可使用while函数进行循环。
$ B1 _' m- e! ]0 ]3 W*******************- (princ "\nPress ENTER to continue:" )
- p4 C4 t0 R) f- T# [$ I5 H& d - (while (/= (car (grread)) 2))
复制代码 *******************% l3 n# X( K( u/ D: N
while用于满足一定条件的循环,其标准语法为:: y3 \# e. W; L4 `+ C, x7 r
(while testexpr [expr...])
4 x' i3 y' C6 p% r6 S/ {. |) p 其中expr解释为“在 testexpr 为 nil 之前要求值的一个或多个表达式”,为可选项(在R14之前没有方括号,但仍为可选项)。
6 J" D; f( Q; H! |, u/ |- v9 J 正常我们使用while时,总会有expr项,更多的时候,我们是为了expr项才会使用这种循环语句,所有我们往往有expr项是不可缺少的感觉。这里我们使用while函数的语法是while函数的特例,即没有expr项的情况。4 H* @8 r% b' s7 A
如果希望对鼠标右键同时反应,可以使用:
5 G. \4 _$ G6 f* a$ F; A) h: Q*******************- (princ "\nPress ENTER to continue:" )
5 c% }$ C$ G+ l! ~* f - (while (and (/= (setq a(car (grread))) 2) ;键盘. L; z; B' T( W$ M! p
- (/= a 11) ;鼠标右键
* _1 r% Q2 o, i6 n# d2 s - (SHORTCUTMENU=0)
. z# V! o4 r( B1 U - (/= a 25) ;鼠标右键
7 O* `' W& o% G5 o6 N# a - (SHORTCUTMENU≠0)9 p) s( J) D$ i* d! C
- )8 p2 k* F. F; t+ g! g! ^
- )
复制代码 *******************" D8 S3 u2 M2 C8 e2 J
, T" b8 T; K7 X( X+ K/ Y9 k
5.输入距离
0 {. U6 w0 W8 E/ r" _4 R/ ~ Lisp语言中输入距离的函数为getdist,但我们有时需要输入负值,有时需要在输入距离的同时得到角度,使用getdist函数就显得无能为力,这时,我们可以灵活使用其它交互输入函数如getpoint、getcorner等,通过计算得到我们所需要的值。
: D" j/ {4 e3 ?5 q 例二是一段输入长度的同时得到默认角度的代码,使用getpoint函数。
5 M) q8 c3 {' U! R& F) A* b1 e. ]************************************************
1 H0 t/ e% r' B3 _3 {% S;;例二- (setq pt0 (getpoint "\n直线基点: " )
& ]" v* g I9 u0 s* {/ X- { - pt1 (getpoint pt0 "\n直线长度: " ) ;长度及角度可用键盘或鼠标定位/ Y9 f: w4 M" @: y
- dst (distance pt0 pt1) ;计算长度
% h% ]# q/ Q4 q/ n# x% j+ d- A; M - ang (angle pt0 pt1) ;计算默认角度( A- x& y8 R! ?9 i! m
- ang1 (getangle pt0 (strcat "\n直线方向<" (angtos ang 1) ">: " ))
7 ]" q+ a" H0 k, o1 {# o2 y - )
复制代码 ************************************************
. I6 i3 N' V; `4 b9 A4 p6 }# _ 例三是可以按阵列方式输入行列间距的代码,输入距离为正值,修改部分代码可输入负值,使用getcorner函数,同时使用initget的控制位128。- Y# E, Z P5 b
************************************************) h1 R4 f% I" t. r( e1 B' ~
;;例三- (defun lc_dist ()+ H% H! t) } d" D# s) H
- (initget 128) ;允许任意输入" Q' H$ Q$ b+ T- T$ U- v
- (setq disr (getpoint "\n指定单位单元或输入行间距: " ))
5 ]" E M) X O9 U E7 n x - (if (= (type disr) 'LIST) ;鼠标输入
- L$ q' L" n; U$ \. q+ y3 ` - (progn) b7 ^! p; J8 q8 _! \
- (initget 1)
1 Z: M* u9 s0 V - (setq dis (getcorner disr "\n指定对角点: " ) ;鼠标输入对角: W+ ]( K, N+ I1 V; t
- disc (abs (- (car dis) (car disr))) ;正值行距& t8 z, g* Q9 W; v
- disr (abs (- (cadr dis) (cadr disr))) ;正值列距; w& h. B, {% q/ U
- ) ;计算行列间距9 v5 J) p1 i L1 J% |4 D5 y$ k
- )
) i/ H! p8 r0 y; b- W3 ^8 m - (if (= (type disr) 'STR) ;键盘输入行距3 U, E2 l6 B# N/ x" b6 U
- (if (setq dis (distof disr)) ;判断输入的是否距离" @3 J' u9 ~' K: f
- (progn
, ^0 V+ u6 |% S9 D - (initget 6)
( Y# ~# O9 c! t - (setq disc (getdist "\n输入列间距: " )) ;输入列距, u, x, M* L7 O0 b
- )
* h& R& U$ y6 }8 l5 m7 m" ^ - (progn ;键盘输入格式不符返回8 A! ~5 A# _4 r, l9 P
- (princ "\n需要正数值或两个二维角点。" )% F' X% J, ]3 r! [9 R; C6 X
- (lc_dist)
' a* T$ c4 l4 m# D- r - )3 i# @3 M/ g5 A7 Q. J
- )
( I0 h8 ?9 S+ g& d: z9 E - (progn ;空输入返回: S6 l! n6 t5 ]& M
- (princ "\n需要正数值或两个二维角点。" )7 l# N0 V% E- `; {/ y$ D9 ]
- (lc_dist)
" I3 d. E# p4 Z2 |0 z - )9 n1 [% B# l$ [, _2 Q9 f, d9 S
- )
7 V: I W: H; O9 _9 Q, q6 M - ). n+ z0 _/ z! Z) K1 O6 v
- )
复制代码 ************************************************
% d9 W. C7 u5 N% G7 D' C+ F. z9 U5 R( d# [
6.数学运算函数的数量界限
& s; Y9 f, K! f( r4 n: \, J 在Lisp中对表中数据进行求和、求最大值等数学运算时,往往直观的对表直接赋予运算函数,使用语句如“(eval (cons 'MAX numlist))”,一般都可以进行计算,但当表中数据数量大于255时,将会出现错误“Bad argument value: does not fit in byte: 256”。 对于这种情况,我们不必对数据表进行分段,可以直接使用函数apply,语法更简单:(apply 'MAX numlist)。apply可将数据表传送给指定的函数进行求值而不受数据数量的影响。2 g$ R! [- R" [
受表中数据数量影响的数学运算函数有:+、-、*、/、max、min、logand及logior。 H0 O; l( Z; M
5 L! E2 Y! j/ B. s, M7 V7.选择集与表4 u% @% e/ |, ^7 m5 O
选择集是一种特殊结构的表,只能通过特定的函数进行操作,但这些函数对大量重复的操作只能通过循环实现,显得力不从心,不能体现Lisp语言表结构的优越性。
5 [ K: @4 N. H7 ?+ r; u n 其实我们只要通过存取实体名或实体句柄,将它们存为一个普通结构的表,完全可以通过常规表操作函数实现对实体的操作。5 }: S# I$ m) ^, e2 o7 }6 P9 h* ~* H
例四是一段使用apply、mapcar函数联合求文本选择集中文本基点最大y值得代码,只是一个示例,如果结合VL-sort函数,可轻松实现对文本的排序。
: ^7 M5 m8 a: O1 x************************************************
' E$ A3 z; V8 N) `2 G( a;;例四- (setq sl nil i -1)1 o ~+ Q' Z2 x) l/ h" V: m3 ]. l
- (repeat (sslength (setq ss (ssget '((0 . "TEXT" ))))) ;选择文本
( ?1 l7 Q. }0 D - (setq i (1+ i)7 M4 \* }$ I' P% J0 L* @
- en (ssname ss i) ;从选择集中取出文本
) @, [1 `% n# x$ p - sl (cons en sl) ;构造包含实体名的表
, J) P% T4 d8 L1 ^0 J: } - )
K" Y0 i0 f$ @/ E* }( n* A2 G1 [8 ` - )
9 V3 M+ v2 }% C* n( U9 u - (setq maxy (apply 'max ;求文本基点最大y值& d* F9 h! Q" t5 A
- (mapcar! [6 e! V% o8 N& ] Q
- '(lambda (x)1 `, p! ]: o, k
- (caddr (assoc 10 (entget x))) ;提取y值* v! _) a" w5 ?. E. R( @' Q* E6 r+ n
- )
5 a+ n, Q2 g6 h - sl
7 `7 D z/ w v8 q - )3 X. A, K, h3 ~ s, W
- )
) D5 I" L$ M5 [1 c7 o7 P4 x" H - )
复制代码 ************************************************
) s2 u8 G2 u% R! d6 `0 E 当然,选择集也有其优势的一面,比如对选择集中实体的删除操作非常简单、选择集中的实体不会重复及选择集可以与Acad命令交互使用等特征是一般表所不具备的,所以,编程时应根据程序要求,灵活运用。
( d. z" o/ t4 |! S" ?' `; `) h; N* h9 N- K+ H0 H! T2 v
8.cal的使用与加载; B6 d' l8 j/ Z7 ~9 L% r' H
Acad随机附带了一些外部定义命令,其中cal(计算器)命令是最常用的命令之一,在加载gromcal.arx后cal可以在Lisp程序中像其它函数一样使用,这就使得我们在程序中对文本的四则运算处理变得简单,如“(cal "1+2/3" )”,其中字符串"1+2/3"可以从图形的文本中提取,也可以是符合cal要求格式的任一字符串(详见Acad联机帮助)。
' A4 N& {: ]& d) J% ^# [- M& _ 需要注意的是,在Acad中gromcal.arx只能加载一次,重复加载将使Acad以外退出(无提示)。需要使用cal函数的Lisp程序,应在程序尾部加上以下代码:6 z0 {" K7 ^( F/ d
*******************- (if (or (= (type c:cal) 'LIST) ;R14使用
; [* I; X- k( {" P+ O - (= (type c:cal) 'SUBR) ;R2000+使用
7 J p8 {! R: |, c - )$ w' E7 \7 w4 V5 u+ l" E- V6 W
- (arxload "geomcal.arx" ))
复制代码 *******************
9 o: k# C4 ]" G" `% h8 m8 x5 `
9.Undo处理
3 y2 T# H: ]- Q3 W @) [ 一个完善的程序应该有较好的出错处理,这是在所有Lisp教材上都提及的,但程序的Undo处理就说得很少或没有提及。
/ t' N4 i0 [8 c+ z 其实Undo处理对程序来说也是非常重要的,尤其对有较多输出的复杂程序而言,不能解决Undo问题,使用起来会极不方便。
5 M# `: c4 v& i0 c7 r$ }/ P 对于Undo问题的解决,一种方法是尽量少用或不用command函数,即不调用原始命令,这是一种较好的方法,但必须注意的是,一段程序必须至少有一次调用command函数,否则Undo命令将取消程序运行前的前一次命令,解决的方法是在程序运行的起始位置加一个无谓的command,如“(command "color" "" )”。! n' W& _# r8 p' J. L3 X
有时不使用command函数不能达到我们要求的一些功能,或使得程序过于复杂,我们可能需要使用一些command函数(原始命令),这是就应该在程序中进行Undo处理,即使用Undo命令的编组功能。7 b. k& U7 J( `* y. t: L
例五是一段程序出错函数与Undo处理的示例。% D: o8 o" x4 [ H3 a z
************************************************* ~0 D: q. M, _# k5 e X
;;例五- (defun newerr (s) ;出错函数
7 g; H3 g: k& A7 v. A* T - (if s
/ N7 }2 \& ?, Q! g8 ]; v5 Q - (progn
8 o/ v8 c4 z/ S/ {$ ^: P, S# s1 i - (term_dialog) ;使用对话框时使用* ~- w/ _0 C1 b# P: o
- (if olderr (setq *error* olderr)) ;出错函数恢复- G0 C9 ^8 o! C/ N6 A8 s
- (if oldvar (setvar ... oldvar)) ;系统变量恢复
; G+ X6 _* |9 T$ g' J, j' q4 k - (if olderr (setq *error* olderr)) ;出错函数恢复5 `/ A8 k# a1 {) ]3 O2 m
- (command "_.undo" "_e" ) ;Undo编组结束: k' ]% f7 O$ E& Q
- )" s& k! h3 ? o2 U% {0 D
- )
: l8 E0 T6 K% ?% v - (princ)
) v2 C) p R" o$ G% \0 b* ?: C - )
- F7 S) z. E. v" C6 t; G/ Z - * _( @" k I( N- V
- (defun c:my(/ ...) ;主程序(主函数)# ^- u0 l, }0 `4 _( j6 ^4 z2 d* M
- (setvar "cmdecho" 0) ;取消命令回显提示
2 T+ z0 \& p- C; z - (command "_.undo" "_BE" ) ;Undo编组开始, e" m$ m( `) [ Z( ^
- (setq olderr *error* *error* newerr) ;调用自定义出错函数) c$ v0 `2 _# @) D; |5 |
- (setq oldvar (getvar ...)) ;保存相关系统变量7 p: P% t: C2 G3 o- i9 ^
- (setvar ... ;设置系统变量9 s' Y' \. {" e0 Q2 y8 q
- ... ;程序段
+ F7 d) n5 U+ f2 P4 o - ...
. N* k6 D# S( x; w3 B% x: K - (setvar ... oldvar) ;恢复系统变量
! M! Q4 d3 _$ |$ {0 ?. u* s" d - (setq *error* olderr) ;恢复出错函数& v$ C7 a8 r6 j" _4 w5 w0 b- Y
- (command "_.undo" "_E" ) ;结束Undo命令编组
1 @ l6 D5 T9 ^% ] - (princ) ;取消程序返回值
V, C1 x2 ?& h! N - )
复制代码 ************************************************ W) Z4 E' |% L$ M' i- @
% v: ?; ]9 [3 I( L) u
10.程序调试是块注释的使用
9 {( |3 E8 j$ p# ~ 我们经常会加上或屏蔽一段代码辅助程序调试,此时最常用的是在需要暂时屏蔽的代码前使用行注释符号“;”,对于较多的代码就需要使用块注释“;|——|;”,如果一段代码需要频繁屏蔽,将行注释与块注释组合使用,可以带来极大方便。0 E( M3 ^9 C6 a' a, ]" N# I
4 P# W0 i3 F7 D" C( E* U, h
[ 本帖最后由 woaishuijia 于 2008-10-8 13:07 编辑 ] |
|