Gaudi 发表于 2024-4-22 12:07:52

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

本帖最后由 Gaudi 于 2024-5-8 16:56 编辑

本来想把每一篇涉及的知识点总结下,方便各位大佬翻阅查看。结果我犯懒了……实在是嫌麻烦。就做个超链接目录吧……
目录第零篇 前言和叠甲第一篇功能拆分和autolisp基础认知第二篇第一部分桩号数值转标注文字第二篇第二部分读取主线起点并计算起点角度第二篇第三部分标注桩号线和桩号文字第二篇第四部分主代码函数调用第三篇第一部分新建图层
第三篇第二部分读取指定文件夹地址
第三篇第三部分读取指定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语言就是一个列表语言,每一对括号就像是表里内容,然后一句一句由内往外或者由上往下运行。
(defun c:Program (AAA \BBB)
(XXX)
)
Defun是固定开头。“c:”表示可以使用函数名通过命令行去调用。“Program”是函数名,可以在内部调用。“AAA”是全局变量,意思可以传递给其他函数。“BBB”是局部变量,只在自身函数内部使用。(我一般都懒得定义)
没了。很简单不是吗。


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

command 的用法,无脑得很,你就打这个命令,然后看命令行需要什么就写什么。
(command "line" Point1 Point2 "")
只要把两个点的坐标传递进去,就能直接画线。



6 基本函数-Setq:定义
有时候你可能会看到 set,好像也有这个功能。不用管它,用 setq。(setq a 1)把1的数值定义到a.(setq a b)把b的数值定义为a.
这这种都是基础,比较常用的下面几种:(setq folderPath (GetFolderAddress))运用上述所学!“因为lisp语言就是一个列表语言,每一对括号就像是表里内容,然后一句一句由内往外或者由上往下运行。”““Program”是函数名,可以在内部调用。”那么从最右的 ( 开始及运行,先是 GetFolderAddress 调用这个自定义函数,把调用这个函数的结果,通过 setq 定义到 folderPath 变量。
来个复杂点的:(setq textPosition2 (polar textPosition (* textAngle (/ PI 180)) 20))从最右的(开始运行。/ PI 180,加减乘除就不说了,看一眼函数定义就知道。(polar …… …… ……),这就是 polar 极坐标函数调用格式,套进去就行。(setq textPosition2 (……)),把计算结果定位到 textPosition2 里去。



7 基本函数-if:条件判断
If语句的通用格式:(if XXX
(progn
    (XXX)
)
(progn
    (xxx)
)
)If 后面跟判定语句,然后先真后假。如果有很多语句需要判定后一起执行,就加个 progn 框起来。
判定语句可以和 not、and、or 连接起来用,这个看名词也知道是怎么回事了。




8 结束

到这里,就结束了。这个快结束了吗?是的,这么快就结束了。
只要掌握了基本函数结构、command-CAD命令调用、setq-参数定义、if-条件判断,很多功能就可以写了。
比如说,上述子程序G移动图层:(defun MoveBlock2Layer (treatMent cateGory)
(setq layername (strcat treatMent cateGory))
(command "change" "l" "" "p" "la" layername "")
)这里面多了一个函数 strcat,字符串连接成一个新的字符串。
比如说,上述子程序F插入属性块:(defun InsertBlock (folderPath blockName textPosition textAngle)
(setq blkDef (strcat folderPath "\\" blockName ".dwg"))
(if (findfile blkDef)
    (progn
      (setq blkDef (strcat "*" blkDef))
      (command "_insert" blkDef textPosition "1"textAngle)
    )
    (progn
      (princ (strcat blkDef "\n该地址没有找到相应块。"))
      (exit)
    )
)
)这里面多一个 finfile,就是判定这个块文件有没有,有就真,没有就假。还有 princ,把字符串或者函数的值在命令行里显示出来。Exit,停止。

比如加载线型:
(defun loadlinetypes ()
(if (not (tblsearch "LTYPE" "TRACKS"))
    (command "_.linetype" "Load" "TRACKS" (strcat "acadiso" ".lin") "")
)
(if (not (tblsearch "LTYPE" "DASHDOT"))
    (command "_.linetype" "Load" "DASHDOT" (strcat "acadiso" ".lin") "")
)
(if (not (tblsearch "LTYPE" "ZIGZAG"))
    (command "_.linetype" "Load" "ZIGZAG" (strcat "acadiso" ".lin") "")
)
)这里面多一个 tblsearch,tbl是table的简写,图层搜索嘛,有就真,没有就假。
所以不要畏难,非常简单。
你不能用lisp解决所有操作,可以先解决一部分操作嘛!
能偷懒就不要加班。

中午吃饭了,休息下。



Gaudi 发表于 2024-4-26 17:32:16

本帖最后由 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。
给个示例:
(setq prefixes '("新建" "利用" "拆除" "更换"))
(setq suffixes '("标志牌" "砼护栏" "波形梁护栏" "挡墙"))
函数仍然是那个 steq 函数,但是后面多了个用 ' 开头的东西。
这个东西就是告诉程序,后面的东西是一起的。

为什么要这么做,原因其实很煎蛋。
观察下函数调用的格式:
(函数名 参数1 参数2)
是不是跟列表一模一样?那你不加个 ' ,电脑怎么知道这个括号里是列表还是函数调用?

它那么笨。

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


3列表中的列表

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

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


4列表的作用

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

(foreach prefix prefixes
    (foreach suffix suffixes
      (setq layername (strcat prefix suffix))

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


5建立图层

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

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


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 ……

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

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



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

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


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




8错误修复


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


这个代码就很简单了,很早之前就放出来过:
(if (not (tblsearch "LTYPE" "DASHDOT"))
    (command "_.linetype" "Load" "DASHDOT" (strcat "acadiso" ".lin") "")
)
都是已经学习过的函数,就不再过多赘述了。

kozmosovia 发表于 2024-4-27 13:18:14

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




'((新建 . 60) (利用 . 4) (拆除 . 3) (更换 . 30)))
改写成(list(cons 新建60) (cons 利用4) (cons 拆除3) (cons 更换30)))

逍遥无声 发表于 2024-4-25 01:53:55

绝对是大佬小号,此贴会火,我说的,占个楼

lxl217114 发表于 2024-4-22 19:43:23

给想学lisp的坛友注入了一针强心剂,很好。

你有种再说一遍 发表于 2024-4-24 19:33:14

tranque 发表于 2024-4-24 14:00
有啥支持MD语法的论坛吗

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

guosheyang 发表于 2024-4-22 12:37:09

坚持每天写一点不断积累总结   就进步了

magicheno 发表于 2024-4-22 12:39:48

加油加油大佬

yk1216 发表于 2024-4-22 13:52:01

感谢大佬分享,我也是初学者,正在慢慢摸索

帝都划水王 发表于 2024-4-22 14:05:02

坚持就是胜利,加油

kx820506 发表于 2024-4-22 14:41:45

感谢分享,我也想学呢,不知道从何学起。

tranque 发表于 2024-4-22 15:05:44

我也在观望中,想学习Autolisp呢,得挤时间学学了

Gaudi 发表于 2024-4-22 15:20:45

本帖最后由 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也会跟自带函数冲突。所以终极办法是编辑器……它会提醒你的。


czb203 发表于 2024-4-22 15:31:51

写得很好,我也好想认真的学习学习

52pj 发表于 2024-4-22 17:13:52

收藏,坐等大佬更新
页: [1] 2 3 4 5 6
查看完整版本: 新手从零开始的第一份LISP程序全纪录