明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 1419|回复: 53

[讨论] 新手从零开始的第一份LISP程序全纪录

    [复制链接]
发表于 2024-4-22 12:07 | 显示全部楼层 |阅读模式
本帖最后由 Gaudi 于 2024-4-29 10:12 编辑

本来想把每一篇涉及的知识点总结下,方便各位大佬翻阅查看。
结果我犯懒了……实在是嫌麻烦。
就做个超链接目录吧……

目录
第三篇  第一部分  新建图层
第三篇  第二部分  读取指定文件夹地址

第三篇  第三部分  读取指定excel表数据


第零篇 前言和叠甲

这是一份记录贴,里面充斥着大量非专业的概念和自己摸索出来的错误信息。
同时也基本上都是重复造轮子,只是在轮子上增加一些自己的贴纸和花纹而已。

甚至不知道发到这里合不合适。如果不合适,烦请版主移动……

那为什么还要买激活码发到论坛里呢。
主要是希望能记录下这份历程,方便自己回看。

然后是希望各种大佬路过的时候帮我解决下问题……

当然,如果能让跟我一样的小白lisp玩家能少走些弯路,能从中有些许收获,那就再好不过了。
是的,虽然这是一个具体的工程案例,但里面的代码思维和函数使用,我个人觉得还是有一定适用性。

我会以个人实际产生的需求出发,记录一步步写出这个lisp程序的历程,中间踩了多少坑、有了多少想法,都会随着逐行代码注释下来。

最终的成果是一系列的LSP文件:
新建图层、获取指定文件夹路径、excel数据读入、查找特定值、属性块插入、属性块数值修改、移动图层,合并而来的标志牌标注和护栏标注。
——以及踩过无数的坑,和获得无数的思考。

这是一个大工程,希望我不要断更。
如果有哪儿不专业或者写错了,请路过的大佬们直接指出来。

追更的朋友可点只看楼主。

耐心跟着这篇记录一起走,相信你看完了一定会有所收获。

开始!

第一篇  功能拆分和autolisp基础认知



0 起因

团队接了600多公里的安全防护设施项目,通过外业数据excel表成套产出工程量表格的工作早已完成,这次项目的时间卡点就在平面图布设上。
通过外业调查而来的桩号、类别(标志牌、砼护栏、波形梁护栏)、方向、处治方式,按一定的规则,将特定的块插入到具体的位置上,还要根据左右方向和主线角度确定块的选装方向,同时要根据不同的处治方式和类别分门别类放置到不同的图层中。

工程量巨大,按以往的经验,起码接近200个工时才能把这一个步骤搞定,还不一定保证完全正确。

如果涉及到删减、增改,那就是欲哭无泪。



1 开始的老办法

初步解决这个问题其实很早,通过CAD自带 dataextraction 命令,提取所有桩号点的坐标和内容,再通过 excel 转换命令,形成 -inser t和 -attedit 命令组,再复制到CAD命令行里执行。
这种操作还是很简单的。

可惜太复杂了点,只适合路线数量少、路线里程长的情况。
所以刚弄出适用成功就被封存了。

转头看向lisp。
工作七八年了,一直没怎么接触,这次不得不硬着头皮啃下来。




2 功能拆分

按我朴素的想法,计算机是很笨的。它只接受一句一句最简单、婴儿式的命令。
所以第一步就是步骤拆分,拆分到你觉得不能再拆分为止——这也是为什么最终产生了若干个子功能LSP程序。
后来我发现这个习惯好像挺不错,不同的功能单独测试,单独成块。

A 桩号标注(选择主线、得出实际长度、确定终点桩号、分隔成一米的间距、找到标注点、标注桩号)
B 新建图层(根据参数确定图层名称、根据图层名称确定现型线宽颜色)
C 获取指定文件夹路径(打开包含块的文件夹、获得这个文件夹的路径)
D 获取外业数据(打开excel、找到指定工作簿、找到指定工作表、找到指定数据范围、返回数据到CAD)
E 找到块插入位置(找到桩号位置、返回桩号和旋转角度)
F 插入属性块(根据块名找到块、插入块)
G 移动块(将插入的块移动到指定图层)
H 修改块属性(根据外业找到特定属性标签、修改属性)


八个子程序!最后还要合并成一个lisp程序。
没办法,开搞吧。



3 初步lisp了解(极具个人向)

不掉书袋去分析这个分析那个了。

核心就是用lisp执行cad命令,有三个方向:
一个是 command+CAD 命令,然后根据参数一步步填,这个本质上是模拟输入。
一个是调用lisp里自带的函数,输入特定参数。
一个是 vla 系列,这个有点绕,大致上相当于把CAD这个小圈子里的东西,转变为微软这个大佬搞的一个平台里都认识的东西,这个平台叫 com。转变后,再用这平台的通用命令去操作。


第一个最简单,能用这个解决就不用想着别的了。但是最简单的一般可操作性就最少,这次就碰见一个案例。
第二个和第三个都比较常见,理解难度第三个稍微复杂一点。
为什么要用第三个办法,有时候是因为必须,比如各软件之间数据的连接;有些时候是因为这种办法相比传统办法更加简单清晰;有些时候是传统办法不能达成需求。



4 lisp语言结构

不得不说,lisp括号匹配真麻烦啊……
每一句都要有括号匹配,一般提示“输入的列表有缺陷”就是这个错误。
这里要说下,为什么会提示“列表”而不是代码呢?
因为lisp语言就是一个列表语言,每一对括号就像是表里内容,然后一句一句由内往外或者由上往下运行。

  1. (defun c:Program (AAA \BBB)
  2. (XXX)
  3. )

Defun是固定开头。
“c:”表示可以使用函数名通过命令行去调用。
“Program”是函数名,可以在内部调用。
“AAA”是全局变量,意思可以传递给其他函数。
“BBB”是局部变量,只在自身函数内部使用。(我一般都懒得定义)

没了。
很简单不是吗。



5 基本函数 -command:调用CAD命令

command 的用法,无脑得很,你就打这个命令,然后看命令行需要什么就写什么。
  1. (command "line" Point1 Point2 "")

只要把两个点的坐标传递进去,就能直接画线。



6 基本函数-Setq:定义

有时候你可能会看到 set,好像也有这个功能。
不用管它,用 setq。
  1. (setq a 1)
把1的数值定义到a.
  1. (setq a b)
把b的数值定义为a.

这这种都是基础,比较常用的下面几种:
  1. (setq folderPath (GetFolderAddress))
运用上述所学!
“因为lisp语言就是一个列表语言,每一对括号就像是表里内容,然后一句一句由内往外或者由上往下运行。”
““Program”是函数名,可以在内部调用。”
那么从最右的 ( 开始及运行,先是 GetFolderAddress 调用这个自定义函数,把调用这个函数的结果,通过 setq 定义到 folderPath 变量。

来个复杂点的:
  1. (setq textPosition2 (polar textPosition (* textAngle (/ PI 180)) 20))
从最右的(开始运行。
/ PI 180,加减乘除就不说了,看一眼函数定义就知道。
(polar …… …… ……),这就是 polar 极坐标函数调用格式,套进去就行。
(setq textPosition2 (……)),把计算结果定位到 textPosition2 里去。




7 基本函数-if:条件判断

If语句的通用格式:
  1. (if XXX
  2.   (progn
  3.     (XXX)
  4.   )
  5.   (progn
  6.     (xxx)
  7.   )
  8. )
If 后面跟判定语句,然后先真后假。
如果有很多语句需要判定后一起执行,就加个 progn 框起来。

判定语句可以和 not、and、or 连接起来用,这个看名词也知道是怎么回事了。





8 结束

到这里,就结束了。
这个快结束了吗?
是的,这么快就结束了。

只要掌握了基本函数结构、command-CAD命令调用、setq-参数定义、if-条件判断,很多功能就可以写了。

比如说,上述子程序G移动图层:
  1. (defun MoveBlock2Layer (treatMent cateGory)
  2.   (setq layername (strcat treatMent cateGory))
  3.   (command "change" "l" "" "p" "la" layername "")
  4. )
这里面多了一个函数 strcat,字符串连接成一个新的字符串。

比如说,上述子程序F插入属性块:
  1. (defun InsertBlock (folderPath blockName textPosition textAngle)
  2.   (setq blkDef (strcat folderPath "\\" blockName ".dwg"))
  3.   (if (findfile blkDef)
  4.     (progn
  5.       (setq blkDef (strcat "*" blkDef))
  6.       (command "_insert" blkDef textPosition "1"  textAngle)
  7.     )
  8.     (progn
  9.       (princ (strcat blkDef "\n该地址没有找到相应块。"))
  10.       (exit)
  11.     )
  12.   )
  13. )
这里面多一个 finfile,就是判定这个块文件有没有,有就真,没有就假。
还有 princ,把字符串或者函数的值在命令行里显示出来。
Exit,停止。


比如加载线型:
  1. (defun loadlinetypes ()
  2.   (if (not (tblsearch "LTYPE" "TRACKS"))
  3.     (command "_.linetype" "Load" "TRACKS" (strcat "acadiso" ".lin") "")
  4.   )
  5.   (if (not (tblsearch "LTYPE" "DASHDOT"))
  6.     (command "_.linetype" "Load" "DASHDOT" (strcat "acadiso" ".lin") "")
  7.   )
  8.   (if (not (tblsearch "LTYPE" "ZIGZAG"))
  9.     (command "_.linetype" "Load" "ZIGZAG" (strcat "acadiso" ".lin") "")
  10.   )
  11. )
这里面多一个 tblsearch,tbl是table的简写,图层搜索嘛,有就真,没有就假。

所以不要畏难,非常简单。
你不能用lisp解决所有操作,可以先解决一部分操作嘛!
能偷懒就不要加班。

中午吃饭了,休息下。


评分

参与人数 8明经币 +11 金钱 +24 收起 理由
仲文玉 + 2 赞一个!
Gu_xl + 3 + 24 赞一个!
菜鸟初来乍到 + 1 为大佬点赞
love1030312 + 1 赞一个!
kucha007 + 1 赞一个!
liufii + 1
baitang36 + 1 好的开始就是成功的一半,加油!
start4444 + 1 坚持就是胜利!写码可以用飞诗编辑器

查看全部评分

发表于 2024-4-27 13:18 | 显示全部楼层
Gaudi 发表于 2024-4-27 10:52
大佬们,我这Lisp才会做第一个程序呢,就开始C#,是不是有点为时过早……

'((新建 . 60) (利用 . 4) (拆除 . 3) (更换 . 30)))
改写成(list(cons 新建  60) (cons 利用  4) (cons 拆除  3) (cons 更换  30)))
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2024-4-26 17:32 | 显示全部楼层
本帖最后由 Gaudi 于 2024-4-26 20:52 编辑

第三篇  第一部分  新建图层




终于写完第二篇了。

终于来到了第三篇。

啰里啰嗦这么久,其实只是为了得到曲线上桩号文字的具体位置信息。

桩号标注完了,才能将外业数据里相应内容定位到CAD主线上。

OK,开始第一步——功能拆分。


1 功能拆分

……哦前面已经拆了。

这里复制下:
A 桩号标注(选择主线、得出实际长度、确定终点桩号、分隔成一米的间距、找到标注点、标注桩号)
B 新建图层(根据参数确定图层名称、根据图层名称确定现型线宽颜色)
C 获取指定文件夹路径(打开包含块的文件夹、获得这个文件夹的路径)
D 获取外业数据(打开excel、找到指定工作簿、找到指定工作表、找到指定数据范围、返回数据到CAD)
E 找到块插入位置(找到桩号位置、返回桩号和旋转角度)
F 插入属性块(根据块名找到块、插入块)
G 移动块(将插入的块移动到指定图层)
H 修改块属性(根据外业找到特定属性标签、修改属性)

进入第一个子函数——新建图层。

仍然是将这个函数的功能进行拆分:

A 定义图层名称、颜色、现型、线宽
B 建立图层

看着真简单啊!开搞。



2 列表

从这里开始,除了数字 real 和文字 string,要引入一个新的数据格式 列表 list。
给个示例:
  1.   (setq prefixes '("新建" "利用" "拆除" "更换"))
  2.   (setq suffixes '("标志牌" "砼护栏" "波形梁护栏" "挡墙"))

函数仍然是那个 steq 函数,但是后面多了个用 ' 开头的东西。
这个东西就是告诉程序,后面的东西是一起的。

为什么要这么做,原因其实很煎蛋。
观察下函数调用的格式:
  1. (函数名 参数1 参数2)

是不是跟列表一模一样?那你不加个 ' ,电脑怎么知道这个括号里是列表还是函数调用?

它那么笨。

除了示例里给直接用 " 括起来的文本定义的列表,还可以是数字,还可以是参数。
甚至还可以是列表。


3  列表中的列表

先看示例:
  1.   (setq colors '((新建 . 60) (利用 . 4) (拆除 . 3) (更换 . 30)))
  2.   (setq linetypes '((标志牌 . "CONTINUOUS") (砼护栏 . "TRACKS") (波形梁护栏 . "DASHDOT") (挡墙 . "ZIGZAG")))
  3.   (setq linewidths '((标志牌 . 15) (砼护栏 . 30) (波形梁护栏 . 30) (挡墙 . 30)))


点对数据,一个点,左右各有一个数据。
我不知道大佬是怎么理解这个东西的。
我理解它TM就是个列表,不过只能是二维,也就是两个数据。
为了简写,不需要每次都加个 ' ,搞得程序复杂,用 . 来代替空格。


4  列表的作用

有朋友会问,我搞列表干啥啊?
那肯定是为了偷懒呗。

  1. (foreach prefix prefixes
  2.     (foreach suffix suffixes
  3.       (setq layername (strcat prefix suffix))


简简单单,定义个图名。
两个 foreach 函数,直接通过 strcat 函数连接起来,形成十六个图名。
现在只要针对每个图名,确定图层参数就OK了。


5  建立图层

新函数!entmake, entity make,建立实体。
这函数贯穿整个autolisp。

entmake 后面紧跟着的就是一个 list。
干嘛用的?朋友们打开CAD,对着任何一个实体,输入 list 。
显示的东西,就是 entmake 后面应该跟着的东西。
里面是点对,所以用这个形式:
  1. (entmake (list
  2.           (cons 0 "LAYER")
  3.           (cons 100 "AcDbSymbolTableRecord")
  4.           (cons 100 "AcDbLayerTableRecord")
  5.           (cons 70 0)
  6.           (cons 2 layername)
  7.           (cons 6 (cdr (assoc (read suffix) linetypes)))
  8.           (cons 62 (cdr (assoc (read prefix) colors)))
  9.           (cons 370 (cdr (assoc (read suffix) linewidths)))
  10.       ))

重点来了:
前面的数字是干什么的?


6   DXF组码

CAD中所有的实体,底层全是一个带着若干组点对的列表。
点对前面的数字是类型,后面是数据。

比如说,图层这个东西,0代表这是个图层,100代表——我也不知道是啥反正必需,70代表可见性,2代表图名,6代表线型,62代表颜色,370代表线宽,290代表打印。

理解了吗?CAD本质上是通过这东西创建实体的。


7 新函数

cdr,新函数。assoc,新函数。

个人老规矩,先说全称。
说 cdr 之前,先聊聊 car。
car = Contents of the Address part of Register,内容  位于  这个  地址   部分  在  寄存器。

……什么JB玩意儿。
看不懂就对了,这东西估计有几十年了,是当年程序员们直接操作硬件时候的历史残留。
你可以理解为程序员们的恶意。

功能其实很简单:取列表的第一个数值。

好,那 cdr =Contents of the Decrement part of Register,内容  位于  这个  减少  部分  在  寄存器。
取列表从第二个数值到最后一个数值。


那 cadr 呢? = Contents of the Address-Decrement part of Register
取列表从第二个数值到最后一个数值,再取新列表的第一个数值。


那 caar 呢? = Contents of the Address-Address part of Register
取列表的第一个数值,再取新列表的第一个数值。

那 cddr 呢? = Contents of the Decrement-Decrement part of Register
取列表从第二个数值到最后一个数值,再取新列表从第二个数值到最后一个数值。

那 cadr 呢? = Contents of the Address-Decrement part of Register
取列表从第二个数值到最后一个数值,再取新列表从第二个数值到最后一个数值,再取新列表的第一个数值。

那 cdddr 呢?= Contents of the Decrement-Decrement-Decrement part of Register
取列表从第二个数值到最后一个数值,再取新列表从第二个数值到最后一个数值,再取新列表从第二个数值到最后一个数值。

能无限循环下去吗?
不行。
……你说他们程序员是不是恶意满满且有什么大病。

所以后来一般只用到 car cdr caar cadr cdar cddr ,到后面用得是 nth.
nth = 1rd 2nd 3rd 4th 5th 6th ……

  1. (nth 0 (list))

就是这么简单!——注意电脑从0开始计数。

你说说,就不能干点人事吗。



上述函数,都是当你知道要取第几位,直接取列表中的数值。
如果不知道呢?我只知道点对列表前面那个数值。
难不成一个个取了用 if 和 equal 比对?

熟悉 excel 的朋友,知道有个函数叫 vlookup。打工人本命函数了属于是。
autolisp 中,同样有这么个函数叫 assoc = association.


通过查找第一个参数,返回对应参数的后续数值。




8  错误修复


将上述代码进行调试,结果发现报错。
仔细一看,原来是没有加载线型。


这个代码就很简单了,很早之前就放出来过:
  1.   (if (not (tblsearch "LTYPE" "DASHDOT"))
  2.     (command "_.linetype" "Load" "DASHDOT" (strcat "acadiso" ".lin") "")
  3.   )

都是已经学习过的函数,就不再过多赘述了。

回复 支持 1 反对 0

使用道具 举报

发表于 2024-4-25 01:53 | 显示全部楼层
绝对是大佬小号,此贴会火,我说的,占个楼
回复 支持 1 反对 0

使用道具 举报

发表于 2024-4-22 19:43 | 显示全部楼层
给想学lisp的坛友注入了一针强心剂,很好。
回复 支持 1 反对 0

使用道具 举报

发表于 2024-4-24 19:33 | 显示全部楼层
tranque 发表于 2024-4-24 14:00
有啥支持MD语法的论坛吗

论坛貌似没有,但是可以导出html的,而且博客类很多支持md
回复 支持 1 反对 0

使用道具 举报

发表于 2024-4-22 12:37 | 显示全部楼层
坚持每天写一点  不断积累总结   就进步了
回复 支持 1 反对 0

使用道具 举报

发表于 2024-4-22 12:39 | 显示全部楼层
加油加油大佬
发表于 2024-4-22 13:52 | 显示全部楼层
感谢大佬分享,我也是初学者,正在慢慢摸索
发表于 2024-4-22 14:05 | 显示全部楼层
坚持就是胜利,加油
发表于 2024-4-22 14:41 | 显示全部楼层
感谢分享,我也想学呢,不知道从何学起。
发表于 2024-4-22 15:05 | 显示全部楼层
我也在观望中,想学习Autolisp呢,得挤时间学学了
 楼主| 发表于 2024-4-22 15:20 | 显示全部楼层
本帖最后由 Gaudi 于 2024-4-22 20:15 编辑

晕,哪位大佬知道怎么在论坛里发代码?
格式怎么调都不对……

代码等大佬们告诉怎么写到论坛里再统一修改吧。
继续!

第一篇 增补

每写完一篇,我会在后面补充这一篇涉及到内容,我曾经踩过的坑和一些收获。

1
如果想在命令行中调用函数,一定记得加“c:”。
我因为这个点,百思不得其解为什么不能调用。
最后才发现这个坑。

2
说来惭愧,完全没找到写代码的专业工具。——当然现在知道了,用VS CODE或者飞诗编辑器。
导致我在查询反括号、输入函数名称错误、中英文括号上,浪费了大量的时间。
血泪。
如果你也是用TXT手敲,记得另存为的时候编码要选ASNI。

3
说来也惭愧,实在不会用VLIDE的调试和断点功能。
希望有大佬能给个教程。——论坛里的基础手册也看了。
所有的调试都是用princ来打印输出感觉有问题的参数数值。
当然这个手法也是要必须掌握。
在感觉有问题的代码前后,把所有涉及到的参数全部打印出来,至少把错误定位出来。

4

  (vl-load-com)
  (setvar "CMDECHO" 0)-  (setvar "CMDECHO" 1)
  (setvar "OSMODE" 0)
  (command "zoom" "e")

就这四行代码,每一行代码都至少卡了我几个小时报错。

我一个一个说。

  (vl-load-com),前面不是说了autolisp执行操作有三种方式,第三种就是vl吗?必须加个这个,把基础环境给加载上。
(setvar "OSMODE" 0),这个是关闭CAD焦点选择,执行一些画线、偏移命令的时候,就是会因为这个开关打开,不晓得怎么就出错了。
(command "zoom" "e"),这个是开局先显示所有对象。autolisp执行操作的第一种方式,直接使用命令行模式,低版本CAD在操作视窗以外的对象,直接就会内存报错。血泪啊,弄了一晚上找原因。
  (setvar "CMDECHO" 0)-  (setvar "CMDECHO" 1),这个是关闭命令行回显,加快代码进度,方便查找princ结果。当然结尾你记得再把回显打开。

就这四条,你甭管写得啥,先全丢进去,保证没错。

5

if语句,你甭管后面是一条执行还是几条执行,统统加上progn。
不知道以为忘记加这个多少次,导致执行失败。
——当然我这也是站着说话不腰疼,其实自己后面写的如果只有一条语句也懒得加了。

6
在这里关于参数的命名,有几个点我实际操作下来,给这么几个小小的建议:
A 采用英文,别用中文和拼音,英文的单词首字母大写。看得清晰些。
B 前括号前面加个空格,后面不加空格。也是看得清晰些。
C 至少用两个单词,一个单词很容易就和自带函数冲突了(踩过的坑)。
D 有时候两个单词MD也会跟自带函数冲突。所以终极办法是编辑器……它会提醒你的。



发表于 2024-4-22 15:31 | 显示全部楼层
写得很好,我也好想认真的学习学习
发表于 2024-4-22 17:13 | 显示全部楼层
收藏,坐等大佬更新
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-5-4 12:26 , Processed in 2.150340 second(s), 26 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表