|
|
马上注册,结识高手,享用更多资源,轻松玩转三维网社区。
您需要 登录 才可以下载或查看,没有帐号?注册
x
Autolisp编程心得
* D% r+ S' F' t+ k1.养成良好的书写习惯, h0 W$ i0 e+ o
众所周知,Lisp是一种表结构语言,括号必须成对出现,在调试时往往为遗漏了一个括号大费周折,所以,养成一个好的编程习惯是学好Lisp所必须的。
# K! k7 g3 G& F ⑴选择一个较好的编辑器,这是一个基本条件,建议使用Visual Lisp编辑器或Lisplink等专用编辑器,此类编辑器可以对函数突出显示。9 [% K; X% _# }7 m/ C+ ]6 p
⑵按Lisp(DCL)专有格式书写,并经常对程序进行“格式化”,及时发现语法错误,并有利于调试是查找错误。
/ f' `# W6 D0 Q3 r9 w' Z# `. f. w ⑶使用自定义函数,并辅助以适当得注释,在较大程序中按功能使用自定义函数可以使得程序条理化。' I7 u% L9 K U3 @0 P p7 s
+ V1 o" V# L9 Z2 N
2.函数中循环与转移的使用) T4 y5 b9 x" k2 ]
在高级语言中一般有类似“goto”的语句实现转移,在AutoLisp中没有转移的函数。我们可以使用自定义函数实现转移,用if及cond辅助实现条件转移。. S3 S4 Z8 a$ T( ]+ C
当我们需要实现在满足一定条件时进行循环的功能,一般使用while函数,但有时需要判断的条件较复杂时,使用while函数往往不能实现或使得程序不够简洁。这时我们可以使用“转移”,将需要实现的功能作为(子)函数,使用恰当,可以在程序中任意“转移”。
# m& h4 P) n& K" h8 w 一般认为,当一段代码在不同处重复使用时,我们才会使用子函数定义,其实,利用函数的更大的优点是使得程序更加结构化。这就使得我们不必拘泥于程序中的循环语句,而使用函数的循环调用,辅助适当的判断,实现“转移”,如A->B->A。当然也可以进行自身调用,构成一个“标准的”循环。% b, m0 I# A# k; g* X- y& J% h
如例一中,“程序执行完毕返回”与“空选返回”两种情况如果使用循环语句,其条件是完全不同的,而将函数本身作为子函数调用,程序简洁明了。' R3 M/ _! |2 U; ^; J3 f( \( u, O' ?
& {5 ]1 X1 ~. o+ O+ a* N6 M3.initget函数中关键字“ ”(空格)的使用5 m! U5 V* h" O4 e, C; S) A5 a
空格可以被用作关键字,一般多用来定义鼠标右键退出。, C2 h0 A1 s! s7 L* ~
⑴当用户输入函数不支持控制位(如entsel、nentsel、nentselp)时,可直接使用“(initget " " )”。
; _: W: F4 k" E) C; H ⑵当用户输入函数支持控制位(如getpoint等)时,可使用“(initget 1 " " )”禁止空输入,而将回车等空输入作为关键字使用。% O/ W. x _3 L1 |$ P$ u
⑶当同时使用其它关键字时,应该将空格作为一系列关键字的最后一个,用“(initget "C " )”(两个空格)调用,否则无效。
1 s0 h. @$ v# |% B) N 见例一。
, k/ o' u) N! o* X# V5 g************************************************
+ q% U% j6 e# G0 ^2 O;;例一- (defun ett_ct()" g# g$ f; {( b9 m8 L8 p
- (initget "C " ) ;关键字“C”及空格3 m' r$ E3 t" X- Z
- (setq s0 (entsel "\n设置颜色C / 选取文本:" ))
; o& k* A1 ?0 [% P k% r% ] - (cond ( (= s0 "C" ) (ett_col)) ;转设置颜色子函数" P' B! j& [7 a
- ( (= s0 "" ) nil) ;空格退出
1 p( a! V8 D# {, v4 y$ B1 y, } - ( (and (= (type s0) 'LIST) ;选择实体: Y9 y5 F# T! s6 G+ V/ t
- (= (cdr (assoc 0 (entget (car s0)))) "TEXT" ) ;判别文本) S6 v; G1 ^& ^! d6 I- P X% v3 i. b" R
- )
. \# w8 Y3 z2 n - ... ;操作内容/ t, L4 l$ ^3 r1 r# n% f
- (ett_ct) ;编辑后返回选择
4 ~0 ]" e& I0 v0 [: N - )
; \. P' t2 j; Z0 l - (t (ett_ct)) ;空选返回选择
n( U* D. n1 t4 R# V4 C - )
0 E2 t. S4 L) v) H' P. Z! f6 I3 J. r - )
复制代码 ************************************************
3 h' x$ P% p( g# ]( o/ H& p# x& F 有时需要进行复杂的判断,使用如“(= s0 "" )”语句可能不能准确判别输入的空格关键字与空选择,可以使用“(= (type s0) 'STR)”语句。
. R4 Q( k5 d4 o+ |
# h4 q7 E0 z8 T4.Lisp的暂停与while的特殊使用
, c S( f# r7 |4 \' P& b* s/ x; y Lisp一般在交互输入时才会暂停,如果只需要实现屏幕显示暂停,可使用grread函数,grread函数对所有合法的输入设备均会作出反应,有时我们只希望对键盘有反应,可使用while函数进行循环。
% u# ~8 L% ?5 x( Q) L3 ~3 X& a! {) C$ B7 i*******************- (princ "\nPress ENTER to continue:" )
5 e6 ]( \5 w. e' c - (while (/= (car (grread)) 2))
复制代码 *******************
( O. U: c7 T% Y: p) _) v3 V7 d while用于满足一定条件的循环,其标准语法为:3 t0 Q: _! A! h
(while testexpr [expr...]) % z' ?" a' E; E/ t. j
其中expr解释为“在 testexpr 为 nil 之前要求值的一个或多个表达式”,为可选项(在R14之前没有方括号,但仍为可选项)。
- X% r' ~8 ~! f) L4 @7 a 正常我们使用while时,总会有expr项,更多的时候,我们是为了expr项才会使用这种循环语句,所有我们往往有expr项是不可缺少的感觉。这里我们使用while函数的语法是while函数的特例,即没有expr项的情况。
4 a5 @, X( v R 如果希望对鼠标右键同时反应,可以使用:& K7 V% U3 p# @
*******************- (princ "\nPress ENTER to continue:" )
; z7 ?4 Y9 p0 v: j! D - (while (and (/= (setq a(car (grread))) 2) ;键盘
: V+ n5 ^, }4 H+ P) K% Y$ Q - (/= a 11) ;鼠标右键. ?, X p5 f$ f. Q
- (SHORTCUTMENU=0)
) Q8 @3 V' k" H# r - (/= a 25) ;鼠标右键
$ n5 u# k, x8 S) f - (SHORTCUTMENU≠0)
2 K$ G+ T1 G6 C - )
" I; x7 z* U, v' y. s T - )
复制代码 *******************
0 G7 `3 o7 d3 Y5 @$ X L1 d _! `/ n; @, D0 Q) P
5.输入距离/ N8 `- x" j) {( p, X( h$ I
Lisp语言中输入距离的函数为getdist,但我们有时需要输入负值,有时需要在输入距离的同时得到角度,使用getdist函数就显得无能为力,这时,我们可以灵活使用其它交互输入函数如getpoint、getcorner等,通过计算得到我们所需要的值。$ x+ H" y% z- A% r. C5 H
例二是一段输入长度的同时得到默认角度的代码,使用getpoint函数。
1 ~7 ^! p9 T6 T1 h) J) e( D. F************************************************
5 ? B+ w3 b" \% \/ a;;例二- (setq pt0 (getpoint "\n直线基点: " )" d7 s6 C6 R! W
- pt1 (getpoint pt0 "\n直线长度: " ) ;长度及角度可用键盘或鼠标定位. h5 g0 @9 N8 |; Y% Z+ }% q
- dst (distance pt0 pt1) ;计算长度
+ {$ j. B# C$ g% B J - ang (angle pt0 pt1) ;计算默认角度
* W9 H1 c9 `" H8 U/ P - ang1 (getangle pt0 (strcat "\n直线方向<" (angtos ang 1) ">: " ))
% @- E$ s7 S; v ?. B - )
复制代码 ************************************************/ z* T9 r9 K6 O" c) y) h# ]( F
例三是可以按阵列方式输入行列间距的代码,输入距离为正值,修改部分代码可输入负值,使用getcorner函数,同时使用initget的控制位128。2 U/ E% X7 y5 c7 x
************************************************+ T4 Z% T6 M5 I) d, \
;;例三- (defun lc_dist ()
6 G f- H# U7 \1 p% ]. m& F - (initget 128) ;允许任意输入3 H2 u0 |* f% h
- (setq disr (getpoint "\n指定单位单元或输入行间距: " ))
1 M. A! U; [1 ]* c% s6 B - (if (= (type disr) 'LIST) ;鼠标输入
$ m4 K* e+ } y( H1 D' { - (progn
2 K5 w( [; p% C7 I: G0 H# V) U - (initget 1)
4 x' @& u* }( M - (setq dis (getcorner disr "\n指定对角点: " ) ;鼠标输入对角1 D) W% B2 P1 D2 Z$ @& @0 t4 `
- disc (abs (- (car dis) (car disr))) ;正值行距
* G3 n, e a/ j+ R; Q$ {$ u( o - disr (abs (- (cadr dis) (cadr disr))) ;正值列距
3 o! h5 W% r) W" R3 y$ i! Y - ) ;计算行列间距: ^+ X' A0 M; |% M# p
- )
2 o# p# ?0 h5 C - (if (= (type disr) 'STR) ;键盘输入行距9 f: ~8 c4 ?, K* l( C: M- t
- (if (setq dis (distof disr)) ;判断输入的是否距离
/ o1 J$ V4 E4 J+ j; A4 [1 A! K - (progn
+ J. G6 h0 s! q1 ` - (initget 6)3 a" m+ C4 X) w9 u9 F. O
- (setq disc (getdist "\n输入列间距: " )) ;输入列距
9 p5 b0 w$ J: n. [- N+ s6 E - )
4 Q8 f! S$ R0 ?: ^( u2 N - (progn ;键盘输入格式不符返回+ W0 f" ?; c1 ?% x; m) v
- (princ "\n需要正数值或两个二维角点。" ): R2 K9 p) o- i2 Z, `8 g
- (lc_dist). s, l- b8 l2 A# E1 J5 C9 W% {
- )+ a: \+ m6 w( ` \2 v
- )
' t% T" d! _& U- o/ S+ E - (progn ;空输入返回
$ t4 J; s4 J% O6 c0 T/ H& s - (princ "\n需要正数值或两个二维角点。" )
6 n1 I ]) b- G. q0 } - (lc_dist)
/ z9 I& M8 O: {0 ~ - )- N7 h( f( Z# d% ?: y2 H
- )0 a$ i x6 r# w% m. o. N
- )
4 T, b, D) o$ g0 _& L: G2 J - )
复制代码 ************************************************
/ p0 \3 P# e/ c* ^2 W0 m& \# [& ]) ^3 K5 W Y+ u: P
6.数学运算函数的数量界限
* D# |! `) o# n9 X7 X1 ]" m$ e1 Z 在Lisp中对表中数据进行求和、求最大值等数学运算时,往往直观的对表直接赋予运算函数,使用语句如“(eval (cons 'MAX numlist))”,一般都可以进行计算,但当表中数据数量大于255时,将会出现错误“Bad argument value: does not fit in byte: 256”。 对于这种情况,我们不必对数据表进行分段,可以直接使用函数apply,语法更简单:(apply 'MAX numlist)。apply可将数据表传送给指定的函数进行求值而不受数据数量的影响。
. R( O( g1 b7 c; q6 I; M 受表中数据数量影响的数学运算函数有:+、-、*、/、max、min、logand及logior。
5 [' e$ [% ]( X/ c) C5 X. _, M0 m p- C, S3 W! }! f
7.选择集与表6 n. h4 k0 D! n5 z7 p; C
选择集是一种特殊结构的表,只能通过特定的函数进行操作,但这些函数对大量重复的操作只能通过循环实现,显得力不从心,不能体现Lisp语言表结构的优越性。2 `( U2 p7 S) Y- w, @9 B2 ~
其实我们只要通过存取实体名或实体句柄,将它们存为一个普通结构的表,完全可以通过常规表操作函数实现对实体的操作。" r! e/ t) b5 L- R0 z
例四是一段使用apply、mapcar函数联合求文本选择集中文本基点最大y值得代码,只是一个示例,如果结合VL-sort函数,可轻松实现对文本的排序。. ]4 X( j" ~! D: x
************************************************) ^' L2 m) L8 F1 _ r! H' Y
;;例四- (setq sl nil i -1)) f& ?5 m/ o) x0 m+ ^$ C
- (repeat (sslength (setq ss (ssget '((0 . "TEXT" ))))) ;选择文本2 _* j" L: |' u+ ~: W$ Z/ N
- (setq i (1+ i)# Y+ i" e9 ~$ F9 g: `
- en (ssname ss i) ;从选择集中取出文本
8 u/ w1 v* z5 i+ _5 l p - sl (cons en sl) ;构造包含实体名的表
, R& B1 J; m. P& I: k% a - )
/ P @4 W6 s3 v- d5 v - )1 k: B$ W, D' h6 p! u
- (setq maxy (apply 'max ;求文本基点最大y值. G O8 O: l C: w @6 u4 Q
- (mapcar4 O" r3 K! W4 W: O
- '(lambda (x)! {+ q# |( v; `, m
- (caddr (assoc 10 (entget x))) ;提取y值
; ^" ~! F$ T/ W5 o - )5 z' {. o) G t& X; P# n4 G
- sl
# J) m8 a$ j9 O* o - )
# R# g' Y5 C8 M0 r6 z - )
. [, y. O, @+ F3 s* d r" M- J5 Y - )
复制代码 ************************************************) r( n2 Y& o9 E, t% s! q
当然,选择集也有其优势的一面,比如对选择集中实体的删除操作非常简单、选择集中的实体不会重复及选择集可以与Acad命令交互使用等特征是一般表所不具备的,所以,编程时应根据程序要求,灵活运用。
3 D: K# _) o& S7 I' |7 \, \! [2 Y7 v" F2 L
8.cal的使用与加载* h1 ]! P& ~& c7 x0 I; F; s
Acad随机附带了一些外部定义命令,其中cal(计算器)命令是最常用的命令之一,在加载gromcal.arx后cal可以在Lisp程序中像其它函数一样使用,这就使得我们在程序中对文本的四则运算处理变得简单,如“(cal "1+2/3" )”,其中字符串"1+2/3"可以从图形的文本中提取,也可以是符合cal要求格式的任一字符串(详见Acad联机帮助)。
6 J8 f$ y& @8 k' r! R/ |) D 需要注意的是,在Acad中gromcal.arx只能加载一次,重复加载将使Acad以外退出(无提示)。需要使用cal函数的Lisp程序,应在程序尾部加上以下代码:
; a7 e2 l0 h4 [' P2 D*******************- (if (or (= (type c:cal) 'LIST) ;R14使用
/ H) ]6 R9 X7 x: f - (= (type c:cal) 'SUBR) ;R2000+使用5 q1 a9 G: y8 g& O
- )
' f3 s& p) Y1 q; [" o& Z - (arxload "geomcal.arx" ))
复制代码 ******************* ( O" U' B9 J6 `2 ?; f8 a
* v" H, y3 \9 E j0 B ^
9.Undo处理 i6 ^1 x" i- X& ]- m8 I+ A0 m% n. ^8 o
一个完善的程序应该有较好的出错处理,这是在所有Lisp教材上都提及的,但程序的Undo处理就说得很少或没有提及。
3 _( a, N d" _$ `/ Z: A 其实Undo处理对程序来说也是非常重要的,尤其对有较多输出的复杂程序而言,不能解决Undo问题,使用起来会极不方便。
1 F4 Y I9 W* o8 G: ? 对于Undo问题的解决,一种方法是尽量少用或不用command函数,即不调用原始命令,这是一种较好的方法,但必须注意的是,一段程序必须至少有一次调用command函数,否则Undo命令将取消程序运行前的前一次命令,解决的方法是在程序运行的起始位置加一个无谓的command,如“(command "color" "" )”。
* M/ m! J2 `: \: v 有时不使用command函数不能达到我们要求的一些功能,或使得程序过于复杂,我们可能需要使用一些command函数(原始命令),这是就应该在程序中进行Undo处理,即使用Undo命令的编组功能。
1 x/ G: j) F7 N7 M 例五是一段程序出错函数与Undo处理的示例。9 X7 S" M0 [/ s& t8 L
************************************************
/ Z/ \$ a' U) t6 j; C ?;;例五- (defun newerr (s) ;出错函数- U6 r1 y: @9 Q& [
- (if s r1 h3 z; ^4 C# {
- (progn2 L* ~5 s" [3 _" `" R
- (term_dialog) ;使用对话框时使用
4 f: t) o3 m# O3 w B5 Q" L - (if olderr (setq *error* olderr)) ;出错函数恢复
* Z" J, W. |: B1 s - (if oldvar (setvar ... oldvar)) ;系统变量恢复
4 r) `# Y- p# Y) c - (if olderr (setq *error* olderr)) ;出错函数恢复$ E0 b1 F2 q2 B; u5 f
- (command "_.undo" "_e" ) ;Undo编组结束/ [ ] g# v. S) d1 R! u
- )
( c& i& ?* ~* n! n - )
7 a" T8 k0 Y8 D - (princ)
& s$ B/ l8 d) p$ ^9 b+ g - )
& _8 L* A6 _: ]8 I
* ^5 A$ J- K8 c! }- (defun c:my(/ ...) ;主程序(主函数)2 L( ?0 f% B6 t) i& H" {
- (setvar "cmdecho" 0) ;取消命令回显提示
5 q( U* W0 M. J7 `. |# N( F% v2 N% ]1 w - (command "_.undo" "_BE" ) ;Undo编组开始3 v- V( Z4 {/ m- G! y, ?; N. _6 b
- (setq olderr *error* *error* newerr) ;调用自定义出错函数
' g& P0 }' E( i/ f) s - (setq oldvar (getvar ...)) ;保存相关系统变量
! d9 O/ ~% \6 i; N- k: e: \ - (setvar ... ;设置系统变量1 M8 ]- E2 v& z# H% ?& C
- ... ;程序段
/ j) n& \4 l2 O# b7 C - ...+ @ z- r m9 o9 m9 c0 G$ B3 B& [
- (setvar ... oldvar) ;恢复系统变量
* _( U$ G' p6 S% E - (setq *error* olderr) ;恢复出错函数
. }- t8 Q$ Z" W1 |: C- t5 C - (command "_.undo" "_E" ) ;结束Undo命令编组
% X4 \2 R/ j3 l( p2 A* E - (princ) ;取消程序返回值
% B' i! E, k" e. v0 G. @ - )
复制代码 ************************************************- F8 q& S+ ]7 `: G3 ?2 S
' _' T, C S4 P% Q
10.程序调试是块注释的使用
# r* }: b7 ?8 v1 q0 Z- _; y 我们经常会加上或屏蔽一段代码辅助程序调试,此时最常用的是在需要暂时屏蔽的代码前使用行注释符号“;”,对于较多的代码就需要使用块注释“;|——|;”,如果一段代码需要频繁屏蔽,将行注释与块注释组合使用,可以带来极大方便。
; C x' ]( l: U$ ?- | j, I" N- a8 v7 Q! c* a
[ 本帖最后由 woaishuijia 于 2008-10-8 13:07 编辑 ] |
|