Autolisp 没有为某些用户正确设置块属性 1.攻击2.操作系统模式3.用户输入4.局部变量与全局变量5.检查块是否存在6.出错时重置环境将它们放在一起

问题描述

第一次提问,希望我能很好地描述问题。

我们公司有一个 Autolisp 代码,有几个人使用相同版本的 AutoCAD,但对于一些用户,lisp 已停止正常运行。

lisp的功能如下:

  • 用户运行 lisp
  • 该程序要求以下内容
    • 块的比例
    • 中文本的前缀
    • 一个区块实体的编号
    • 运行号的增量
    • 在哪里放置第一个

这应该导致一个带有标记的块和具有以下格式(前缀)的文本(如果数字不包含三个数字,则可能是中间部分)(运行数字),例如PT001 或 PX100。

然而,有些用户并没有这样做,而是遇到了缺少前缀和数字的情况,并开始只看到上述可能的文本中间部分,而其他时候,同一用户可能会遇到只显示前缀的情况。标记按原样显示,但文本无法按预期工作。

非常感谢您对分析以下代码是否存在缺陷的任何帮助。

如果代码看起来“完美无缺”,我认为块或其属性有问题。

-E

(defun c:pointnumber()
(setvar "ATTDIA" 0)
(setq sc (getreal "\nEnter scale: "))
(setq px (getstring "\nSet prefix for point number: "))
(setq nr (getint "\nThe number for the first point: "))
(setq ic (getint "\nIncrement of the number: "))
(setq point 1)
(while (/= point nil)
    (setq point (getpoint "\nChoose a point: "))
    (if (/= point nil)
        (progn
        (setq inr (itoa nr))
        (if (< nr 100) (setq md "0"))
        (if (< nr 10) (setq md "00"))
        (if (> nr 99) (setq md ""))
        (setq ph (strcat px md inr))
        (command "insert" "pointnumber" point sc sc 0 ph)
        (setq nr (+ nr ic))
        )
    )
)
(setvar "ATTDIA" 1)(princ)
)

解决方法

您当前的代码存在许多问题:其中一些可能仅仅被认为是不好的做法,如果用户使用无效数据进行响应,一些会导致程序失败,而另一些会导致程序失败或出现意外行为取决于执行程序的 AutoCAD 环境的设置。

1.攻击

您所描述的行为的罪魁祸首可能是 ATTREQ 系统变量,它决定了用户是否会收到作为 INSERT 命令一部分的属性值提示。如果 ATTREQ=0 在程序运行时,块将以其默认属性值插入。

您可以通过在调用 INSERT 命令之前存储此系统变量的当前值并将其设置为 1 来确保环境之间的行为一致(以确保发出属性提示),然后在命令之后或程序结束时恢复原始值。

例如:

(defun c:test ( / atr )
    (setq atr (getvar 'attreq))
    (setvar 'attreq 1)

    ;; ... Do your thing

    (setvar 'attreq atr)
    (princ)
)

2.操作系统模式

通过 AutoLISP 向命令提供点数据时,该点将受到提供该点时处于活动状态的任何对象捕捉模式的影响。我在回答 here 中更详细地描述了这一点。

避免这种情况的最简单方法是使用 "_non""_none" 对象捕捉修饰符指示 AutoCAD 忽略后续点输入的所有对象捕捉模式,例如:

(command "insert" "pointnumber" "_non" point sc sc 0 ph)

3.用户输入

您应该考虑缺少用户输入或无效用户输入以避免在程序执行过程中出现错误 - 这可以通过使用 if 语句或 initget 函数轻松实现,例如:

(initget 7) ;; Prevents Enter,zero,or negative numbers
(setq sc (getreal "\nEnter scale: "))

或者:

(initget 6)
(if
    (and
        (setq sc (getreal "\nEnter scale: "))
        (setq px (getstring "\nSet prefix for point number: "))
        (setq nr (getint "\nThe number for the first point: "))
        (setq ic (getint "\nIncrement of the number: "))
    )
    ;; ... Do your thing
)

或者,您可以使用我在 Prompting with a Default Option 教程中描述的方法之一为每个提示配置默认值,例如:

(setq sc (cond ((getreal "\nSpecify scale <1.0>: ")) (1.0)))

4.局部变量与全局变量

目前,您程序中的所有变量都是全局变量:也就是说,它们是在文档(绘图)命名空间中定义的,并且即使在程序完成执行后也会保留其值。

正如我在关于 Localising Variables 的教程中所描述的,如果这些变量无意中与其他程序使用的全局变量共享它们的名称,或者当程序在其中构建列表或其他累积数据结构时,这可能会导致问题一个循环。

除非程序的正确运行需要使用全局变量,否则我建议将这些变量声明为函数的局部变量,例如:

(defun c:pointnumber ( / ic inr md nr ph point px sc ) ;; Local variables

    ;; ...

)

5.检查块是否存在

将块名称直接提供给 INSERT 命令假定该块的定义已存在于活动图形中,或者具有该文件名的图形存在于工作目录或 AutoCAD 支持文件搜索中路径 - 如果两个条件都不满足,INSERT 命令将在程序执行过程中出错。

因此,您可以预先测试这些条件,如果未找到该块,则通知用户,否则继续执行剩余的操作:

(if
    (or
        (tblsearch "block" "pointnumber") ;; Checks for existing definition
        (findfile "pointnumber.dwg")      ;; Checks for drawing file
    )
    ;; ...
)

您还可以使用 cond 函数代替一系列 if/else 表达式。

6.出错时重置环境

由于您在程序执行期间更改系统变量值,因此您应该确保在程序执行期间出现错误时将用户的 AutoCAD 环境重置为其原始状态 - 请注意用户按下 Esc退出程序也会报错。

您可以通过定义本地错误处理程序来实现这一点,正如我在关于 Error Handling 的教程中所述。如果在程序执行期间遇到错误,则会评估本地错误函数,因此您可以在此函数的定义中包含表达式以将 AutoCAD 环境重置为其原始状态 - 在您的情况下,这将涉及重置ATTDIA 系统变量。

将它们放在一起

;; Define function,declare local variables
(defun c:pointnumber ( / *error* bn ic nr ns pt px sc vl vr )

    ;; Define local error function to reset system variables on error
    (defun *error* ( msg )
        (mapcar 'setvar vr vl) ;; Reset list of system variables
        (if (not (wcmatch (strcase msg t) "*break,*cancel*,*exit*"))
            (princ (strcat "\nError: " msg))
        ) ;; end if
        (princ)
    ) ;; end defun

    ;; Define block name
    (setq bn "pointnumber")

    (if (or (tblsearch "block" bn)        ;; Definition in drawing
            (findfile (strcat bn ".dwg")) ;; Drawing file in support path
        ) ;; end or
        (progn
            (initget 6) ;; Prevents 0 & negatives
            (setq sc (cond ((getreal "\nSpecify scale <1.0>: ")) (1.0))
                  px (getstring "\nSpecify prefix <none>: ")
            ) ;; end setq
            (initget 4) ;; Prevents negatives
            (setq nr (cond ((getint "\nSpecify starting number <1>: ")) (1)))
            (initget 6) ;; Prevents 0 & negatives
            (setq ic (cond ((getint "\nSpecify increment <1>: ")) (1)))

            (setq vr '(attreq attdia cmdecho) ;; List of system variables
                  vl  (mapcar 'getvar vr)     ;; Store current values
            ) ;; end setq
            (mapcar 'setvar vr '(1 0 0)) ;; Set system variables appropriately
            (while (setq pt (getpoint "\nSpecify point <exit>: "))
                (setq ns (itoa nr)
                      nr (+ nr ic)
                )
                (repeat (- 3 (strlen ns)) (setq ns (strcat "0" ns))) ;; Pad to 3 digits
                (command "_.-insert" bn "_S" sc "_R" "0" "_non" pt (strcat px ns))
            ) ;; end while
            (mapcar 'setvar vr vl) ;; Reset list of system variables to their original values
        ) ;; end progn
        ;; Else the block was not defined/found
        (princ (strcat "\nThe block \"" bn "\" is not defined in the active drawing and cannot be found."))
    ) ;; end if
    (princ) ;; Suppress the value returned by the last evaluated expression
) ;; end defun

还可以实施其他可能的改进,例如:

  • 通过使用 ActiveX insertblock 方法或 entmake/{{1} 消除对标准 AutoCAD 命令(在本例中为 INSERT 命令)的调用的依赖} 将DXF数据直接写入绘图数据库的函数。

  • 通过引用属性标记名称来填充属性,以消除对在块引用中遇到属性引用的顺序的依赖性(可以通过使用 { 在每个绘图的基础上进行修改) {3}} 命令)。

  • 使用“动态默认值”(如我的 BATTMAN 中所述),并可能在绘图会话之间存储默认值。