|
|
马上注册,结识高手,享用更多资源,轻松玩转三维网社区。
您需要 登录 才可以下载或查看,没有帐号?注册
x
Autolisp编程心得 1 j: J1 f6 L& d: V6 V* S' e4 g
1.养成良好的书写习惯, B* ^& y: u# r1 M
众所周知,Lisp是一种表结构语言,括号必须成对出现,在调试时往往为遗漏了一个括号大费周折,所以,养成一个好的编程习惯是学好Lisp所必须的。9 C# M& S; K" ?
⑴选择一个较好的编辑器,这是一个基本条件,建议使用Visual Lisp编辑器或Lisplink等专用编辑器,此类编辑器可以对函数突出显示。
6 I# q/ A* e: L' Y' ^8 P- g ⑵按Lisp(DCL)专有格式书写,并经常对程序进行“格式化”,及时发现语法错误,并有利于调试是查找错误。% J& y" v7 I7 e
⑶使用自定义函数,并辅助以适当得注释,在较大程序中按功能使用自定义函数可以使得程序条理化。
) _. `" D0 ^" r; u9 v+ q# r/ }+ K2 _" p
2.函数中循环与转移的使用- i/ [" h; d6 y- K0 E
在高级语言中一般有类似“goto”的语句实现转移,在AutoLisp中没有转移的函数。我们可以使用自定义函数实现转移,用if及cond辅助实现条件转移。
) s( T" }2 e5 Q, B# Q p5 V2 z 当我们需要实现在满足一定条件时进行循环的功能,一般使用while函数,但有时需要判断的条件较复杂时,使用while函数往往不能实现或使得程序不够简洁。这时我们可以使用“转移”,将需要实现的功能作为(子)函数,使用恰当,可以在程序中任意“转移”。& |) }5 k9 _1 ~" G$ r& q6 d; C
一般认为,当一段代码在不同处重复使用时,我们才会使用子函数定义,其实,利用函数的更大的优点是使得程序更加结构化。这就使得我们不必拘泥于程序中的循环语句,而使用函数的循环调用,辅助适当的判断,实现“转移”,如A->B->A。当然也可以进行自身调用,构成一个“标准的”循环。' H* b; F* E% E4 I
如例一中,“程序执行完毕返回”与“空选返回”两种情况如果使用循环语句,其条件是完全不同的,而将函数本身作为子函数调用,程序简洁明了。
+ x d1 W; A4 z& `) H/ [9 c( {- R
3.initget函数中关键字“ ”(空格)的使用
% T. z9 U' ]& ^ ^ b3 ] 空格可以被用作关键字,一般多用来定义鼠标右键退出。
+ F8 F j. z" z6 x1 ` ⑴当用户输入函数不支持控制位(如entsel、nentsel、nentselp)时,可直接使用“(initget " " )”。9 n- S$ I0 A' t
⑵当用户输入函数支持控制位(如getpoint等)时,可使用“(initget 1 " " )”禁止空输入,而将回车等空输入作为关键字使用。
: Z) o2 o. D7 v ⑶当同时使用其它关键字时,应该将空格作为一系列关键字的最后一个,用“(initget "C " )”(两个空格)调用,否则无效。
" B; x; s5 x! G$ `4 J: _) | 见例一。
4 w' d5 R" E. l************************************************
) t. f! E! t+ }. N6 x5 h/ {;;例一- (defun ett_ct()& X2 J* S2 u- Y- @0 d& t
- (initget "C " ) ;关键字“C”及空格
$ b) X* f9 X) I8 f/ q0 Y- p - (setq s0 (entsel "\n设置颜色C / 选取文本:" ))
* Z1 l+ M! c2 ~4 O - (cond ( (= s0 "C" ) (ett_col)) ;转设置颜色子函数
; l/ H& W& v, C1 ~4 B - ( (= s0 "" ) nil) ;空格退出# B5 E8 I) G- H4 T
- ( (and (= (type s0) 'LIST) ;选择实体# [8 b0 A- n1 D5 f5 [, c$ c
- (= (cdr (assoc 0 (entget (car s0)))) "TEXT" ) ;判别文本; e& r) k9 D4 r$ ~5 ~
- )
* R9 e+ `/ b3 F+ i$ z2 w3 ~ - ... ;操作内容, Q& j1 ]! ` |5 Z; I* W
- (ett_ct) ;编辑后返回选择
, G' |- }) h g" k$ C) [! Z - )/ i* ^# A! M3 X3 n
- (t (ett_ct)) ;空选返回选择/ {0 h9 A) T6 j; l
- )9 D4 y& `3 j# @/ e
- )
复制代码 ************************************************
- R' J' {9 c# m4 k! e$ c 有时需要进行复杂的判断,使用如“(= s0 "" )”语句可能不能准确判别输入的空格关键字与空选择,可以使用“(= (type s0) 'STR)”语句。
$ n: w2 j1 V- H) h- u( q' w, }: i# K _# w8 B
4.Lisp的暂停与while的特殊使用+ n3 J$ N8 F+ U" x
Lisp一般在交互输入时才会暂停,如果只需要实现屏幕显示暂停,可使用grread函数,grread函数对所有合法的输入设备均会作出反应,有时我们只希望对键盘有反应,可使用while函数进行循环。
% c! N( N4 A: c1 h*******************- (princ "\nPress ENTER to continue:" )
. w7 Y5 ]* j. F; Z5 R+ ~4 E( t - (while (/= (car (grread)) 2))
复制代码 *******************
2 o7 {; H K* x7 d* k8 N% u, h# r while用于满足一定条件的循环,其标准语法为:5 I7 o2 r6 y: j: R7 _- s, J
(while testexpr [expr...])
5 j1 z7 G7 q* x- g 其中expr解释为“在 testexpr 为 nil 之前要求值的一个或多个表达式”,为可选项(在R14之前没有方括号,但仍为可选项)。
5 o, V% t& a2 u9 k 正常我们使用while时,总会有expr项,更多的时候,我们是为了expr项才会使用这种循环语句,所有我们往往有expr项是不可缺少的感觉。这里我们使用while函数的语法是while函数的特例,即没有expr项的情况。
; E3 q' z+ ^1 ?2 h+ ]- B 如果希望对鼠标右键同时反应,可以使用:6 a* E: L7 @( H
*******************- (princ "\nPress ENTER to continue:" )" Y& E$ M" C5 V9 [
- (while (and (/= (setq a(car (grread))) 2) ;键盘7 V1 J. Q( p* [/ V! K) a% U9 h
- (/= a 11) ;鼠标右键9 E' D) t/ ?) `% q4 Z* L
- (SHORTCUTMENU=0)# k/ t+ `( ^8 C G" M( I7 k$ L% p
- (/= a 25) ;鼠标右键
$ S$ @7 e: _" K. \! W - (SHORTCUTMENU≠0)6 e0 b6 p6 ~* h+ v; d: H
- )
4 ~' Z4 w6 [1 @ - )
复制代码 *******************4 W+ X, c7 \6 [5 d+ ]: f
# G8 a+ \! _1 C7 K; t+ Q0 A1 y5.输入距离# ~+ g9 i: x6 Q7 z3 y1 U) _
Lisp语言中输入距离的函数为getdist,但我们有时需要输入负值,有时需要在输入距离的同时得到角度,使用getdist函数就显得无能为力,这时,我们可以灵活使用其它交互输入函数如getpoint、getcorner等,通过计算得到我们所需要的值。
& {! v4 w6 j' K; f2 S+ s 例二是一段输入长度的同时得到默认角度的代码,使用getpoint函数。( D% m) h9 d& \
************************************************8 |6 Y3 S2 b; [7 G6 b
;;例二- (setq pt0 (getpoint "\n直线基点: " )
2 o: i2 X2 Y, @5 p- V& V2 ^ - pt1 (getpoint pt0 "\n直线长度: " ) ;长度及角度可用键盘或鼠标定位" f* ?( F" p; x2 F4 {3 U
- dst (distance pt0 pt1) ;计算长度
; U. d5 i- ^1 \6 U7 Y/ c - ang (angle pt0 pt1) ;计算默认角度! @$ r5 ^) R) l4 Q! i2 _" h
- ang1 (getangle pt0 (strcat "\n直线方向<" (angtos ang 1) ">: " ))9 _8 K7 R& f: S; [3 ]
- )
复制代码 ************************************************; m1 U) H' U0 K( z
例三是可以按阵列方式输入行列间距的代码,输入距离为正值,修改部分代码可输入负值,使用getcorner函数,同时使用initget的控制位128。
! j" V* [9 U( f- B9 i: z************************************************0 t u+ e# H6 _. I# h) x
;;例三- (defun lc_dist ()
- |) K: u6 `1 s- C. Q& Y - (initget 128) ;允许任意输入
( g, {) x+ } ]( @6 o - (setq disr (getpoint "\n指定单位单元或输入行间距: " ))
+ a9 f) K7 H$ g$ \' X, \ - (if (= (type disr) 'LIST) ;鼠标输入3 \" X/ o( N2 J/ ?1 b, n" v! u
- (progn
( I7 I% c: [6 d/ t0 H" o( I5 U - (initget 1)
9 z" ?9 ^# _6 l g i - (setq dis (getcorner disr "\n指定对角点: " ) ;鼠标输入对角. s4 [ V( ?: [2 e$ T: ?
- disc (abs (- (car dis) (car disr))) ;正值行距
Y7 G* U# E( }, I - disr (abs (- (cadr dis) (cadr disr))) ;正值列距
. ]9 @% D7 R& ? r( ^ e - ) ;计算行列间距
7 G9 I* d* A9 k6 `9 f - )
0 t9 V! c2 _5 f8 N" M7 v - (if (= (type disr) 'STR) ;键盘输入行距- Z6 Z6 l& y2 O( P3 R
- (if (setq dis (distof disr)) ;判断输入的是否距离
! E! }1 i! C5 S1 f. X4 p# O6 t - (progn
3 Q2 [( o' }/ H- z% {. i7 [ - (initget 6)( W& H# ~/ Y) U6 G4 |
- (setq disc (getdist "\n输入列间距: " )) ;输入列距' c$ P9 G4 S4 ^4 r7 K
- )
, ]' h% i) `6 X - (progn ;键盘输入格式不符返回+ F1 @- r6 r0 I# Z$ _' L) `0 U
- (princ "\n需要正数值或两个二维角点。" )
% X! o$ A' r$ J) T, Y - (lc_dist)2 j4 y' l( ^# i* ]) }6 u
- )
1 a# i4 r* X. J& B9 J' D - )
! Q x" q( `. e2 T1 U - (progn ;空输入返回
9 @% r( q8 x- n1 W, { - (princ "\n需要正数值或两个二维角点。" )
2 _( \% r4 a# n$ d& R& m - (lc_dist)
! X V6 E9 ]: J5 o: p* b - )
' x# Z0 p2 Q) O: m: J+ Y# p7 B" H - )
6 L, X& X3 c& D - )
# Y4 x# g. E4 W2 L8 s+ s - )
复制代码 ************************************************
7 q [/ a7 m; _- p1 w1 @* Y
' {& u2 N1 ^! p* V6.数学运算函数的数量界限
8 s3 y6 Z) s# q) a! n 在Lisp中对表中数据进行求和、求最大值等数学运算时,往往直观的对表直接赋予运算函数,使用语句如“(eval (cons 'MAX numlist))”,一般都可以进行计算,但当表中数据数量大于255时,将会出现错误“Bad argument value: does not fit in byte: 256”。 对于这种情况,我们不必对数据表进行分段,可以直接使用函数apply,语法更简单:(apply 'MAX numlist)。apply可将数据表传送给指定的函数进行求值而不受数据数量的影响。
6 R" U1 a/ d6 z5 |1 l9 v- X* I 受表中数据数量影响的数学运算函数有:+、-、*、/、max、min、logand及logior。* d/ ]" d8 E% i M, x: T) F
/ U4 b2 A) H4 v2 A3 W
7.选择集与表
1 M; K3 q: T# i1 R! d( d7 o 选择集是一种特殊结构的表,只能通过特定的函数进行操作,但这些函数对大量重复的操作只能通过循环实现,显得力不从心,不能体现Lisp语言表结构的优越性。
! ^6 h+ d X {: y7 [+ n( P# ~7 f! e 其实我们只要通过存取实体名或实体句柄,将它们存为一个普通结构的表,完全可以通过常规表操作函数实现对实体的操作。 C( [& v) C' b* Y/ I
例四是一段使用apply、mapcar函数联合求文本选择集中文本基点最大y值得代码,只是一个示例,如果结合VL-sort函数,可轻松实现对文本的排序。
9 t. o4 k( S/ n* d************************************************( i) w/ F4 e) Y+ C
;;例四- (setq sl nil i -1)
" g- }6 c$ s1 B3 B: @% |3 x - (repeat (sslength (setq ss (ssget '((0 . "TEXT" ))))) ;选择文本
9 U/ s% C+ C& h - (setq i (1+ i)1 U, y8 Q( F! s
- en (ssname ss i) ;从选择集中取出文本
3 o N1 C, b4 Q3 c7 | - sl (cons en sl) ;构造包含实体名的表
. j ~$ T+ R, S t+ _ - )) n x( j3 T3 }9 V/ v
- )
% R/ }) W! [2 s/ g6 H - (setq maxy (apply 'max ;求文本基点最大y值
9 S; |* |, w* S9 k2 d - (mapcar
# e" r3 T# w4 C/ u1 ]1 e5 `+ o - '(lambda (x)
' U/ @2 _% c. p5 C% z - (caddr (assoc 10 (entget x))) ;提取y值: j% g M3 g$ R6 f" C6 }
- )
2 F, `+ X1 v/ P9 e d - sl
( ~4 b$ Z0 Q1 l1 |6 i" w - )
; E0 G2 a: j# ? O - )
- ]2 N5 C! p' {& D - )
复制代码 ************************************************
/ `( i5 {% f. F5 @2 H0 \' N3 ^7 f 当然,选择集也有其优势的一面,比如对选择集中实体的删除操作非常简单、选择集中的实体不会重复及选择集可以与Acad命令交互使用等特征是一般表所不具备的,所以,编程时应根据程序要求,灵活运用。9 [* X" p8 E0 p& Z
2 N% `# u: @8 P4 O' |
8.cal的使用与加载
( }4 l9 e5 ?2 D7 P9 x5 d Acad随机附带了一些外部定义命令,其中cal(计算器)命令是最常用的命令之一,在加载gromcal.arx后cal可以在Lisp程序中像其它函数一样使用,这就使得我们在程序中对文本的四则运算处理变得简单,如“(cal "1+2/3" )”,其中字符串"1+2/3"可以从图形的文本中提取,也可以是符合cal要求格式的任一字符串(详见Acad联机帮助)。
' r& N( }2 r4 m5 @ 需要注意的是,在Acad中gromcal.arx只能加载一次,重复加载将使Acad以外退出(无提示)。需要使用cal函数的Lisp程序,应在程序尾部加上以下代码:
4 m1 d; w, S* }" E2 d; X*******************- (if (or (= (type c:cal) 'LIST) ;R14使用
9 c1 U/ p1 A0 ?' n - (= (type c:cal) 'SUBR) ;R2000+使用+ x z( O: j, a% I* k
- )
+ ~4 Q* i$ O3 p1 ?; a! i - (arxload "geomcal.arx" ))
复制代码 *******************
0 }) b2 d" s1 C/ m, _6 ~& @" b4 n. R- T$ |/ U
9.Undo处理
^7 `- E% X# \ 一个完善的程序应该有较好的出错处理,这是在所有Lisp教材上都提及的,但程序的Undo处理就说得很少或没有提及。' k# z: w* q% ~5 ~* D/ M
其实Undo处理对程序来说也是非常重要的,尤其对有较多输出的复杂程序而言,不能解决Undo问题,使用起来会极不方便。' H6 n, ]4 V0 N+ \3 W
对于Undo问题的解决,一种方法是尽量少用或不用command函数,即不调用原始命令,这是一种较好的方法,但必须注意的是,一段程序必须至少有一次调用command函数,否则Undo命令将取消程序运行前的前一次命令,解决的方法是在程序运行的起始位置加一个无谓的command,如“(command "color" "" )”。: a2 N5 z; K3 |& z( b; b
有时不使用command函数不能达到我们要求的一些功能,或使得程序过于复杂,我们可能需要使用一些command函数(原始命令),这是就应该在程序中进行Undo处理,即使用Undo命令的编组功能。; P4 y3 {2 M2 |! P5 ], A2 d) a) H
例五是一段程序出错函数与Undo处理的示例。( i1 p- h3 f) L# N2 N' [ g' M0 T% E
************************************************4 z9 g `, Y* \, s( X
;;例五- (defun newerr (s) ;出错函数
0 |1 j9 P* {- ]" w+ P, _# l# O - (if s$ H0 m1 I* J9 D7 L5 P9 z1 S" @- s
- (progn
, P/ ?- M# i% A" ^9 O3 t+ x - (term_dialog) ;使用对话框时使用
' H+ D, R, G, b! I - (if olderr (setq *error* olderr)) ;出错函数恢复
# g3 `0 ^! {3 u( s, z) T - (if oldvar (setvar ... oldvar)) ;系统变量恢复
5 |1 k7 w8 O! y4 e& |1 X, Q - (if olderr (setq *error* olderr)) ;出错函数恢复1 K6 g& c! x8 x: I# z8 |
- (command "_.undo" "_e" ) ;Undo编组结束
7 B' z0 W. a G1 T/ }: l3 a - )$ S: p( e+ g* `9 S
- )" x' Y1 H0 N2 @0 l2 z) y
- (princ)8 B# p( m' p% Y" {$ I* O
- )$ a# I' e D8 h7 c
( T: r* s3 ^* x- (defun c:my(/ ...) ;主程序(主函数)
% p; u) m6 \; Z0 J$ m* I - (setvar "cmdecho" 0) ;取消命令回显提示
- C# T: o( `2 g- O- t - (command "_.undo" "_BE" ) ;Undo编组开始
3 J- K3 t' Q$ c- p - (setq olderr *error* *error* newerr) ;调用自定义出错函数8 m6 q4 l4 |! W) w: L ~+ r
- (setq oldvar (getvar ...)) ;保存相关系统变量
! o2 l& e9 U$ g( J) S G v* M - (setvar ... ;设置系统变量
% Z' M: B* l1 F& X; A4 f- V. c# C5 w - ... ;程序段
* U: t! z/ b" G! | - ...
- r! e; | |0 I8 [1 C, X4 g - (setvar ... oldvar) ;恢复系统变量$ ^+ W$ d) n8 n X
- (setq *error* olderr) ;恢复出错函数
' K- W6 p4 M. b3 W2 v - (command "_.undo" "_E" ) ;结束Undo命令编组
, D6 f; b( M- i9 N0 |8 ` - (princ) ;取消程序返回值8 D+ r$ L u) ^
- )
复制代码 ************************************************
0 l& T$ x( p" o; P" [* p% {0 U
/ Y# r. D+ D$ Q4 |6 N, ]10.程序调试是块注释的使用
U3 P: l8 m# k# |9 X, I* Q) p' K 我们经常会加上或屏蔽一段代码辅助程序调试,此时最常用的是在需要暂时屏蔽的代码前使用行注释符号“;”,对于较多的代码就需要使用块注释“;|——|;”,如果一段代码需要频繁屏蔽,将行注释与块注释组合使用,可以带来极大方便。2 f, g& r8 o9 |# p
' u) b2 u/ r3 @[ 本帖最后由 woaishuijia 于 2008-10-8 13:07 编辑 ] |
|