明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
楼主: Gaudi

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

    [复制链接]
发表于 2024-4-24 23:50 | 显示全部楼层
学习中,顶贴
发表于 2024-4-25 01:53 | 显示全部楼层
绝对是大佬小号,此贴会火,我说的,占个楼
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2024-4-25 11:46 | 显示全部楼层
第二篇  第三部分  标注桩号线和桩号文字




来到最后一个通用函数。

照样三板斧:功能拆分、代码实现基本功能、错误修复和需求完善。

1 功能拆分

A 确定直线标注起点和长度。
B 计算直线标注终点。
C 标注直线。
D 确定文字标注内容、位置、角度。
E 标注文字。

其中直线长度、文字高度,是开始自己输入。
直线起点是通过vla函数直接读取。
文字内容、切线角度,是前文两个函数返回。

因为桩号线是标注在曲线两侧,所有将直线标注拆分为向左侧标注和右侧标注,每一侧标注一半直线长度。



2  代码实现

首先是计算直线标注终点,在已知起点坐标和标注方向的前提下,用 polar 函数计算另外一点。
别的办法一半碰不到,不去纠结。

polar 函数的标注格式如下:
  1. (polar pt ang dist)


照样给个示例:
  1. POLAR Point1 (+ (* PI 0.5) ANG) (/ normalLength 2)


注意 polar 函数需求的角度是弧度形式,逆时针方向旋转为正。
加π/2等于逆时针旋转九十度,切线方向变为曲线左侧垂线方向。

同理可以得到,加3π/2等于逆时针旋转270度,切线方向变为右侧垂线放线。

两个方向上的距离都是一半直线长度。

得到起点、终点坐标后,开始标注直线。

太简单了朋友们,用最简单的办法:
  1. (command "line" Point3 Point2 "")

你就在CAD里输入 line ,然后看提示一步一步写就行。

同理,输入 text ,一步一步看。
确定文字标注样式,这里选择 中下 。

你会发现,文字标注的角度格式需求是角度。
那么通过 angle 获取的文字角度要进行一个角度弧度转换。

代码如下:
  1. (setq ANG2 (/ (* (ANGLE Point2 Point1) 180) PI))
  2. (command "text" "bc" "m2p" Point2 Point3 fontHeight ANG2 ZHK+M)


3  错误修复和需求完善

太煎蛋了,一路走下来,第一次没有报错。

这时候就要开始做桩号标注位子的选择了。

根据标注位置的不同,分为左侧、右侧和中间。中间的代码刚刚已经写了。

那左侧和右侧相比已经实现的基础中间代码,有什么区别呢?

第一,标注直线的长度不再除以2。
第二,桩号文字的对齐方式不再是中下,而是左下和右下。

那么实现起来就非常煎蛋了:
  1. (setq Point2 (POLAR Point1 (+ (* PI 0.5) ANG) normalLength))
  2. (command "line" Point1 Point2 "")
  3. (setq ANG2 (/ (* (ANGLE Point2 Point1) 180) PI))
  4. (command "text" "br" Point2 fontHeight ANG2 ZHK+M)


另外一侧一模一样。


到这里就只剩下整合了。

可以用IF语句,判断、执行、判断、执行、判断、执行。

但这里我采用了另外一种函数 COND 。
condition的简称。

形式如下:
  1. (cond
  2.    (条件1
  3.         (执行1)
  4.     )
  5.     (条件2)
  6.         (执行2)
  7.     )
  8. )


不用管那么多,超过两种选择就用cond。

 楼主| 发表于 2024-4-25 17:10 | 显示全部楼层
第二篇  第三部分  增补




1

经历了前面一个函数略显复杂的VLA函数,这次的标注函数,煎蛋又轻松。


所有代码涉及到的知识,均未超出开篇所述的几个基础函数。

所以大家一定不要畏难。

代码这点东西,如果你纯粹自用,单个功能的核心实现代码绝对不超过6行。

真得,不信你自己写一写,实现一个功能的核心实现代码,基本上就几行,能到6行了,那都是极其复杂的东西了。

你想想,一个拆分到不能再拆分的原子功能,需要转6个弯,这台大几千上万块的大聪明机器才明白。

是不是有点过于卧龙凤雏了?

那有朋友就会问了,你骗我!随便看看都是几百行。

很多原子功能组合起来的嘛。

况且一个程序的90%代码是用来处理以下功能:
A 读取不在编写者意图中的数据,并提示
B 采用不在编写者意图中的格式,并提示
C 输出不在编写者意图中的数据,并提示
D 实现不在编写者意图中的功能,并提示
E 大量初始设定
F 大量错误提示
G 大量参数读取
H 可能还要附带图形界面

我将其称之为“沟通代码”……
你自己用,这部分代码完全可以舍去。
因为和自己沟通再顺畅不过了。



2

不扯淡,回到具体代码。

这里需要注意CAD中角度的描述。弧度和角度。

一般来说,程序自带的函数需求的都是弧度,需要用户选择的都是角度。

比如 text insert 等等,一般都是要求角度。

回顾下角度和弧度的互转代码:
  1. (* (/ textAngle PI) 180)
  2. (* textAngle (/ PI 180))


角度的起始方向是X轴正,沿逆时针旋转。



3

text 文字标注的时候,不管干啥,一定记得定义对齐方式。

会大大方便以后的操作。
 楼主| 发表于 2024-4-26 12:21 | 显示全部楼层
第二篇  第四部分  主代码函数调用




我是不是有点太磨叽了……几天了,才到主代码。
所有准备工作全部就绪,开始写主代码。





1

起点和终点煎蛋,且格式一样,没有什么特殊的地方。
直接调用三个函数。

代码如下:
  1.   (setq startZH startZH_Input)
  2.   (stringZH startZH)
  3.   (setq starPoint (VLAX-CURVE-GETSTARTPOINT OBJCenterLine))
  4.   (setq startAngle (angleCAL OBJCenterLine starPoint))
  5.   (label normalAngle normalLength startAngle starPoint fontHeight ZHK+M)


  1.   (setq endZH (+ totalDistance startZH_Input))
  2.   (stringZH endZH)
  3.   (setq endPoint (VLAX-CURVE-GETENDPOINT OBJCenterLine))
  4.   (setq endAngle (angleCAL OBJCenterLine endPoint))
  5.   (label normalAngle normalLength endAngle endPoint fontHeight ZHK+M)

一定一定要把参数命名写好,事半功倍的行为。




2 功能拆分

中间桩号就比较麻烦,这里要引入一个新函数 repeat ,用于循环标注。

照样开始拆分功能。

A 获得主线的实际长度。
B 根据标注长度和标注间距,计算出需要标注多少个桩号。
C 计算实际桩号的标注间距。
D 第二个桩号调整(因为起始桩号不一定是整桩,所以起始桩号与第二个桩号之间的距离不一定和标注间距相等)。
E 标注桩号。
F 将标注点加上实际标注间距。
G 标注桩号。



3 获得曲线长度

通过两个VLA函数获取。
  1. (setq OBJCenterLine_Distance (VLAX-CURVE-GETDISTATPOINT OBJCenterLine (VLAX-CURVE-GETENDPOINT OBJCenterLine)))

VLAX-CURVE-GETENDPOINT (定义 - 曲线 - 获得 终点),传递一条VLA曲线,获得终点坐标。
VLAX-CURVE-GETDISTATPOINT  (定义 -  曲线 - 获得 起点 距离),传递一个VLA曲线的坐标,获得与起点的距离。

VLA函数真是又长又软啊。

4 计算桩号个数、实际标注间距和实际标注个数

  1.   (setq trueLabelSpace (/ OBJCenterLine_Distance (/ totalDistance labelSpace)))
  2.   (setq NN (FIX (/ OBJCenterLine_Distance trueLabelSpace)))

这个就不再解释了。

5 第二个桩号调整
  1. (setq midZH (+ startZH_Input (- labelSpace (REM startZH_Input labelSpace))))

首先,将起始桩号除以标注间距,再取余数,得到碎桩数值。
然后将标注间距减去碎桩数值,得到实际间距。
然后将起点桩号加上实际间距,得到实际桩号。

6 非首桩号
  1. (setq midZH (+ labelSpace (* UU labelSpace) (* (FIX (/ startZH_Input labelSpace)) labelSpace)))

起始桩号除以标注间距,取整,再乘标注间距,就能得到起始桩号往小的方向,最近的一个标注间距倍数的桩号。
比如标注间距20米,起始231.11米,先得到11.xxxx,取整为11,乘20得220。

先固定加一个标注间距,得到第二个桩号数值。
这里是240。
UU 代表这是第几个桩号,乘以标注间距,就依次得到了每个桩号。

6 标注点

  1. (REM startZH_Input labelSpace)

首先,将起始桩号除以标注间距,再取余数,得到碎桩数值。
  1. (- trueLabelSpace (REM startZH_Input labelSpace))

然后将实际标注间距,减去碎桩数值,就得到起始桩号和第二个桩号那一段的实际标注距离。
  1. (+ (* UU trueLabelSpace) (- trueLabelSpace (REM startZH_Input labelSpace)))

然后就是加上第几个桩号乘以实际间距,就得到相应的标注点距离了。
  1. (setq midPoint
  2.       (VLAX-CURVE-GETPOINTATDIST OBJCenterLine
  3.         (+ (* UU trueLabelSpace) (- trueLabelSpace (REM startZH_Input labelSpace)))
  4.       )
  5.     )

VLAX-CURVE-GETPOINTATDIST ,(定义 - 曲线 - 获得 点 按照 距离)
自然语言编程来袭,传递曲线和距离,获得点坐标。




7 标注

获得桩号文字和标注点后,剩下的就是调用函数了。
  1.     (setq midAngle (angleCAL OBJCenterLine midPoint))
  2.     (label normalAngle normalLength midAngle midPoint fontHeight ZHK+M)



8 repeat 函数的使用

repeat 函数需要给一个上限,告诉程序运行这么多次。
其通用模板为:
  1. (repeat NN
  2.     (XXX)
  3. )


repeat 函数很煎蛋,只适合用于循环次数固定的用法。
如果不知道循环次数,但知道循环结束条件,可以采用 while 。
唯一的变化就是将上限NN数值变为判定语句。
发表于 2024-4-26 14:30 | 显示全部楼层
很详细,学习了。
 楼主| 发表于 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-26 22:57 来自手机 | 显示全部楼层
我建议lsp学点就差不多了  少走弯路  直接学C#
发表于 2024-4-27 02:04 | 显示全部楼层
可以学c#了,很多概念lisp学不到的,例如数据结构
发表于 2024-4-27 10:25 | 显示全部楼层
又不是专业程序猿,数据结构学不学的意义不大。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-5-7 03:25 , Processed in 0.275387 second(s), 18 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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