LISP 开发指南

本指南提供了有关使用 AutoLISP 和 ActiveX 对象两种方式在 ZWCAD 上进行二次开发的信息，包括以下主题：

  • AutoLISP 简介

    • 本章主要介绍 AutoLISP 的起源、使用现状、使用前景、语言特点及能够实现的功能等内容。

  • LISP 程序设计基础

    • 本章主要介绍 LISP 程序设计的基础知识、调试和运行 LISP 程序的方法和技巧等内容。

  • 库函数分类介绍

    • 本节将 ZWCAD 提供的 230 多个库函数按功能进行分类介绍，个别使用频率较高的函数辅以实例代码进行演示说明。

  • 使用 ActiveX 对象

    • 本节主要介绍使用 ActiveX 对象进行程序开发的优点、ZWCAD 对象模型及 ZWCAD 与其它支持 ActiveX 的应用程序的交互等内容。

  • 附录

    • 本章用列表的形式将 ZWCAD 提供的 300 多个库函数（含 ActiveX 函数）按功能进行分类，并对各函数的调用格式和主要功能进行简要的介绍。


新版本说明

版本回顾

新版本的 LISP 提高并完善了原有的接口质量，特别是在曲线测量函数部分的实现情况有了很大的提高。与此同时，并在以前的基础上新增加对数函数和用户授权函数。但是需要说明的，这次版本中还有一些不太常用的 LISP 接口暂时还未实现。具体内容请参看已更新版本的下一章内容。


AutoLISP 简介

在开始学习 AutoLISP 之前，您也需要了解它的过去、现在和将来，本章从以下三个方面对 AutoLISP 进行简要的介绍。

  • AutoLISP 起源和现状

  • AutoLISP 语言的特点

  • AutoLISP 程序设计的作用


AutoLISP 起源和现状

AutoLISP 语言是 LISP（List Processing Language） 语言的一个子集，由于在上世纪末 AutoCAD 将 LISP 溶入到计算机辅助设计中，所以称之为 AutoLISP。除了 FORTRAN 和 COBOL，大多数在上世纪六十年代早期开发出来的编程语言都过时了，但是 LISP 却顽强地生存下来了，并且已经成为人工智能的首选程序设计语言。

现在，AutoLISP 是当今广泛流行于工程设计行业和制造业、服装设计、教育部门等各界的一种计算机辅助设计程序二次开发语言，并且已有数以万计的第三方软件开发人员使用 AutoLISP 语言编制各种专业应用程序。

虽然现在 ZWCAD 提供了更强大的基于 C/C++ 的 ZDS 和 ZRX 二次开发接口，但要熟练运用这些接口开发专业应用程序，需要较深的计算机专业知识和丰富的开发经验，导致掌握这些编程方法对普通用户来说比较困难，所以 AutoLISP 仍然是设计师首选的二次开发语言。


AutoLISP 语言的特点

AutoLISP 语言与 ZWCAD 环境完全溶为一体，用户可以随时使用 AutoLISP 语句，它是被解释执行的，任何一个语句键入后就能马上执行，所以它对于交互式的程序开发是非常便利的。

AutoLISP 语言易学易用，即使没有学过任何程序设计语言的 ZWCAD 用户，都可以在最短的时间内掌握它并为实际工作所用。AutoLISP 语言具有如下特点：

  • 语法简单

    AutoLISP 是一种弱数据类型的程序设计语言，不用特殊的变量定义，ZWCAD 提供的 AutoLISP 解释器根据变量的值自动确定变量的数据类型；

    语法规则单一。

  • 库函数功能强大

    AutoLISP 提供了众多功能函数，其中不少可以直接控制 ZWCAD，再加上 AutoLISP 可以直接调用执行所有 ZWCAD 内部命令，并且可以操纵所有的 ZWCAD 系统变量。可以说，学好 AutoLISP 的关键是这些库函数的学习。

  • 开发环境不挑剔

    只要是一般的文本编辑程序都可以用于开发 AutoLISP 程序，如notepad、wordpad、UltraEdit等。

  • 可移植性强

    在旧版本的 ZWCAD 平台上开发的 AutoLISP 程序可以零修改直接在新版本 ZWCAD 上加载运行，甚至在其它 CAD 平台上开发的 AutoLISP 程序也只需要微小的改动即可在 ZWCAD 上正确加载运行。

当然 AutoLISP 也有缺点，由于它是 LISP 语言的一个子集，它继承了 LISP 语言的编程规则而导致的繁多括号，给开发和阅读 AutoLISP 程序带来诸多不便，以致于经常有人把“LISP”理解为“Lost of Insipid Stupid Parenthesis”，但若在开发过程中注意编排代码格式（如缩进、空行）是可以将这个影响降到最低的。


AutoLISP 程序设计的作用

ZWCAD 作为一个通用的计算机辅助设计程序，提供了众多用于产生图形的命令，但是仍有某些命令未被提供。例如，ZWCAD 未提供绘制圆柱齿轮的命令。通过 AutoLISP 您可编制参数化设计圆柱齿轮的程序，让您的设计工作更高效。实际上编写 AutoLISP 程序可以实现以下功能：

  • 强化 ZWCAD 内部命令

  • 创建更有用的 ZWCAD 新命令

  • 简化繁琐的环境设置或绘图步骤

  • 参数化图形设计

  • ASCII 文件读写（如读写配置信息）

但是 AutoLISP 也不是万能的，当您希望所开发的应用程序具备以下功能时，请不要选择 AutoLISP：

  • 二进制文件的读写

  • 调用操作系统的功能

  • 直接存取硬件设备

  • 高级的用户接口（如自定义实体）


LISP 程序设计基础

本章介绍 AutoLISP 程序设计的基础知识。正如第一章中“AutoLISP 语言的特点”主题所言，学习 AutoLISP 程序设计主要是学习基本语法和库函数的用法，由此可见本章的重要性。本章主要包括以下内容：

  • AutoLISP 表达式和语法结构

    • 本节主要介绍 AutoLISP 表达式语法规则、AutoLISP 表达式块、在 ZWCAD 命令行中如何使用 AutoLISP 表达式。

  • AutoLISP 变量

    • 本节主要介绍变量命名规则、如何为变量赋值、如何查看变量的值、释放变量的内存空间、AutoLISP 预定义变量。

  • AutoLISP 数据类型

    • 本节主要介绍如何查看变量或常量的数据类型及使用各种数据类型（整数、实数、字符串、图元名、表、选择集、VLA 对象、变体和文件描述符）的注意事项。

  • AutoLISP 基本输出函数

    • 本节主要介绍如何在 ZWCAD 命令行显示信息、如何取消函数调用后的命令行回显、如何输出控制字符。

  • 自定义函数和命令

    • 本节主要介绍如何创建自定义函数、如何创建自定义命令、如何重定义 ZWCAD 内部命令、局部变量和全局变量的区别、如何使用无名函数。

  • 程序流程控制

    • 本节主要介绍三种程序流程控制结构：顺序、选择和循环。

  • LISP 程序文件格式

    • 本节主要介绍 LISP 源代码格式和注释。

  • 在 ZWCAD 中加载和运行 LISP 程序

    • 本节主要介绍如何在 ZWCAD 中手动加载 LISP 文件、如何自动加载 LISP 文件、如何运行 LISP 程序。

  • 在 ZWCAD 中调试 LISP 程序

    • 本节主要介绍在 ZWCAD 中调试 LISP 程序的技巧。

  • 发布 LISP 程序

    • 本节主要介绍如何加密 LISP 程序。


表达式和语法结构

表达式是 AutoLISP 程序的基本单位，本章主要介绍 AutoLISP 表达式的语法规则、AutoLISP 表达式块，以及如何在中望CAD 命令行中使用 AutoLISP 表达式。

AutoLISP 表达式语法规则

无论 AutoLISP 表达式的复杂程序如何，每一个 AutoLISP 表达式必须符合以下格式：

(函数名 参数1 参数2 …)

1.表达式必须包含括号，且左右括号一定要成对出现

2.函数名、参数之间必须以单个或多个空格分隔，函数名或参数与括号之间可以没有空格

3.函数名可以为 AutoLISP 库函数，也可为用户自定义函数或命令

4.参数1、参数2等操作数可以是变量，也可以是 AutoLISP 表达式，如下所示：

(函数1 (函数2 参数1) (函数3 参数2))

若在中望CAD 命令行输入该代码，ZWCAD AutoLISP 解析器将按如下方式处理该代码：
函数 1 有两个参数，函数 2 和函数 3 各有一个参数；函数 2 和函数 3 的返回值作为参数传递给函数 1。

在中望CAD 命令行（命令：）中输入 AutoLISP 表达式可以直接求值，如下所示：

命令：(+ 3.1 5.4)　　　　　　　　　　　　;;;返回两数之和 8.50000
命令：(* 3.14 (+ 2.1 3.5))　　　　　　　　;;;先求和再求积，返回结果 17.5840

AutoLISP 表达式块

在程序设计中单个表达式的能力是非常有限的，比如在 if 分支语句（在本章的“程序流程控制”主题中介绍）中，“是表达式”或“否表达式”就可能不是单一的 AutoLISP 表达式，此时可用 progn 函数将多个表达式组合成表达式块，类似于 C/C++ 中用左花括号（{）和右花括号（}）构建语句块。该函数语法如下：

(progn 表达式1 [表达式2] [表达式3]…)

表达式块返回最后一个表达式的值。

在 ZWCAD 命令行中使用 AutoLISP 表达式

从上例可以看出嵌套表达式中，正确配对左右括号可能会比较困难，在中望CAD 命令行中输入 AutoLISP 表达式后，可以自动检测括号是否配对，例如，在中望CAD 命令行中输入如下代码：

命令：(+ 3.14 (* 2.1 3.5)　　　　　　　　　　;;;缺少一个右括号

命令行将出现如下提示：

Missing: 1) >

此提示表明有多少层右括号没有闭合，若出现此提示，用户必须输入所需数目的右括号才能对表达式求值，如下所示：

Missing: 1) >)

出现此类错误的常见原因是忽略了文本字符串中的右双引号（"），在这种情况下，右括号将被解释为字符串的一部分，而不会被用来与左括号配对。建议出现该类错误时，不要仅输入所需数目的右括号以完成表达式的求值，而应该按 Esc 键取消表达式并重新输入正确的表达式。


AutoLISP 变量

在计算机中，变量常用于引用某内存地址中存储的数据，变量名必须遵循一定的命名规则才能被计算机识别，本节主要介绍变量的命名规则、为变量赋值、查询变量的值、释放变量的内存空间和 AutoLISP 预定义变量。

变量命名规则

AutoLISP 通过变量来引用数据，变量名不区分大小写，可以由字母、数字和除(（左括号）、)（右括号）、.（句点）、'（单引号）、"（双引号）和 ;（分号）外的特殊字符的任何序列组成，且变量名不能只由数字字符组成，如 str1、radius 等都是合法的 AutoLISP 变量。

例如，以下示例代码使用 setq 函数将字符串“Hello, ZWCAD!”赋给变量 str1：

命令：(setq str1 "Hello, ZWCAD!")
"Hello, ZWCAD!"

为了方便您自己和其他需要阅读您的代码的用户，在设置 AutoLISP 变量名时请注意尽量做到“见名知意”；为保证程序执行的效率，建议变量名不要超过8个字符。

在 ZWCAD 中允许用户自定义的变量名与 AutoLISP 关键字（预定义变量或内置函数）同名，但这不是一种良好的编程习惯，因为其它应用程序也可能要依赖这些 AutoLISP 关键字，因此建议尽量避免自定义的变量名与 AutoLISP 关键字同名。

为变量赋值

在为变量赋新值前，该变量将一直保留原来的值，若未对变量赋值，则其值为 Nil，可以用 setq 函数为变量赋值，格式如下：

(setq 变量1 值1 [变量2 值2 …])

setq 函数将指定的值赋给指定的变量，并将该值作为函数结果返回。若在 ZWCAD 命令行中使用 setq 函数，函数结果将显示在命令行中：

命令：(setq radius 3.2 color "red")
"red"

显示变量的值

使用 AutoLISP 进行程序开发时，要想显示变量的当前值，只需在 ZWCAD 命令行中输入!（感叹号）+ 变量名，如下所示：

命令：!radius
3.20000

释放变量的内存空间

没有指定值的 AutoLISP 变量称为 Nil 变量，它不占用内存空间。

每个变量都占用一小部分内存，因此良好的编程习惯是重新利用变量名，或者在变量的值不再需要时将其设为 Nil。将变量设置为 Nil 可释放用于存储该变量的内存空间。 例如，您不再需要使用 radius 变量，可以通过下列表达式释放变量所占用的内存空间：

命令：(setq radius Nil)
nil

预定义的变量

下列预定义的变量可以在 AutoLISP 应用程序中通用：

变量名 | 含义
PAUSE | 定义为由一个双反斜杠（\\）字符构成的字符串，此变量与 command 函数配合使用，用于暂停正在执行的命令，等候用户输入。
PI | 实际上为一个符号常量，其值近似等于 3.14159（圆周率）。
T | 符号常量，用作表示非 Nil 值。


LISP 数据类型

LISP 提供了 type 函数用于查看变量或常量的数据类型，如下所示：

命令：(type "ZWCAD") 　　　　　;;;返回字符串常量 "ZWCAD" 的数据类型 STR

下面对有效的 LISP 数据类型分别进行介绍：

整数（INT）

整数是没有小数点的数。例如，0、1、-512 和 9597 这些全都是整数。AutoLISP 的整数是 32 位带符号的数，范围是 -2,147,483,648 至 +2,147,483,647。当一个函数导致了一个越界（数据溢出）的时候，它会自动地转换到相反的符号，如 -2,147,483,648 会变成 2,147,483,647。您可把这种现象当作一个循环回转，这样当您把 -1 加上 -2,147,483,648，您的结果会是：+2,147,483,647。如下图所示：

实数（REAL）

实数是有小数点的数（也称为“浮点数”）。虽然 AutoLISP 最多只显示六位小数，但这里实数是以双精度浮点数的格式存储的，这就是说实数有 14 位精度，例如 2.0000000000000。您会发现非常大的数和非常小的实数会以科学计数法表示，如 3.44861E-07（即 0.000000344861）。-1 至 1 之间的实数必须要有一个前置零，例如 0.9、0.0 或 -0.1，您不能表示为 .9、.0 或 -.1，否则 ZWCAD 将会报错。

字符串（STR）

字符串是用英文双引号括起来的字符的组合，如 "VPORT" 或 "Please type your name:"，ZWCAD 支持的最长字符长度是 132 位。"" 被称为空串。

在使用控制字符的时候，要使用反斜杠（\）作为前缀。例如，\n 是换行符。在字符串中显示一个引用标志时，可以使用 \"，例如，"The screw is 1\" long."。也可以用 "\ddd" 来表示字符串，其中 ddd 为对应字符的 8 进制 ASCII 码，例如，可以用 "\101\141" 表示字符串 "Aa"。

图元名（ENAME）

图元名是一个无符号的 16 进制标签，如 <Entity name: 6130ad0>，为了与 ActiveX 对象区别，也称作 AutoLISP 图元。实际上它是一个指向 ZWCAD 所维护的文件中某条记录的指针，在 AutoLISP 中，您不必对真正的图元进行操作，相反您只要先找出它的图元名，然后对它的图元名进行操作就可以了。

指定给图纸中的图元的图元名仅在当前编辑任务中有效，下一次打开该图纸时，ZWCAD 将为图元重新分配图元名；但图元的句柄在每次打开图纸时都是固定不变的，因此可以利用图元的句柄在编辑任务之间引用图元。

表（LIST）

表是在括号中以空格分隔的一组相关值，提供了一种存储大量相关值的有效方法，因此也称为关联表。每一个合法的 AutoLISP 表达式都是一个表，例如 (1 2 3)、(* pi 2)、(cen rad) 和 ("The answer is : " ANS)。两个数组成的表通常用作二维点的 X，Y 坐标，如 (1.0 2.0)；三个数组成的表通常用作三维点的 X，Y，Z 坐标，如 (10.0 11.0 12.0)。

一种被称作点对（Dotted Pair） 的特殊表是用来对图元数据进行描述的，是由以英文句点（.）分隔的两个相关值构成的表，句点前后各有一个或多个空格，如 (62 . 1)。点对数据和 DXF 编码十分相似（虽然不可以说完全一模一样）。例如，(62 . 1) 表示图元的颜色（组码 62）是红色（red，1 表示颜色索引值）。

选择集（PICKSET）

选择集是一个或多个图元的集合，如 <Selection set: 125917216>。用户可以通过 AutoLISP 函数交互地将图元添加到选择集中，或从选择集中删除。选择集会消耗 ZWCAD 的临时文件存储区，所以 AutoLISP 不允许同时打开多于 128 个选择集。

VLA 对象（VLA-OBJECT）

图纸中的图元、符号表和词典都可以被表示为 Visual LISP ActiveX (VLA) 对象，如 #<VLA-OBJECT IZCADLine 782e468>，这是 Visual LISP 的一种数据类型。在使用 ActiveX 函数时，必须引用 VLA 对象，而不是 AutoLISP 图元。

变体（VARIANT）

变体是一种灵活的数据类型，它可以存储任何类型的数据。一般很少直接使用变体类型的变量，因为当使用 Variant 时性能比较差，可能有潜在的运行时错误，而且代码看上去也变得不清晰，一般在与 ActiveX 对象交互时应用，如调用某 ActiveX 对象的某些方法时，必须传递变体类型的参数，此时才会使用变体数据类型。变体类型的变量一旦被赋值后，就具有了实际的数据类型。

文件描述符（FILE）

文件描述符是指向被 AutoLISP 打开的文件的一个十六进制的标签，如 <File: #785B7408>，也像图元数据一样，您不必直接通过文件名直接引用，只需对文件描述符进行操作。AutoLISP 可以打开一个文件进行读 、写、附加操作，它将一直保持打开状态，直至在 AutoLISP 程序中显式地关闭该文件。它只能对 ASCII 文件进行操作，不能对二进制文件操作，并且只能提供有序的访问，不能进行随机的访问。


基本输出

AutoLISP 提供了 prin1、princ、print 和 prompt 四个基本输出函数，可在图形窗口或文本窗口显示提示信息，对于调试程序、用户交互是非常必要的。 另外，prin1、princ 和 print 函数还可以将信息写入已打开的 ASCII 文件中，本章不作介绍，请参见本指南第三章“AutoLISP 库函数分类介绍”中“文件处理函数”主题。

显示信息

prompt 函数在中望CAD 命令窗口上显示信息（一个字符串），并返回 nil。

princ、prin1 和 print 函数都在中望CAD 命令行上显示一个表达式的结果（不一定是字符串）。这些函数之间的差异如下所示：

  • princ 解析字符串中的控制字符并显示字符串，不包含在双引号中

  • prin1 不解析字符串中的控制字符，只是原样显示字符串，包含在双引号中

  • print 不解析字符串中的控制字符，只是原样显示字符串，包含在双引号中，且在末尾含一个空格

如下的示例代码用相同的字符串展示了这四个基本输出函数之间的差异：

命令：(setq string "The \"Max\" diameter is \24612.5.")　　　　　　 　　;;;为变量赋值
"The \"Max\" diameter is Φ2.5."

命令：(prompt string)
The "Max" diameter is Φ2.5.nil　　　　　　　　　　　　　　　　 　　　　;;;打印字符串（不带双引号），并返回 nil

命令：(princ string)
The "Max" diameter is Φ2.5."The \"Max\" diameter is Φ2.5."　　　　　　;;;打印字符串（不带双引号），并返回该字符串

命令：(prin1 string)
"The \"Max\" diameter is Φ2.5.""The \"Max\" diameter is Φ2.5."　　　　;;;打印字符串（带双引号），并返回该字符串

命令：(print string)
"The \"Max\" diameter is Φ2.5."<空格>"The \"Max\" diameter is Φ2.5."　;;;打印字符串（带双引号），并返回该字符串（在打印信息和返回值之间添加一个空格）

静默退出

若调用 princ 函数而不为它传递一个表达式或字符串，该函数不显示任何内容也不返回任何值。

因此，在自定义函数时，经常在函数末尾不传参数调用 princ 函数，以避免命令行回显函数的最后一个表达式的值，这种情况称为静默退出。

字符串中的控制字符

双引号引起来的字符串中的反斜杠（\）字符使得字符串中可以包含控制字符（或八进制 ASCII 码），下表列出了当前 AutoLISP 能识别的控制字符：

表达式 | 返回值
\\ | \ 字符
\" | " 字符
\n | 换行符
\r | 回车符
\t | 制表符
\nnn | 八进制 ASCII 为 nnn 的字符

若需要在双引号引起来的字符串中使用反斜杠（\）或英文双引号（"），则必须在前面添加反斜杠（\）。例如，若在中望CAD 命令行中输入以下 LISP 代码：

命令：(princ "The \"license\" is out of date(2023\\10\\20)")
The "license" is out of date(2023\10\20)

若需要在字符串中的特定位置强制换行，可使用换行符（\n），以下示例代码展示了该用法：

命令：(prompt "Sorry!\nZWCAD hasn't been installed.")
Sorry!
ZWCAD hasn't been installed.

回车符（\r）返回至当前行的行首，即会覆盖上一次在该行显示的字符串（类似于 MS Word 中改写效果），但这对显示增量信息很有用，比如，循环过程中显示所处理对象的数量的计数器等。

制表符（\t）可以被用于以表格形式输出字符串时调整制表文本字符串的对齐，如下示例代码展示了制表符的用法：

命令：(princ "Aaa\tBbb\tCcc\nDdd\tEee\tFff\nGgg\tHhh\tKkk") (princ)
Aaa Bbb Ccc
Ddd Eee Fff
Ggg Hhh Kkk


自定义函数和命令

函数是结构化程序设计的功能模块，合理的使用函数，可以使程序的逻辑更清楚，同时也可提高代码重用性。AutoLISP 提供了 200 多个内置的库函数，在使用这些函数时，您不必关心它是如何实现的，只需要按照 LISP 函数参考中对各函数的介绍传递合法的参数，即可得到需要的结果；在您开发 LISP 程序过程中很有可能发觉这些函数仍不够用，这时您可以自定义函数。本节主要介绍自定义函数、自定义命令、重定义中望CAD 内部命令、函数中的局部变量和全局变量、无名函数的使用等内容。

自定义函数

在 LISP 程序中，用户可以使用 defun 函数定义自己的函数，格式如下：

(defun [c:] 函数名称 ([参数1] [参数2]… / [局部变量1] [局部变量2]…) 表达式1 [表达式2]…)

defun 后面的字符串表示函数名，后面的括号里的字符串称为参数表，分为两个部分：“/”左边的表示函数的参数，右边的表示函数定义的局部变量（关于局部变量与全局变量的内容下面再讲），该参数表可为空（无论是否提供参数或局部变量，括号都不可省略）。在参数表之后是组成函数的表达式，函数定义中至少要包括一个表达式。下面示例代码定义了一个简单的函数，函数名为 welcome，该函数不接受任何参数，只在中望CAD 命令行中显示“Hello ZWCAD”：

命令：(defun welcome ()(princ "\nHello, ZWCAD!")(princ))
WELCOME

下面的示例代码演示了如何在中望CAD 命令行中调用无参数的自定义函数：

命令：(welcome)(prompt "\nThis is an AutoLISP demo program.")(princ)
Hello, ZWCAD!
This is an AutoLISP demo program.

不接受任何参数的函数看起来用处不太大，但您可以用这种函数来查询某个系统变量或条件的状态，并返回表明该状态的值。下面示例代码定义了一个接受两个字符串参数的函数 paramTest，该函数将它们与另一个字符串组合，并返回结果字符串：

(defun paramTest (arg1 arg2 / lcstr)
  (setq lcstr "中望龙腾")
  (strcat lcstr ", " arg1 " " arg2)
)

因为 AutoLISP 总是返回函数最后一个表达式的计算结果，因此 paramTest 函数返回一个字符串，这是一个不使用 princ 函数静默退出的特例。下面的示例代码演示了如何调用带参数的自定义函数：

命令：(setq company (paramTest "Chinaweal" "Longteng"))
"中望龙腾, Chinaweal Longteng"

自定义命令

自定义命令实际上是一种特殊的自定义函数，它可以象中望CAD 的内部命令一样直接在中望CAD 命令行中使用，因此，使用自定义命令可以扩展中望CAD 的内部命令。

要将函数用作中望CAD 命令，必须确保它们遵循如下规则：

  • 函数名必须使用格式 C:XXX （不区分大小写）。名称的 C: 部分必须存在；XXX 部分是用户选择的命令名

  • 函数不可带有参数，但参数表中可以有局部变量

可以在任何中望CAD 内部命令的执行过程中透明地调用用户自定义命令，但当调用 command 函数执行某中望CAD 内部命令时不可透明调用自定义的命令。当透明地调用用户自定义命令时，必须在 XXX 部分前加上单引号（'），下列示例代码说明了该用法：

(defun c:prtinfo ()　　　　　　　 ;;; 自定义命令 prtinfo，该函数作用是打印一行字符
  (princ "\n开始绘制…")
  (princ)      ;;; 静默退出
)

命令: line　　　　　　　　;;; 在执行中望CAD 内部命令 line 绘制直线过程中透明调用自定义命令 prtinfo
线的起始点: 'prtinfo
开始绘制…
线的起始点:
角度(A)/长度(L)/指定下一点:
角度(A)/长度(L)/跟踪(F)/闭合(C)/撤消(U)/指定下一点:

命令：(command "line" 'prtinfo '(0.0 0.0 0.0) pause "") 　　;;; 调用 command 函数执行中望CAD 内部命令 line 时透明调用用户自定义命令 prtinfo
错误: 被拒绝函数

执行用户自定义命令有两种方式，一种是带括号使用完整的函数名，如 (C:prtinfo)；另一种是直接使用函数名中的 XXX 部分，下面的示例代码展示了这种用法：

命令：(c:prtinfo)　　　　;;;带括号使用完整的函数名
begin drawing...
命令：prtinfo　　　　　;;;仅函数名中 XXX 部分
begin drawing...

注意 在 LISP 程序文件中只能用第一种方法（带括号的完整函数名）调用用户自定义命令。

重定义 ZWCAD 命令

在某些情况下，用户可能希望重新定义中望CAD 现有的内部命令，例如，希望重定义 New、Open 或 Quit 命令实现在终止编辑任务之前将运行信息写入日志文件的效果。通过 defun 函数自定义命令并结合 undefine 命令使用，可以达到该目的。通过 undefine 命令，可用自定义的命令来重定义中望CAD 的内部命令，要想恢复中望CAD 的内部命令，可使用 redefine 命令。

注意 对中望CAD 内部命令的重定义仅在当前编辑任务有效，若重启中望CAD 程序将恢复中望CAD 的内部命令。

下面介绍命令的几种形式，中望CAD 的内部命令有五种形式，区别如下（以 Line 命令为例）：

  • Line 命令的一般形式

  • _Line 为避免所开发的 LISP 程序在其它语言环境中使用时命令解析出错的形式

  • .Line 对中望CAD 内部命令的原始引用（屏蔽 undefine 命令对中望CAD 内部命令的重定义）

  • _.Line 在其它语言环境中对中望CAD 内部命令的原始引用（屏蔽 undefine 命令对中望CAD 内部命令的重定义）

  • -line 以命令行交互方式执行中望CAD 内部命令（并非所有的中望CAD 内部命令都能通过命令行交互方式执行）

若每次使用 close 命令关闭当前编辑文档时，都希望中望CAD 将修改记录到日志文件中，则可以定义 AutoLISP 函数 C:Close 来替换中望CAD 的内部命令 close，下面的示例代码展示了这一用法：

(command "undefine" ".close")  ;;;取消 ZWCAD 内部命令 close

(defun c:close (/	  *ZWCADOject*		curfilename
           SYS	  filename   fileid	curdate	   year
           month	  day	     hour	minute	   second
           datestr
          )
  (vl-load-com)
  ;;;获取当前文件的文件名
  (setq *ZWCADOject* (vlax-get-acad-object))
  (setq curdoc (vla-get-ActiveDocument *ZWCADOject*))
  (setq curfilename (vla-get-name curdoc))
  (if (null (findfile "c:\\zwcad.log"))		;;;检测日志文件是否存在，若不存在则创建
    (progn
      (setq SYS (vlax-create-object "Scripting.FileSystemObject"))
      (vlax-invoke-method SYS 'CreateTextFile "c:\\zwcad.log")
    )
  )
  (setq filename (findfile "c:\\zwcad.log"))
  (setq fileid (open filename "a"))
  (setq curdate (rtos (getvar "cdate") 2 6))	;;;获取当前系统时间
  (setq year (substr curdate 1 4))
  (setq month (substr curdate 5 2))
  (setq day (substr curdate 7 2))
  (setq hour (substr curdate 10 2))
  (setq minute (substr curdate 12 2))
  (setq second (substr curdate 14 2))
  (setq	datestr (strcat year "/" month "/" day " " hour ":" minute ":" second " "))
  (princ (strcat datestr curfilename " 被修改。\n") fileid)	;;;将日志写入日志文件
  (close fileid)
  (command ".close")  ;;;执行 ZWCAD 内部命令 close
  (princ)	;;;静默退出
)

现在，若在中望CAD 命令行中输入 close，Z中望CAD 将调用自定义命令 C:Close，将当前系统时间和文件名写入 C:\zwcad.log 文件中。

建议：通过 command 函数调用命令时，所有命令都使用句点前缀的格式，以确保应用程序中调用的是中望CAD 的内部命令，而不是重定义的自定义命令。

局部变量与全局变量

良好的编程习惯是尽量少用全局变量，尽可能用局部变量。在 AutoLISP 中仅在函数内部使用的变量称为局部变量；当前加载的所有 LISP 程序中所有函数都可以访问的变量称为全局变量。

使用局部变量可以确保函数中的变量不受其它函数的影响，从而保证了数据安全，并且在函数调用结束后，该变量所占用的内存被自动释放，从而提高了程序运行的效率。

用全局变量代替局部变量的危害是：第一，您可能在声明该变量的函数之外不经意地修改了它的值，这可能导致不可预知的结果，并且要想检测出这类问题将非常困难；第二，在当前编辑任务结束之前，全局变量所占用的内存空间是不会释放的，若声明的全局变量过多，将会严重消耗系统内存。

虽然使用全局变量存在这些不利因素，但在某些情形下，合理使用全局变量会使程序逻辑更清楚，此时标记全局变量是一个好习惯，最通用的标记方法是在变量名的首尾各添加一个星号（*），例如，*ModelSpace*。

以下示例代码展示在用户自定义函数中使用局部变量的用法（要确保在参数表中斜杠和局部变量之间至少有一个空格）：

(defun test (/ product company)    ;;;声明了两个局部变量：product company
  (setq	product	"ZWCAD"
    company	"ZWSoft"
  )
  (princ (strcat "\nproduct = " product))
  (princ (strcat "\ncompany = " company))
  (princ)
)

现在，调用上面代码定义的 Test 函数，查看局部变量（product 和 company）的值：

命令：(test)
product = ZWCAD
company = ZWSoft

Test 函数调用结束后，再次查看局部变量（product 和 company）的值：

命令：!product
nil
命令：!company
nil

由此可见，在函数调用完毕后，在该函数中定义的局部变量所占用的内存空间被自动释放。

创建无名函数

在经常使用某一个或一组表达式，而又觉得把它定义成一个新函数开销太大时，可使用 lambda 函数将这些表达式定义成无名函数，类似于 C++ 的内联函数，其语法格式如下所示：

(lambda 参数表 表达式1 [表达式2]…)

lambda 将定义的函数放在要使用它的位置，还可以使程序的意图表达得更清楚。无名函数无法在中望CAD 命令行调用，它常与 apply 和（或）mapcar 函数连用，以便对表中的元素执行某个操作，返回它最后一个表达式的值，如下面示例代码返回二维点 pt0 关于原点的对称点 pt1：

(setq pt1 (mapcar
        '(lambda (pt)     ;;;参数表
           (* pt -1)    ;;;表达式
         )
        pt0
      )
)


程序流程控制

和其它程序设计语言一样，AutoLISP 作为一种结构化的程序设计语言，也提供了三种基本的程序结构，即顺序、选择和循环：

顺序结构

对于任何程序设计语言，所设计的程序都是按照表达式的先后次序顺序执行的，在中望CAD 命令行中执行如下示例代码：

命令：(setq a "ZWCAD")(setq b a)　　　　;;;先对变量 a 赋值，然后再把变量 a 的值赋给变量 b，返回"ZWCAD"

若将两个表达式的前后顺序颠倒，因变量 a 在使用前未被赋值，因此其值为 nil，所以变量 b 的值亦为 nil。由此可见，在设计 LISP 程序时，要理清程序逻辑，组织好各 AutoLISP 表达式在程序中的前后顺序。

选择结构

在程序流程中碰到岔路口是再所难免的，这个时候就需要利用选择结构（分支判断结构）根据不同的选择条件得出不同的结论。AutoLISP 提供了if 和 cond 函数分别用于实现单一条件判断和多分支条件判断，类似于 C 语言中的 if...else 和 switch....case 结构：

  • 单一条件判断（if）

    (if 条件表达式 是表达式 [否表达式])

    函数先执行“条件表达式”，若其返回值为 T，则执行“是表达式”，否则执行“否表达式”，若“否表达式”未提供，则跳出 if 函数，继续执行之后的 AutoLISP 表达式。下面示例代码定义了 test1 函数，输出 num1 和 num2 中较大的一个：

    (defun test1 (num1 num2)
  (if (< num1 num2)
    (princ num2)
    (princ num1)
  )
  (princ) ;;;静默退出
)

    “是表达式”和“否表达式”也可以是其它的 if 表达式，从而形成了嵌套的选择结构，可以实现多分支选择。下面的示例代码定义了 test2 函数，根据 score 参数的值输出"优秀"（score > 90）、“合格”（60 ≤ score < 90）和“不合格”（score < 60）：

    (defun test2 (score)
  (if (< score 60)
    (princ "不合格")
    (if	(>= score 90)
      (princ "优秀")
      (princ "合格")
    )
  )
  (princ) ;;;静默退出
)

  • 多分支条件判断（cond）

    (cond (条件表达式1 [是表达式1]) (条件表达式2 [是表达式2])…)

    函数会顺序检查各个条件表达式，直到其中一个条件成立（返回 T），即执行该条件表达式对应的“是表达式”，而不会再对其余的条件表达式进行判断。

    注意 每个条件表达式只对应一个“是表达式”，而没有“否表达式”，“是表达式”也可省略，此时将返回条件表达式的逻辑值，下列示例代码定义了 test3 函数实现上面 test2 函数相同的功能：

    (defun test3 (score)
  (cond
    ((>= score 90) (princ "优秀"))
    ((and (>= score 60) (< score 90)) (princ "合格"))
    ((< score 60) (princ "不合格"))
  )
  (princ) ;;;静默退出
)

比较上面示例代码的 test2 函数和 test3 函数，不难发现， 用 cond 函数实现多分支判断会使代码的可读性更好。

循环结构

计算机最擅长的工作就是重复执行某些指令，若某程序设计语言未提供循环结构，那么写出来的程序可能又臭又长。AutoLISP 提供了 repeat 函数和 while 函数分别用于实现指定次数的循环和不定次数的循环。

  • 指定次数的循环（repeat）

    • (repeat 重复次数 [表达式…])

    函数会重复执行表达式指定次数（整数），需重复执行的表达式可以是一个或多个。下面示例代码在中望CAD 命令行输出“ZWCAD”10 次：

    命令：(repeat 10 (princ "\nZWCAD"))

  • 不定次数的循环（while）

    • (while 条件表达式 [是表达式1 是表达式2…])

    函数先检测“条件表达式”，若返回值为 T，则依次执行各“是表达式”，执行完最后一个“是表达式”后，程序会返回函数开头，重新检测“条件表达式”，若返回值为 T，继续执行各“是表达式”……。只有当检测到“条件表达式”的返回值为 nil 时，才会跳出 while 函数。下面示例代码定义了 test4 函数，返回 1 至 num 参数（正整数）的所有整数的和：

    (defun test4 (num / i sum)
  (setq i 1)              ;;;初始化循环变量
  (setq sum 0)
  (while (<= i num)       ;;;检测条件表达式
    (setq sum (+ sum i))
    (setq i (1+ i))       ;;;循环变量自增
  )
  sum
)

    注意 若程序每次检测“条件表达式”的返回值恒为 T，程序将会一遍一遍地执行各“是表达式”而无法结束，这被称为“死循环”。导致死循环的主要原因是，执行“是表达式”后，循环变量的值没有改变，如上例，若将 (setq i (1+ i)) 表达式注释掉，执行函数 test4 将进入死循环。


AutoLISP 程序文件

虽然可以在 ZWCAD 的命令行中输入 AutoLISP 代码，但如果将 AutoLISP 代码存储在文件中，在测试和调试一系列指令时，从文件中加载 AutoLISP 代码要比每次修改后都重新输入更方便。AutoLISP 代码通常保存在扩展名为 lsp 的 ASCII 文本文件中。

源代码格式设置

在 AutoLISP 代码中广泛使用的括号使代码非常不易阅读，解决这个问题的经典技术是缩进，嵌套越深的代码行，越向右缩进。

适当的使用空格或空行，也可增强 LISP 代码的可读性。在 AutoLISP 代码中，在变量名、常量和函数名之间的多个空格等效于一个单独的空格，行尾的多个空格也被视为一个单独的空格。如下列代码虽然格式不同，但返回相同的结果：

代码行未缩进：

(setq pt1 (list 0.0 0.0 0.0) pt2 (list 100.0 0.0 0.0) pt3 (list 100.0 100.0 0.0))

代码行缩进：

(setq pt1 (list 0.0 0.0 0.0)
      pt2 (list 100.0 0.0 0.0)
      pt3 (list 100.0 100.0 0.0)
)

不难看出，缩进的代码可读性更高。

注释

在 AutoLISP 程序文件中包含注释是一个好习惯。注释不仅对编程者有用，而且对将来维护程序的人也是有帮助的，因为他们可能需要修改程序以满足用户的新要求。用注释进行下列工作：

  • 给出标题、作者和创建日期

  • 对程序的功能、关键算法进行说明性注释

  • 添加调试注释

注释以一个或多个分号（;）开始并延续到行尾， 这种注释方式被称为单行注释。

;单行注释
(setq radius 3.5) ;;;设置圆的半径

在;|…|;中的任何文本都将被忽略，因此注释可以包含多行代码，这种注释方式被称为多行注释。

;多行注释
(setq radius 3.5)
;|多行注释，以下两行 LISP 代码将被忽略
(setq cenpt (list 0.0 0.0 0.0))
(setq area 6.28)
|;多行注释结束


加载和运行 AutoLISP 程序

要运行 LISP 程序必须先在 ZWCAD 里将其加载，下面我们介绍如何在 ZWCAD 中加载 LISP 程序。

手动加载

手动加载 AutoLISP 文件的步骤如下：

1.鼠标点击下拉菜单中的“工具”»“加载应用程序”，或者在命令行输入 APPLOAD 后按回车（Enter）键，打开“加载应用程序文件”对话框。

2.点击“添加”按钮，在弹出的“打开”对话框中选择您要加载的 LISP 文件，所选择的文件将被添加到步骤 1 所示对话框的列表中。

3.在步骤 1 所示对话框的列表中选择欲加载的 LISP 文件，点击“加载”按钮，这样 LISP 程序就被加载到 ZWCAD 中了。

注：您也可以通过 ZWCAD 命令行来手动加载 LISP 文件，只需要命令行输入，例：(load "C:\\Program Files\\ZWCAD 2023 Chs\\Sample\\LISP\\Sample.lsp")，其中 C:\\Program Files\\ZWCAD 2023 Chs\\Sample\\LISP 是程序的路径，sample.lsp 是 LISP 程序的文件名。

自动加载

有时候您可能会编写一个 LISP 程序专用于设置绘图环境，如设置图层、文字样式、标注样式等，而且您可能希望这些 LISP 文件在每次启动 ZWCAD 或在 ZWCAD 中新建图纸时自动加载和运行，通过以下两种方法可以达到该目的。

  • 利用启动组自动加载

    执行上面步骤 2 后，在上图 2 所示对话框的列表中选择欲自动加载的 LISP 文件，点击“添加到启动组”按钮，或者点击“启动组”按钮，弹出下图所示对话框。

    点击“添加”按钮，选择欲自动加载的 LISP 文件。这样添加到启动组中的这些 LISP 文件在每次启动 ZWCAD 或在 ZWCAD 中新建图纸时都将自动加载或运行。

    注意 “加载应用程序文件”对话框的列表中被添加到启动组的 LISP 文件会反灰显示（如下图所示），这些文件不能通过点击“删除”按钮从列表中删除，必须先把文件从启动组中删除。

  • 利用 LISP 启动文件（zwcad.lsp）自动加载

    将设置绘图环境的 LISP 代码直接添加到 ZWCAD 安装目录下的 zwcad.lsp 文件中，这样以后每次启动 ZWCAD 时都会自动加载这些代码（但在 ZWCAD 中新建图纸时被不自动加载这些代码）。

运行 lsp 程序

在 AutoLISP 程序文件中用户可以包含多个自定义命令、自定义函数，也可写一些单独的 AutoLISP 表达式（不包含在任何自定义命令或自定义函数中），这三者的执行方式是不一样的：

  • 自定义命令

    自定义命令实际上是一种特殊的自定义函数，其函数名格式符合 C:XXX。自定义命令一般没有参数，您可以象执行 ZWCAD 内部命令一样在命令行执行这些命令，只需在命令行输入 XXX 即可。

  • 自定义函数

    自定义函数的函数名前不需添加“C:”前缀。自定义函数一般都有参数，是供其它自定义函数或自定义命令调用的，也可在命令行执行，但您需要在命令行输入调用这些函数的 AutoLISP 表达式，即输入 (funcname arg1 arg2) 。

  • 单独的 AutoLISP 表达式

    AutoLISP 程序文件中单独的 AutoLISP 表达式，在文件加载时就会立即执行，它们主要用于设置绘图环境。


调试技巧

建议使用“Visual LISP 编辑器”进行调试，下面是中望CAD 中的一些调试技巧，可以有效提高调试效率。

利用命令行回显

中望CAD 自身有一定的检测错误的能力，可以很容易检测出函数调用时的参数错误、command 函数调用中望CAD 内部命令时的关键字错误、透明命令调用等问题。

默认情况下，中望CAD 检测到这类错误时，将中断程序的执行，并在命令行报告错误信息，然后回显出错的代码，虽然这给源代码的安全性带来了隐患，但对调试程序是很有帮助的。但是以下几种情形将给调试 LISP 程序带来诸多不便：

1.当用户将 cmdecho 系统变量设置为 0，若 command 函数调用中望CAD 内部命令时，命令行不会显示命令执行过程，因此若该过程中存在 bug，将很难发现。

2.若您自定义了 *error* 函数进行错误处理，当中望CAD 检测到该类错误时，只会在命令行报告错误信息，而不会回显出错的代码，这样也不利于调试。

3.若您将系统变量 _zw_xprintlsperrcode 系统变量设置为 0，当中望CAD 检测到这类错误时，也只会在命令行报告错误信息而不回显出错的代码。

建议：程序调试阶段，请将 cmdecho 和 _zw_xprintlsperrcode 系统变量都设置为 1，并且将您自定义的 *error* 函数暂时注释掉。

利用 alert 函数

调用 alert 函数可以弹出一个仅带“确定”按钮的小对话框，并暂时中断 LISP 程序的执行，直到您按下对话框中的“确定”按钮，其作用相当于 AutoCAD 的 LISP IDE 中的设置断点，通常在您需要查看变量的值时，使用该函数。

该函数仅接受一个字符串类型的参数，因此它不能直接用于查看非字符串类型的变量值，可以使用附录中“转换函数列表”主题中介绍的相关函数将非字符串类型的变量转换为字符串类型后，再传递给该函数，也可以统一使用 vl-princ-to-string 函数，它可以将任意类型的变量的值转换为字符串形式。

利用全局变量

前面在“LISP 程序设计基础”的“自定义函数和命令”主题中介绍过全局变量和局部变量，虽然过多使用全局变量存在种种弊端，但在程序开发阶段还是建议您尽量使用全局变量，因为在 LISP 程序运行完毕或因出错而中断后，您都可以在中望CAD 命令行通过感叹号（!）+ 变量名的形式查看某变量的值。

巧用命令中断符（pause）

在 LISP 程序中会经常调用 command 函数执行中望CAD 内部命令，在执行某些命令时可能会因传递的参数不正确，或因中望CAD 命令频繁切换而导致程序不能达到预期的效果，此时可以在 command 函数的参数中使用 PAUSE 中断命令序列，逐一排查问题所在。


Visual LISP 编辑器

中望CAD 采用微软的 Visual Studio Code（以下简称 VSCode） 作为 LISP 编辑器，以插件的形式实现 LISP 的开发、测试和调试功能。

在安装 ZWCAD 的过程中，会自动安装 VSCode。

首次启动中望CAD，在 %userprofile% 目录下生成 .vscode 文件夹，插件文件夹存放在 %USERPROFILE%\.vscode\extensions 目录下。

使用环境

操作系统

支持 Windows 7 或更高版本。

ZWCAD 版本要求

支持 ZWCAD 2019 SP2 及更高版本。

其他要求

需要安装 .NET Framework 4.5

对于 Windows XP 以上的操作系统，可能需要自行安装 VSCode。

入口

可以通过以下三种方式访问 Visual LISP 编辑器。

  • Ribbon：管理 » 应用程序 » Visual LISP 编辑器
  • 经典界面：工具 » Visual LISP 编辑器
  • 命令行：执行 VLIDE 或 VLISP 命令打开 Visual LISP 编辑器

启动 Visual LISP 编辑器后，弹出以下界面。

单击 View » Extensions，切换至扩展视图页面，单击 ZwLisp 进入相应的界面，如下图所示。

界面风格

ZwLisp 提供一套自有的界面风格，用户可以通过以下两种方式进行设置。

  • 左下角的齿轮按钮：Manage » Color Theme » zwlisp theme
  • 在 ZwLisp 主窗口单击 Set Color Theme 按钮。

启动调试

添加 .lsp 文件

单击 File » Open Folder，在弹出的对话框中选择 .lsp 文件所在的文件夹。

单击位于左侧的 Debug 按钮切换至调试页面。调试面板中记录变量、监视、调用堆栈、断点等信息。

选择调试环境

首次启动调试，需要选择 zwlisp 调试环境。

单击左上角的齿轮 [image] ，系统自动打开配置文件 launch.json，将配置项 program 设置为 ZWCAD 的实际安装路径。 如下图所示。

选择调试模式

有两种调试模式可供选择：Attach to ZWCAD 和 Launch ZWCAD。

  • ZWCAD 已启动，选择 Attach to ZWCAD 模式
  • ZWCAD 未启动，选择 Launch ZWCAD 模式

选择调试模式后，单击 Debug » Start Debugging 启动调试。VScode 界面出现调试工具条。

设置断点

单击行号左边的空白处可以设置行断点，程序会在该行第一个左括号处中断。

在任意左括号处按 Shift+F9 可以设置列断点，程序会在断点处中断。

示例

在 VSCode 中打开 foo.lsp，在第二行设置断点。

选择 Launch ZWCAD 模式启动调试，在 ZWCAD 中加载对应的 foo.lsp 文件，执行自定义命令 foo，程序在断点处中断，如下图所示。

调试过程

上述示例在断点处中断后，可通过左侧调试面板观察变量的值。还可以在 WATCH 栏中添加要查看的变量，例如添加 e 和 i，此时变量未赋值，初始值均为 nil。

跳过断点后按 F11 单步执行，观察左侧调试面板中变量和监视变量中值的变化。

相关命令

  • APPLOAD
  • VLISP


发布 LISP 程序

经过不断地编码和调试，您开发的 LISP 应用程序已经满足需求了，但这并不意味着整个开发阶段完成，还需要发布您的工作成果。本节介绍发布 LISP 程序的步骤：

整理源代码

也许您在开发过程中就已经完成了这项工作，但是在最后发布前这项工作还是必不可少的，其作用主要在于保证程序的可维护性和健壮性。主要包括以下几个方面的工作：

1.调整代码格式，合理使用缩进、空格、空行，并对算法关键点添加注释；

2.注释调试代码，包括恢复系统变量的值（如 cmdecho 等）、恢复自定义 *error* 函数、将调试使用的全局变量声明为局部变量等；

3.增加容错处理代码。一个完善的应用程序应该具有相当的健壮性，当用户进行各种破坏性操作时都能够正常运行，虽然可以调用附录中的“错误处理函数列表”主题中介绍的函数进行错误处理，但有时在数据输入时就做好容错处理会使程序健壮性更好，如程序提示用户选择图元，而用户实际没有选中任何图元，此时就需要做好容错处理。


库函数分类介绍

正如第一章所言，学习 AutoLISP 程序设计主要是学习 LISP 程序设计基础知识和 AutoLISP 库函数的用法，第一方面已经在第二章中进行了介绍，本章对库函数的用法进行详细的讲解。中望CAD 提供了 230 多个 AutoLISP 库函数，本章将这些函数按功能分类进行介绍，在各类函数中分别抽取使用比较频繁的函数并辅以实例代码进行介绍，主要包括以下内容：

  • 数学运算函数

    • 本节主要介绍中望CAD 提供的用于数学运算的 AutoLISP 库函数，这些函数也称为操作符，并以 /（除）函数为例介绍使用数学运算函数的注意事项。

  • 关系运算函数

    • 本节主要介绍中望CAD 提供的用于比较两个变量或表达式返回值大小关系的 AutoLISP 库函数，并以 ＝（等于）、eq、equal 和 wcmatch 函数为例介绍这些函数的微小差异。

  • 逻辑运算函数

    • 本节主要介绍中望CAD 提供的用于构建复合条件的 AutoLISP 库函数，这些函数经常和关系运算函数一起用于选择结构或循环结构的判断条件。本节以 and 和 or 函数为例介绍该类函数的用法。

  • 位运算函数

    • 本节主要介绍中望CAD 提供的用于二进制位逻辑运算的 AutoLISP 库函数，并以调用 logand 函数设置 undo 开始标记或结束标记为例介绍该类函数的用法。

  • 转换函数

    • 本节主要介绍中望CAD 提供的用于变量数据类型转换或格式转换（如整数转换成字符串、实数转换成各种格式的字符串、字符串转换成各种格式的实数、角度值转换成各种格式的字符串等）、单位转换（如毫米转换成英寸等）和坐标系转换的 AutoLISP 库函数，并以 itoa、rtos、distof、angtos、angtof、ascii、chr、cvunit 和 trans 函数为例介绍了该类函数的用法。

  • 字符串处理函数

    • 本节主要介绍中望CAD 提供的用于字符串合并、大小写转换、字符数统计等的 AutoLISP 库函数，并以 strcase、strcat 和 strlen 为例介绍该类函数的用法。

  • 关联表处理函数

    • 本节主要介绍中望CAD 提供的用于创建表、查询表、修改表元素等的 AutoLISP 库函数，并以 list、nth、car、cadr、cdr、append、cons 和 subst 函数为例介绍该类函数的用法。

  • 数据输入函数

    • 本节主要介绍中望CAD 提供的用于与用户交互的 AutoLISP 库函数，并以 getxxx、initget 函数为例介绍该类型函数的用法。

  • 几何实用函数

    • 本节主要介绍中望CAD 提供的和图形的几何计算相关的 AutoLISP 库函数（如获取两直线的交点、获取两点之间的距离等），并以 osnap 和 textbox 函数为例介绍该类函数的用法。

  • 文件处理函数

    • 本节主要介绍中望CAD 提供的用于文件搜索、读写文件的 AutoLISP 库函数，并以 findfile、getfiled、open、princ、write-line 和 read-line 函数为例介绍该类函数的用法。

  • 图元处理函数

    • 本节主要介绍中望CAD 提供的用于获取图元数据、创建新图元、删除或修改图元、为图元添加扩展数据等的 AutoLISP 库函数，并以 entsel、nentsel、entnext、entdel、entget、entmod 和 entmake 函数为例介绍该类函数的用法。

  • 符号表处理函数

    • 本节主要介绍中望CAD 提供的用于遍历符号表和查询表记录等的 AutoLISP 库函数，并以 tblnext 和 tblsearch 函数为例介绍该类函数的用法。

  • 选择集处理函数

    • 本节主要介绍中望CAD 提供的用于创建选择集、添加图元到选择集、从选择集中删除图元等的 AutoLISP 库函数，并以 ssget、ssadd 和 ssdel 函数为例介绍该类函数的用法。

  • 词典处理函数

    • 本节主要介绍中望CAD 提供的用于创建词典和遍历词典条目等的 AutoLISP 库函数，并以 dictsearch 和 dictnext 为例介绍该类函数的用法。

  • 查询函数

    • 本节主要介绍中望CAD 提供的用于查询和设置中望CAD 系统变量或环境变量的 AutoLISP 库函数，并以 getvar、setvar、getenv 和 setenv 函数为例介绍该类函数的用法。

  • 命令函数

    • 本节主要介绍 中望CAD 提供的用于调用中望CAD 内部命令的 AutoLISP 库函数，以 command 函数为例介绍该类函数的用法。

  • 错误处理函数

    • 本节主要介绍 LISP 程序函数容错处理的几种方式，主要包括自定义 *error* 函数捕捉错误并终止程序、vl-catch-all-apply 函数捕捉错误并继续执行程序两方面的内容。


数学运算函数

中望CAD 提供了+（加）、-（减）、*（乘）、/（除）、1+（递增）和 1-（递减）等 23 种 AutoLISP 数学运算函数，该类函数的详细列表请参见附录中“数学运算函数列表”主题。本节以实例说明算术运算函数 /（除）的用法及注意事项。

传递给算术运算函数的 number 参数分别为整数或实数值时，函数将返回不同的值。若所有的参数都是整数，则返回值也是整数；但若一个或全部参数是实数，则返回值为实数，如下面示例代码所示：

命令：(/ 12 5)
2
命令：(/ 12.0 5)
2.40000


关系运算函数

中望CAD 提供了=（等于）、/=（不等于）、<（小于）、<=（小于或等于）、>（大于）、>=（大于或等于）、eq 、equal 和 wcmatch 等 9 种 AutoLISP 关系运算函数，该类函数的详细列表请参见附录中“关系运算函数列表”主题。关系运算表达式，返回值为 nil 或 T，主要用于选择结构和循环结构的条件表达式。本节着重介绍 =、eq、equal 判断是否相等时的区别和使用 wcmatch 函数进行通配符匹配的用法。

判断是否绝对相等

= 和 eq 函数的参数可以是变量、常量或表达式，这二个函数都用于判断参数是否相等，但又存在区别：

= 函数

用于判断两个或多个变量、常量或表达式的值是否相等，仅当所有参数的值完全相等时才返回 T，否则返回 nil。例如：

表达式 | 返回值 | 说明
(= "ZWCAD" "ZwCAD") | nil | 比较两个字符串常量是否相等
(= 3.1415 pi) | nil | 比较两个实数是否相等

注意 因实数存在精度问题，建议不要用 = 函数判断两个实数是否相等。

eq 函数

用于判断两个表达式是否为同一个对象或变量的引用。可能虽然两变量或表达式的值相等，但由于它们不是对同一变量或对象的引用而返回 nil。例如，

命令：(setq lst1 (list 10.0 12.0 0.0))
命令：(setq lst2 (list 10.0 12.0 0.0))
命令：(setq lst3 lst2)

表达式 | 返回值 | 说明
(eq lst1 lst2) | nil | lst1 和 lst2 值相等，但是对不同表达式的引用
(eq lst2 lst3) | T | lst2 和 lst3 是对同一表达式的引用

忽略微小差异判断是否相等

equal 函数

当比较两个实数（或由实数组成的表，如点表）时，即使是恒等的两个数，如果采用不同的计算方法，结果也有可能稍有差别，可以使用 equal 函数在设定的误差范围内进行比较。该函数经常用于比较两实数是否相等，例如：

表达式 | 返回值 | 说明
(equal 3.1415 pi 0.001) | T | 比较两个实数是否相等，允许误差小于 0.001
(equal 3.1415 pi 0.00001) | nil | 比较两个实数是否相等，允许误差小于 0.00001

wcmatch 函数

当比较两个字符串时，是无法设置误差范围的，那么如何忽略微小差异来判断两字符串是否相等呢？

中望CAD 提供了 wcmatch 函数将单个字符串与通配模式作比较，如果字符串与通配模式匹配则返回 T，否则返回 nil。通配模式与许多系统和应用程序使用的常用表达方式相似，在通配模式中，字母和数字被视为原意，方括号用于指定可选的字符或者指定一定范围内的字母或数字，问号（?）用于匹配单个字符，星号（*）用于匹配多个字符，这一点在 ssget 函数的过滤条件或 entget 函数检索多个注册的应用程序中的扩展数据中很有用。

假定下面示例代码中的 string 变量的值为 “This is a test string, and its last five letters are \"test1\".”，则下面示例代码说明了 wcmatch 函数的用法：

表达式 | 返回值 | 说明
(wcmatch string "This*") | T | 检测 string 是否以“This”开始
(wcmatch string "*test[3-58]*") | nil | 检测 string 是否含有“test3”、“test4”、“test5”或“test8”
(wcmatch string "That,This*,*test1") | T | 检测 string 是否等于“That”，或者以“This”开始，或者以“test1”结束


逻辑运算函数

逻辑运算也称布尔（Boolean）运算，用于测试真假值，它的参数一般为关系表达式。在 AutoLISP 中，0（零）和 nil 被认为是假（false），而任何非 0（零）值和 T 被认为是真（true）。中望CAD 提供了 and （与）、or（或）和 not （非）三种基本的 AutoLISP 逻辑运算函数，该类函数的详细列表请参见附录中“逻辑运算函数列表”主题。关于这三种逻辑运算函数的运算规则请参见下面的真值表：

表达式 1 | 表达式 2 | And 返回值 | Or 返回值
T | T | T | T
T | nil | nil | T
nil | T | nil | T
nil | nil | nil | nil
表达式 | Not 返回值
nil | T
T | nil

简单的说：
and 函数——参数全为 T（真）时，才返回 T（真）；
or 函数—— 参数全为 nil（假）时，才返回 nil（假）；
not 函数——参数为 T（真）时，返回 nil（假）；参数为 nil（假）时，返回 T（真）。

例如，常规的数学不等式“60 ≤ a ≤ 79”在 LISP 程序中则需要用 and 函数来表示：

(and (>= a 60) (<= a 79))

在进行逻辑运算时，若 and 和 or 函数在计算表达式 1 后即可确定该逻辑运算的最终返回值，将提前结束对参数表中其余表达式求值。如下所示：

(and (< a 50) (setq a 50))

上例中 and 函数的两个参数分别为 (< a 50) 和 (setq a 50)，在执行该表达式时，先计算表达式 (< a 50) 值，若该表达式的返回值为假（0 或 nil），则无论表达式 2 的返回值是什么，and 函数的最终执行结果都是假，因此将不再对表达式 (setq a 50) 进行求值，即该示例代码的作用是检测变量 a 的值，保证其值不小于 50。

(or (< a 50) (setq a 50))

上例中 or 函数的两个参数分别为 (< a 50) 和 (setq a 50)，在执行该表达式时，先计算表达式 (< a 50) 值，若该表达式的返回值为真（非 0 或 T），则无论表达式 2 的返回值是什么，or 函数的最终执行结果都是真，因此将不再对表达式 (setq a 50) 进行求值，即该示例代码的作用是检测变量 a 的值，保证其值不大于 50。


位运算函数

ZWCAD 提供了 boole、logand、logior 和 lsh 等 AutoLISP 位运算函数，该类函数的详细列表请参见附录中“位运算函数列表”主题。在介绍位运算函数之前，我们有必要先了解一下数据在计算机中的存储形式。

计算机都是以二进制形式存储数据的，最小单位是位（bit），每 8 个 bit 位构成一个字节（Byte），每个 bit 位存储的值只能为 1 或 0。我们对数据所占内存空间的描述都是以 Byte 为单位进行计量的。接下来我们介绍的位运算函数就是针对各个 bit 位进行的逻辑运算。在计算机内存中每个字节的存储如下图所示，每个小方格代表组成该字节的一个 bit 位：

在单纯的算术运算中，我们很少使用位运算函数。在 ZWCAD 中，位运算函数经常用于操作一些值为可选位码值之和的系统变量（如 AFLAGS、AUTOSNAP、CMDACTIVE、UNDOCTL 等）。下面示例代码以 UNDOCTL 系统变量为例，演示位运算函数的用法：

在 ZWCAD 中，UNDOCTL 系统变量可选的位码及各位码在内存储存形式如下表所示：

位码值 | 含义 | 内存存储形式
0 | 关闭 undo | 位码 0 的内存存储形式
1 | 打开 undo | 位码 1 的内存存储形式
2 | 只能放弃一条命令 | 位码 2 的内存存储形式
4 | 打开“自动” | 位码 4 的内存存储形式
8 | 一个编组处于当前活动状态 | 位码 8 的内存存储形式

以下示例代码用于检测当前是否设置了 undo 起始标志，若已设置，则为其设置 undo 终止标志，若未设置，则设置 undo 起始标志：

(defun undosetting ()
  (if (logand 8 (getvar "undoctl"))    ;;;判断是否设置了 undo 起始标志
    (command "undo" "end")
    (command "undo" "be")
  )
  (princ)
)

十进制整数 8 的二进制表示形式如下图所示：

十进制 8 的二进制形式

因 0 与任何数的“与”运算的结果都是 0（假），而 1 只有与 1 进行“与”运算时才返回 1（真）。上面示例代码中 (logand 8 (getvar "undoctl")) 表达式用于检测系统变量 undoctl 的二进制形式中第 4 位是否为 1，若返回值为 T，则表明 undoctl 系统变量的二进制形式中第 4 位为 1， 即有一个编组处于当前活动状态（设置了 undo 起始标志）。


转换函数

中望CAD 提供了 angtof、angtos、ascii、atof、atoi、chr、cvunit、distof、itoa、rtos 和 trans 等 11 种 AutoLISP 转换函数，用于字符串转换、角度转换、ASCII 码转换、单位转换和坐标系转换。该类函数的详细列表请参见附录中“转换函数列表”主题。

字符串转换

某些输出函数只能接受字符串参数，此时需要将数字（整数或实数）转换成字符串。

itoa 函数，可以将整数转换成字符串，以下示例代码展示了该函数的用法：

命令：(itoa 2023)
"2023"

rtos 函数，可以将实数转换成字符串，调用的格式如下：

(rtos 数值 [模式 [精度]])

若未指定“模式”和“精度”参数，结果字符串的格式由中望CAD 的 LUnits（指定实数的单位）和 LUprec（指定实数的精度）系统变量决定。

以下示例代码，调用 rtos 函数分别设置不同的转换模式和精度将预置变量 pi 转换成字符串返回：

表达式 | 返回值 | 说明
(rtos pi 1 4) | "3.1416E+00" | 模式1 ＝ 科学计数法，精度 ＝ 4
(rtos pi 2 5) | "3.14159" | 模式2 ＝ 小数，精度 ＝ 5
(rtos pi) | "3.1416" | 不指定“模式”和“精度”参数，当前系统变量：LUnits = 2，LUprec = 4

distof 函数，可以将一个表示实数的字符串转换成实数，与 rtos 函数互补，调用的格式如下：

(distof 字符串 [模式])

“字符串”参数必须是表示一个实数的字符串，即该参数的值必须能按“模式”参数指定的格式进行正确地解析；“模式”参数用于指定字符串的格式单位（科学计数、小数、工程、建筑或分数），起到协助中望CAD 进行字符串解析的作用，该参数也可省略。

以下示例代码，调用 distof 函数设置不同的模式将表示实数的字符串转换成实数：

表达式 | 返回值 | 说明
(distof "100.123E-3") | 0.100123 | 省略“模式”参数
(distof "12 1/3" 5) | 12.3333 | 模式 5 ＝ 分数
(distof "1'-2.30\"" 3) | 14.3000 | 模式 3 ＝ 工程

angtos 函数，可以将以弧度为单位表示角度的实数转换成字符串，调用的格式如下：

(angtos 角度 [模式 [精度]])

若未指定“模式”和“精度”参数，结果字符串的格式由中望CAD 的 AUnits（指定角度的单位）和 AUprec（指定角度的精度）系统变量决定。

以下示例代码，调用 angtos 函数分别设置不同的转换模式和精度将实数 1.25 转换成字符串返回：

表达式 | 返回值 | 说明
(angtos 1.25 0 2) | "71.62" | 模式 0 ＝ （十进制）度，精度 ＝ 2
(angtos 1.25 1 2) | "71d37'" | 模式 1 ＝ 度/分/秒，精度 ＝ 2
(angtos 1.25) | "72" | 不指定“模式”和“精度”参数，AUnits = 0，AUprec = 0

由于 angtos 函数计算时会考虑到 AngBase 系统变量，所以下列代码总是返回 "0"：

(angtos (getvar "AngBase"))

在 AutoLISP 中要以各种模式和精度返回 AngBase 系统变量的角度与 WCS 的 X 轴方向的夹角，可以先 AngBase 的值保存到一个临时变量中，再将 AngBase 设置为 0，然后调用 angtos 函数求值，最后再恢复 AngBase 的初始值，如下面示例代码所示：

(defun GetAngBase (mode precision / old_angbase temp)
  (setq old_angbase (getvar "AngBase"))
  (setvar "AngBase" 0.0)
  (setq temp (angtos old_angbase mode precision))
  (setvar "AngBase" old_angbase)
  (eval temp)
)

假定运行下列代码之前，已经执行 (setvar "AngBase" 0.5233) 语句将 AngBase 系统变量的值设置为 0.5233（弧度），执行以下代码可以指定的模式和精度返回 AngBase 的字符串形式：

命令：(getangbase 1 2)
"29d59'"

angtof 函数，可以将一个表示角度的字符串转换成实数形式的弧度值，与 angtos 函数互补，调用的格式如下：

(angtof 字符串 [模式])

“字符串”参数必须是表示一个合法的角度值（十进制度、度/分/秒、百分度、弧度或勘测单位均可）；“模式”参数用于指定角度字符串的格式单位，起到协助中望CAD 进行字符串解析的作用，该参数也可省略。

以下示例代码，调用 angtof 函数设置不同的模式将表示角度的字符串转换成实数：

表达式 | 返回值 | 说明
(angtof "35d30'20\"") | 0.453762 | 省略“模式”参数
(angtof "3.14159r" 3) | 2.97566 | 模式 3 ＝ 弧度

角度转换

angtos 和 atof 函数结合使用，可以将表示角度的实数从弧度（如 angle 和 getangle 函数返回值）转换成角度。首先调用 angtos 函数并指定“模式”参数为 0 将表示弧度的实数转换成十进制角度的字符串，然后再调用 atof 函数将表示实数的字符串转换成实数，如下面代码所示：

命令：(setq deg (atof (angtos (getangle "\n指定角度（弧度数）：") 0)))
指定角度（弧度数）：输入弧度数或点取一点
第二点: 点取另一点
17.0000

虽然这种方法可以达到目的（弧度转换成角度），但这种方法不直观，建议根据弧度和角度的几何转换关系自定义转换函数，下例中 Rad->Deg 函数实现弧度转角度的功能，Deg->Rad 函数实现角度转弧度的功能：

(defun Rad->Deg	(radian)
  (* (/ radian pi) 180.0)
)

(defun Deg->Rad	(degree)
  (* (/ degree 180.0) pi)
)

ASCII 码转换

在程序设计中经常将字符转换为 ASCII 码或将一个 0 至 255 之间的整数转换成一个字符，AutoLISP 实现该功能的函数分别是：ascii 和 chr。如下示例代码展示了这两个函数的用法：

表达式 | 返回值 | 说明
(ascii "\n") | 10 | 返回换行符的 ASCII 码
(chr 65) | "A" | 返回 ASCII 码为 65 的字符

单位转换

cvunit 函数可以在不同测量单位之间进行换算，调用的格式如下：

(cvunit 数值 原始单位 转换单位)

“原始单位”指定源测量单位，“转换单位”指定目标测量单位，两者可以为 zwcad.unt 文件中定义的任何单位类型，但若这两个参数指定单位不兼容（如米和千克），该函数将返回 nil。

在图形编辑过程中，cvunit 函数进行单位转换时，必须在中望CAD 安装目录下的 zwcad.unt 文件中查找“原始单位”和“转换单位”参数指定的单位字符串。如果应用程序有许多值需要从一个单位系统转换为另一个，建议单独调用一次 cvunit 函数将 1.0 从源单位系统转换到目标单位系统，然后在以后的转换中用这个调用的返回值进行缩放，如下示例代码展示了这种用法：

(defun batcvunit (valuelist from-unit to-unit / factor)
  (setq factor (cvunit 1 from-unit to-unit))       ;;;获取单位转换时的比例因子
  (mapcar '(lambda (x) (* x factor)) valuelist)
)

如果需要将多个以米为单位的尺寸（如 12.3 45.6 25.3 24.1 68.4 等）转换为以英寸为单位的尺寸，只需要执行 LISP 语句 (batcvunit '(12.3 45.6 25.3 24.1 68.4) "meters" "inches") 即可，返回值为 (484.252 1795.28 996.063 948.819 2692.91)。

坐标系转换

trans 函数将点或位移从一个坐标系转换到另一个坐标系中，调用的格式如下：

(trans 点 原位置 新位置 [位移])

当“位移”参数非零时，“点”参数表示一个三维位移，否则表示一个三维点。“原位置”指定坐标系转换时的源坐标系；“新位置”指定坐标系转换时的目标坐标系。

下面对中望CAD 坐标系进行简要的介绍：

坐标系 | 描述
WCS | 世界坐标系。所有其它坐标系都相对于 WCS 定义，该坐标系恒定不变，相对于 WCS 所测量的值可以稳定地转换到其它坐标系中，默认情况下，中望CAD 将 WCS 作为当前坐标系，所有从中望CAD 返回的点，包括 AutoLISP 程序和外部函数返回的点，都是 WCS 中的点。
UCS | 用户坐标系。用户可以设置 UCS 以方便绘图。所有传入中望CAD 命令的点，都是当前 UCS 中的点（除非用户在命令行中输入的点前加“*”，如 *12.5,3.2）。若应用程序需将 WCS、OCS 或 DCS 中的坐标传递给中望CAD 命令，则必须先调用 trans 函数将它们转换到当前 UCS 中。
OCS | 对象坐标系。entget 函数返回的复杂图元中的子图元的特征点是以此坐标系表示的，即相对于主图元对象本身的坐标。根据该坐标的用途，这些点经常被转换到 WCS、当前 UCS 或当前 DCS 中。反过来，在调用 entmod 或 entmake 函数将这些点写入复杂实体之前，它们也必须被转换到 OCS 中。
DCS | 显示坐标系。在显示图形前，先将对象转换到此坐标系中。DCS 的原点存储在中望CAD 的 Target 系统变量中，并且以 Z 轴作为观察方向，这些坐标可以决定显示给中望CAD 用户的画面的位置。在涉及视口的操作中，此类坐标系的转换较多。
PSDCS | 图纸空间 DCS。此坐标系仅能与当前激活的模型空间视口进行转换。它实质上是一个二维的转换，如果“位移”参数为零，则 X 坐标和 Y 坐标总是按比例缩放和平移，Z 坐标仅按比例缩放而不平移，因此它可以用于确定两个坐标系之间的比例因子。

trans 函数的“原位置”和“新位置”参数可以用下列任一方法来指定坐标系：

  • 用一个整数代码表示。有效的代码及各代码的含义请参见《LISP 函数参考》中 trans 主题。

  • AutoLISP 图元名。对于平面对象，OCS 可能与 WCS 不同，若相同，则 OCS 和 WCS 之间的转换是一个恒等转换。

下面示例代码实现了无论当前 UCS 如何，为所有选择中的圆绘制十字中心线：

(defun c:crosscenterline (/ osnapOld filediaOld cecolorOld
           celtypeOld sset circleNum circle-ename
           dxflist radius cenp
          )
;;;备份当前系统变量的值
  (setq osnapOld (getvar "osmode"))
  (setq filediaOld (getvar "filedia"))
  (setq cecolorOld (getvar "cecolor"))
  (setq celtypeOld (getvar "celtype"))
;;;加载中心线所需线型
  (if (null (tblsearch "ltype" "center"))
    (command "-linetype" "L" "center" "zwcadiso" "")
  )
;;;设置系统变量的新值
  (setvar "osmode" 0)
  (setvar "filedia" 0)
  (setvar "cecolor" "1")
  (setvar "celtype" "center")
  (setq sset (ssget '((0 . "CIRCLE"))))
  (setq circleNum (sslength sset))
  (setq i 0)
;;;遍历选择集中的圆
  (repeat circleNum
    (setq circle-ename (ssname sset i))
    (setq dxflist (entget circle-ename))
    (setq radius (cdr (assoc 40 dxflist)))	    ;;;获取圆的圆心

坐标
    (setq cenpt (cdr (assoc 10 dxflist)))  	;;;entget 返回的圆心

坐标是 WCS 中的点
    (setq cenpt (trans cenpt circle-ename 1))	;;;将圆心的 WCS 坐标

转换到当前 UCS 下
    (command "line" cenpt (polar cenpt 0.0 (+ radius 3.0)) "")
    (command "line" cenpt (polar cenpt (* pi 0.5) (+ radius 3.0)) "")
    (command "line" cenpt (polar cenpt (* pi 1.0) (+ radius 3.0)) "")
    (command "line" cenpt (polar cenpt (* pi 1.5) (+ radius 3.0)) "")
    (setq i (1+ i))
  )
;;;恢复系统变量的原值
  (setvar "osmode" osnapOld)
  (setvar "filedia" filediaOld)
  (setvar "cecolor" cecolorOld)
  (setvar "celtype" celtypeOld)
  (princ)
)


字符串处理函数

中望CAD 提供了strcase、strcat、strlen 和 substr 等 17 种 AutoLISP 字符串处理函数。该类函数的详细列表请参见附录中“字符串处理函数列表”主题。

strcase 函数用于将字符串中的所有字母字符转换为大写或小写，调用的格式如下：

(strcase 字符串 [模式])

“字符串”参数指定待处理的字符串；“模式”参数指定返回字符的大小写。若忽略“模式”参数，或它被赋值为 nil， 则 strcase 函数返回大写的字符串。

命令：(strcase "Hello, ZWCAD.")
"HELLO, ZWCAD."

若“模式”参数为 T，则返回小写的字符串。AutoLISP 提供了预定义的变量 T，以便在这种需要用非 nil 值作为真/假开关的情况下使用。

命令：(strcase "Hello, ZWCAD." T)
"hello, zwcad."

strcat 函数用于将多个字符串组合成一个长字符串，调用的格式如下：

(strcat [字符串1 [字符串2]…])

它一般用于将字符串变量的值插入到常量字符串中，如下示例代码将一个字符串存储到变量中，然后用 strcat 将该变量的值（字符串）插入到另一个常量字符串中：

命令：(setq appname "ZWCAD") (setq msg (strcat "You're running " appname " application."))
"You're running ZWCAD application."

strlen 函数用于计算某字符串中的字符数目（包括空格）或多个字符串的字符总数，调用的格式如下：

(strlen [字符串1 [字符串2]…])

若变量 msg 被赋值为上述字符串，如下示例代码可计算其字符数：

命令：(strlen msg)
33

substr 函数用于返回某个字符串的一个子串，调用的格式如下：

(substr 字符串 起始位置 [长度])

“字符串”参数指定待处理的字符串；“起始位置”参数指定子字符串的第一个字符在原字符串中的位置，注意，原字符串中的第一个字符的位置为 1；“长度”参数指定子字符串中包括的字符数，若未提供该参数，substr 函数将返回由指定的开始字符及其后的所有字符组成的字符串，如下示例代码展示了如何获取某文件的主文件名（注意实际上可用 vl-filename-base 函数达到该目的）：

命令：(setq flname "dasetup.log")
"dasetup.log"
命令：(setq newlen (- (strlen flname) 4))
7
命令：(setq mflname (substr flname 1 newlen))
"dasetup"

用户需要得到一个字符串，该字符串包括除某字符串最后四个字符（句点和三个字母的扩展名）以外的所有字符。可以用 strlen 函数得到文件名字符串的长度，并从该值中减去 4，即为 substr 函数所需的“长度”参数。


表处理函数

中望CAD 提供了append、assoc、car、cadr、caddr、cdr、cons、foreach、length、list、listp、mapcar、member、nth、reverse 和 subst 等 32 个 AutoLISP 表处理函数，该类函数的详细列表请参见附录中“表处理函数列表”主题。表提供了保存大量相关值的一种有效方法，利用表处理函数可以高效地进行表的遍历并读取其中的各元素，熟练操纵表，可以创建更加强大和灵活的应用程序。

创建和查询表

list 函数提供了将相关项编组成表的一种简单方法，是 AutoLISP 中使用频率非常高的一个函数。表中的各个元素的数据类型可以相同或不同，下列示例代码将五个不同数据类型的数值组合成一个表：

命令：(setq lst1 (list 1.0 "one" 1 "ZWCAD" "AutoLISP"))
(1.00000 "one" 1 "ZWCAD" "AutoLISP")

若表中所有元素都是常量，则可以用 quote 函数显式地定义该表，而不必使用 list 函数。quote 函数返回未求值的表达式，单引号（'）可用作 quote 函数的简写。下列示例代码构建了一个三维点表并将其赋值给变量 pt1：

命令：(setq pt1 '(52.0 12.5 0.0))
(52.0000 12.5000 0.000000)

nth 函数可以从表中检索指定序号的项，调用的格式如下：

(nth 索引号 表)

“索引号”指定要返回的元素的序号（表的第一个元素的序号为 0）。下列示例代码返回表 lst1 中的第二项：

命令：(setq elem2 (nth 1 lst1))
"one"

car 和 cdr 函数提供从表中读取项目的另一种方法。car 函数返回表的第一个元素，cdr 函数以表的形式返回表中除第一个元素外的所有元素：

命令：(car ls1)
1.00000
命令：(cdr ls1)
("one" 1 "ZWCAD" "AutoLISP")

AutoLISP 支持最多四层深度的 car 和 cdr 串联，例如，caaaar、caaadr、caadar、cadaar、cdaaar、caaddr、cadadr、cdaadr、caddar、cdadar、cddaar、caaar、caadr、cadar 和 cdaar 等都是有效的 AutoLISP 函数，这些函数等效于对 car 和 cdr 函数的嵌套调用：每一个 a 表示一个对 car 函数的调用 ，每个 d 表示一个对 cdr 函数的调用：

(caar lst1) 等效于 (car (car lst1))
(cdar lst1) 等效于 (cdr (car lst1))
(cadadr lst1) 等效于 (car (cdr (car (cdr lst1))))

这些串联形成的函数经常用于处理二维点表或三维点表，例如，下列示例代码展示了如何获取三维点表的 X、Y 和 Z 坐标：

命令：(setq x_val (car pt1))
52.0000
命令：(setq y_val (cadr pt1))
12.5000
命令：(setq z_val (caddr pt1))
0.000000

修改或添加表元素

AutoLISP 提供了三个函数用于对现有的表进行修改：

  • append 在表的末尾添加新项，并返回新表

  • cons 在表的开始添加新项，并返回新表

  • subst 用新项替换找到的每一个旧项，并返回新表

以上三个函数都不修改原表，而只是返回修改后的表，若要修改原表，需用户显式的对存储原表数据的变量重新赋值。

append 函数可接受任意数量的表，并将它们合并到一个新表中，因此函数的所有参数都必须是表。如下示例代码将另一个“ZWCAD”添加到表 ls1 中，注意必须先将“ZWCAD”转换成表才能传递给 append 函数：

命令：(setq ls2 (append ls1 '("ZWCAD")))
(1.00000 "one" 1 "ZWCAD" "AutoLISP" "ZWCAD")

cons 函数用于将单个元素与表组合。用 cons 函数可将某元素添加到表的开始，如下代码所示：

命令：(setq ls3 (cons "ZWCAD" ls1))
("ZWCAD" 1.00000 "one" 1 "ZWCAD" "AutoLISP")

subst 函数用于替换表中出现的某个或多个相同的项。如下示例代码将用字符串“ZWSoft”替换表中所有的“ZWCAD”：

命令：(setq ls4 (subst "ZWSoft" "ZWCAD" ls2))
(1.00000 "one" 1 "ZWSoft" "AutoLISP" "ZWSoft")

点对（Dotted Pair）处理

点对（Dotted Pair）是一种特殊的表，它必须包含两个元素，且两元素间以句点（.）分隔，之所以称之为特殊的表，是因为求这种表结构的最后一个 cdr 时中望CAD 将报错（因为其最后一个 cdr 不是 nil），下列代码展示了这一区别：

命令：(setq lst '(0 "LINE"))
(0 "LINE")
命令： (cddr lst)
nil
命令：(setq pair (cons 0 "LINE"))
(0 . "LINE")
命令：(cddr pair)
错误: 错误参数类型

前面曾介绍过，cons 函数用于在现有表的第一个元素前添加新项，若该函数的第二个参数不是表结构，则它将创建一个点对，例如，下列示例代码创建一个点对：

命令：(setq entity (list (cons 'type "circle") (cons 'color 1) (cons 'radius 1.25) (cons 'cenpt '(0.0 0.0 0.0))))
((TYPE . "circle") (COLOR . 1) (RADIUS . 1.25000) (CENPT 0.000000 0.000000 0.000000))

car、cdr 和 assoc 函数经常用于处理点对，下列示例代码展示了相关用法：

命令：(setq temp (assoc 'radius entity))　　　　　　;;;从表中获取圆的半径
(RADIUS . 1.25000)
命令：(cdr temp)
1.25000


数据输入函数

中望CAD 与用户的交互，除可以通过在 command 函数中使用 PAUSE 符号暂停以等待用户输入外，还提供了一些函数专用于提示用户输入数据（角度、点、距离、字符串等），如 getangle、getcorner、getdist、getint、getkword、getorient 和 getpoint 等，该类函数的详细列表请参见附录中“数据输入函数列表”主题。

getxxx 函数

所有的用户输入函数 getxxx 都会在命令行显示一行提示信息并暂停以等待用户输入指定类型的数据，并返回输入的值。

在前文中介绍过，用户在开发 AutoLISP 应用程序时，需要捕获错误并处理错误。当用户与中望CAD 交互时，用户输入函数利用中望CAD 的错误检查能力，能够自动检测出微小错误并进行相应处理，预先调用 initget 函数可以提供附加的过滤能力。

函数 getkword 暂停以等待用户输入关键字或关键字缩写，关键字必须在调用 getkword 函数之前由 initget 函数定义。所有的用户输入函数（getstring 除外）除了正常返回值外，都可以接受关键字。

getkword 函数的“提示”参数用于打印一条提示信息，如果不指定该参数，命令行将不显示任何提示信息，无法指导用户进行输入。虽然可以在前面调用 prompt 或 princ 函数打印提示信息，但这不是一个好办法，因为 getkword 函数在检测到用户输入非法时，会再次打印提示信息（“提示”参数），若没有指定“提示”参数并且用户输入非法，那么中望CAD 会在命令行中打印“不能识别输入. 请重试.”，这可能造成混乱，因为原始提示可能已经不在命令提示区域内。

用户输入函数的条件控制

initget 函数可以对下一个被调用的用户输入函数进行底层控制。能够接受关键字输入的函数有 getint、getreal、getdist、getangle、getorient、getpoint、getcorner、getkword、entsel、nentsel 和 nentselp。getstring 函数是唯一不接受关键字的用户输入函数。initget 函数的调用格式如下：

(initget [位] [关键字列表])

“位”参数指定控制位（多个控制码之和），它用于控制是否允许下一个用户输入函数的某一类输入值；“关键字列表”参数用于指定关键字及其缩写，在下一个用户输入函数调用中将识别该关键字或其缩写。

initget 函数所设置的控制位和关键字仅在下一个用户输入函数的调用中生效，在该调用后，它们就不再起作用。应用程序不必再次调用 initget 函数来清理关键字。

下面详细介绍“位”和“关键字列表”参数的用法：

“位”参数

该参数的值限制了其后用户输入函数的输入类型，这样就缩小了错误检查的范围。要一次设置多个条件，可以将这些值相加得到一个“位”值，该值应该在 0 到 255 之间。 若未使用“位”参数，该参数的默认值为 0。例如下面代码：

(initget (+ 1 2 4))
(getreal "\n请输入重量：")

此示例代码要求输入重量，当用户试图输入负数、零、字符串或只按回车（Enter）键时，中望CAD 将显示错误信息，并重新提示“请输入重量：”，如以下示例代码所示：

命令: (initget (+ 1 2 4))
nil
命令: (getreal "\n请输入重量：")
请输入重量：a
不能识别输入. 请重试.
请输入重量：-3
需要一正值. 请重试:
请输入重量：0
需要 1 和 20000 之间的整数.
请输入重量：12.5
12.5000

“关键字列表”参数

该参数指定一个关键字序列，下一个用户输入函数的调用将识别这些关键字。

中望CAD 对关键字序列有如下格式规定：

1.每个关键字与随后的关键字之间用一个或多个空格分隔，例如，“Radius Perimeter Area”定义了三个关键字

2.关键字只能由字母、数值和连字符（-）组成

除完整的关键字外，initget 函数可以定义关键字的缩写。当用户输入与关键字相同的字符串（不区分大小写）或关键字的缩写（不区分大小写）时，用户输入函数会返回预定义的关键字。定义关键字的缩写有两种方法：

  • 关键字的必需部分用大写字母表示，其余部分用小写字母表示。大写的缩写部分可以位于关键字的任何位置（例如，“SCale”、“eXit”或“toP”）

  • 整个关键字用大写字母表示，其后紧跟一个逗号，然后再跟随其缩写形式（例如，“LTYPE,LT”）。这种情况下，关键字的缩写形式必须包含关键字的第一个字符，这意味着“EXIT,X”是无效的

以下示例代码中，在调用 getkword 之前调用了 initget 函数，它指定了三个关键字。该程序检查这些关键字，并执行相应的代码：

(defun c:draw (/ op)
  (initget "Rectangle Circle Line")     ;;;定义关键字列表
  (setq op (getpoint "矩形(R)/圆(C)/<直线(L)>："))
  (cond
    ((= (type op) 'list) (command "_line" op))
    ((or (= op nil) (= op "Line")) (command "_line"))
    ((= op "Rectangle") (command "_rectangle"))
    ((= op "Circle") (command "_circle"))
  )
  (princ)
)

此 initget 函数未限制下一个调用的用户输入函数的输入类型，并建立包含三个关键字“Rectangle”、“Circle”和“Line”的序列。然后，调用 getpoint 函数用于获取一个点坐标，同时也可接受一个字符作为关键字，并显示下列提示：

矩形(R)/圆(C)/<直线(L)>：

结果存放于局部变量 op 中，若用户直接在绘图区点取一点，经过 cond 函数进行条件判断，将以该点作为起点绘制直线；若用户输入 L 或 l 或回车，进入直线绘制命令；若用户输入 R 或 r ，则进入矩形绘制命令；若用户输入 C 或 c ，则进入圆绘制命令。


几何实用函数

中望CAD 提供了 angle、distance、inters、osnap、polar 和 textbox 等 AutoLISP 几何实用函数，用于计算交点、角度、距离等。该类函数的详细列表请参见附录中“几何实用函数列表”主题，本小节将详细介绍 osnap 和 textbox 函数的用法。

angle 函数可以获取指定两点的连线与 X 轴正方向所成角度的弧度值（在当前 UCS 中）。

distance 函数可以获取指定两点之间的三维距离，类似于 ZWCAD 的 dist 命令。

polar 函数可以获取相对于指定点一定距离和一定角度的点（极坐标方式）。

inters 函数可以获取两条直线的交点或延长线的交点。

对象捕捉

osnap 函数可用中望CAD 对象捕捉模式来捕捉点，调用的格式如下：

(osnap 点 模式字符串)

“点”参数指定捕捉基点；“模式字符串”参数指定捕捉模式，有效的捕捉模式包括：nea（最近点）、end（端点）、mid（中点）、cen（圆心）、gcen（几何中心）、per（垂足）、tan（切点）、qua（象限点）、ins（插入点）、nod（节点）、int（交点）、app（外观交点）、para（平行）、ext（延伸）。

以下示例代码获取最接近 pt1 点的对象的端点：

(setq pt2 (osnap pt1 "end"))

以下示例代码获取最接近 pt1 点的对象的中点、端点或圆心：

(setq pt3 (osnap pt1 "mid,end,cen"))

如果有一个点满足要求，osnap 函数以三维点表的形式返回该点的坐标；若有多个捕捉点满足要求，那么返回值由系统变量 SortEnts 的设置决定；若没有点满足要求，则返回 nil。

注意 osnap 函数进行对象捕捉时，捕捉精度受系统变量 AperTure 的影响，它用于设置对象捕捉靶框的大小。

文本区域

textbox 函数用于测量文本对象（单行文字或多行文字）的矩形边框，它返回矩形边框的左下角点和右上角点坐标，调用的格式如下：

(textbox dxf表)

"dxf表"参数，是一个图元定义表，该表的格式与 entget 函数返回表的类型相同。该表可以包含文字对象的完整组码，也可仅包含描述文字内容（组码 1）的表。

textbox 函数只是用于测量文本对象的边框范围，它假定文本对象的对齐方式为左对齐，插入点在 WCS 的坐标原点，且旋转角度为 0，返回值为一个包含两个三维点表的关联表，受文本对象的文字内容（组码 1）、字体（组码 7）、文字高度（组码 40）、宽度比例（组码 41）、倾斜角度（组码 51）和文字生成方式（组码 71）的影响。因此，在 textbox 函数的参数（图元定义表）尽量提供这些信息，否则将以当前默认值进行测试。

如下面示例代码，向 textbox 函数传递了一个最简单的图元定义表（仅包含组码 1，用于设置文本内容），由于未指定附加信息（字体、文字高度、宽度比例、倾斜角度和文字生成方式），所以将以当前缺少的文字样式和文本高度进行测量。

命令：(textbox '((1 . "Hello ZWCAD")))
((0.000000 0.000000 0.000000) (25.0000 2.50000 0.000000))

以下示例代码，向 textbox 函数传递了一个包含所有附加信息的图元定义表（文字样式为 standard，文字高度为 5.0，宽度比例为 0.667，倾斜角度为 18 度，文字反向生成），测量文本对象的边框：

命令：(textbox '((1 . "Hello ZWCAD") (7 . "standard") (40 . 5.0) (41 . 0.667) (51 . 0.314) (71 . 2)))
((-34.7031 0.000000 0.000000) (0.000000 5.00000 0.000000))

以下示例代码，可以在任意 UCS 下为所选的单行文字对象添加文本边框：

(defun c:AddTextBox (/ txtobj txtdxf roangle inspt border lbpt rupt)
  (setq txtobj (car (entsel "\n选择单行文字：")))
  (setq txtdxf (entget txtobj))
  (setq roangle (/ (* (cdr (assoc 50 txtdxf)) 180.0) pi))
  (setq inspt (cdr (assoc 10 txtdxf)))   ;;;获取文字插入点（在 WCS 中）
  (setq inspt (trans inspt txtobj 1))   ;;;将文字插入点从 WCS 转到当前 UCS 中
  (setq border (textbox txtdxf))
  (setq lbpt (car border))
  (setq rupt (cadr border))
  (command "rectangle" lbpt rupt)
  (command "_move" "L" "" "0.0,0.0" inspt "_rotate" "L" "" "@" roangle)
  (princ)
)


文件处理函数

中望CAD 提供了close、findfile、open、read-char、read-line、vl-file-copy、vl-file-rename 和 vl-mkdir 等 19 种 AutoLISP 文件处理函数，可用于搜索文件、读写文件、复制文件、创建目录等，该类函数的详细列表请参见附录中“文件处理函数列表”主题。

文件搜索

findfile 函数，可用于搜索指定的文件，调用的格式如下：

(findfile 文件名)

“文件名”参数指定要搜索的文件。若该参数仅指定了文件名（未指定文件路径），则将在中望CAD 搜索路径下搜索文件；若该参数指定了完整的路径及文件名，则中望CAD 在指定路径下搜索文件。如果搜索成功，则返回完整的文件名及路径，如下面示例代码所示：

命令：(setq filename (findfile "zwcad.lsp"))
"C:\\Program Files\\ZWCAD 2023 Chs\\zwcad.lsp"

命令：(setq filename (findfile "C:\\Program Files\\test.dat"))
"C:\\Program Files\\test.dat"

注意 在指定文件路径名时，必须在反斜杠（\）之前再加上一个反斜杠，或者用斜杠（/）作为目录分隔符，这样 AutoLISP 才能识别指定的路径名。

getfiled 函数，用于通过中望CAD 的标准对话框来选择文件。调用的格式如下：

(getfiled 标题 默认文件名 扩展名 行为模式)

“标题”参数指定弹出对话框的标题；“默认文件名”参数指定弹出对话框时展开的缺省目录，若其中仅包含文件名，则弹出对话框展开的缺省目录为“桌面”，若其中包含完整的路径和文件名，则弹出对话框展开指定的目录；“扩展名”参数指定缺省的文件扩展名，如果为空字符串（""），则缺省值为 *（所有文件类型）；“行为模式”参数是一个标志位，用于设置对话框的行为，具体可参见《LISP 函数参考》中 getfiled 主题。如果对话框中包含用户指定的文件名，则 getfiled 返回指定文件名的字符串，否则该函数返回 nil。下面的示例代码展示了该函数的用法：

命令：(getfiled "选择文件" "C:\\Program Files\\test.dat" "dat" 0)
选择文件对话框

读写文件

在 AutoLISP 程序中读写文件前，必须调用 open 函数打开一个已存在的文件，调用的格式如下：

(open 文件名 模式)

“文件名”参数指定需要打开的文件，一般传递 findfile 或 getfiled 函数的返回值；“模式”参数指定下一步的文件操作模式（只读、写入或追加）。若指定的文件存在，且指定的操作模式有效，该函数返回一个文件描述符，如下面示例代码所示：

(setq filename (findfile "zwcad.lsp"))
(if (null filename)
  (princ "\n未找到指定文件。")
  (setq fileId (open filename "a"))
)

上面代码以追加的方式打开中望CAD 搜索路径中的 zwcad.lsp 文件，上面代码的返回值为：

<File: #785B7428>

在“基本输出函数”主题中介绍了 princ、prin1 和 print 函数可以向中望CAD 命令行输出信息，这些函数的调用格式如下（以 princ 函数为例）：

(princ [字符串 [文件描述符]])

若指定了“文件描述符”参数指定要写入的文件，便可将信息写入指定的文件中。执行以下示例代码，将 (princ "\nHello, ZWCAD.”) 写入中望CAD 搜索路径中的 zwcad.lsp 文件中：

命令：(princ "\n(princ \"\\nHello, ZWCAD\")" fileId)
"\n(princ \"\\nHello, ZWCAD\")"
命令：(close fileId)

由于 zwcad.lsp 文件是自动加载的 LISP 文件，因此运行以上代码后，再次启动中望CAD 应用程序时将在命令行显示“Hello, ZWCAD”信息。

write-line 函数也可以将信息写入指定文件中，调用的格式如下：

(write-line 字符串 [文件描述符])

“字符串”参数指定需要写入的字符串；“文件描述符”参数指定要写入的文件（若未传递“文件描述符”参数或该参数为 nil，该函数将向中望CAD 命令行输出信息）。与 princ、prin1 和 print 函数不同的是，write-line 函数在将一个字符串写入指定文件后，会自动在字符串后添加一个换行符，以下示例代码与上面示例代码的功能相同：

命令：(write-line "(princ \"\\nHello, ZWCAD\")" fileId)
"(princ \"\\nHello, ZWCAD\")"
命令：(close fileId)

read-line 函数可以从指定文件读取一行文本，调用格式如下：

(read-line [文件描述符])

“文件描述符”参数指定要读取的文件。多次执行 read-line 函数可以逐行返回指定文件的内容，若到达文件尾，则返回 nil。下面的示例代码从中望CAD 搜索路径中的 zwcad.lsp 文件中读取以上示例代码写入的字符串：

命令：(read-line fileId)
"(princ \"\\nHello, ZWCAD\")"

下面的示例代码中定义了一个函数，用于向中望CAD 搜索路径中的 zwcad.lsp 文件中添加文本（程序首先遍历文件，若文本已存在，则不添加）：

(defun AddText (string / IsExist filename fileId txttemp)
  (setq IsExist 0)
  (setq filename (findfile "zwcad.lsp"))
  (if (null filename)
    (princ "\n未找到指定文件。")
    (progn
      (setq fileId (open filename "r"))
      (if (null fileId)
    (princ "\n打开文件失败。")
    (while (setq txttemp (read-line fileId))   ;;;遍历文件内容，查找要添加的字符串是否已存在
      (if (= txttemp string)
        (setq IsExist 1)
      )
    )
      )
    )
  )
  (close fileId)
  (if (= IsExist 1)
    (princ "\n该字符串已存在。")
    (progn
      (setq fileId (open filename "a"))
      (if (null fileId)
    (princ "\n打开文件失败。")
    (progn
      (write-line string fileId)   ;;;将字符串写入文件
      (close fileId)
    )
      )
    )
  )
  (princ)
)

注意 在进行文件读写操作时，调用 open 函数打开某文件后，确定不再需要操作该文件时，务必显式调用 close 函数关闭该文件，否则可能导致预期内容未正确写入该文件，该函数接受一个参数，是一个文件描述符。


图元处理函数

中望CAD 提供了 entsel、nentsel、nentselp、entnext、handent、entget、entdel、entmod、entmake 和 entupd 等 12 种 AutoLISP 图元处理函数，可用于选择图元、遍历图元、获取图元 dxf 数据、删除图元、更新图元、创建图元等，该类函数的详细列表请参见附录中“图元处理函数列表”主题。

获取图元名

为了对某图元进行操作（如修改图元颜色、线型、图层等），必须先获取它的图元名。AutoLISP 提供了两种获取图元名的方式：

以交互方式获取图元名

AutoLISP 提供了 entsel 和 nentsel 函数用于以交互的方式获取图元名，它们都要求用户通过在绘图区交互地拾取点来选择单个图元，因此无法获取在绘图区不可见或位于冻结图层上的图元。

entsel 函数只能用于获取简单图元或复杂图元（块、老式多段线和三维多段线等）主图元的图元名，调用的格式如下：

(entsel [提示])

“提示”参数用于指定在命令行输出的提示信息，若省略该参数，程序暂时等待用户选择图元时命令行提示“选择对象:”。若在该函数调用前调用了 initget 函数设置了关键字列表，在用户选择图元前可以键入指定的关键字执行其它操作。entsel 函数的返回值是一个关联表，如下所示：

(<Entity name: 6140140> (218.919 109.551 0.000000))

关联表中第一个元素为所选图元的图元名，可以再次对该返回值调用 car 函数获取；第二个元素为选择图元时拾取点位置，可以再次对该返回值调用 cadr 函数获取，一些图元操作（中望CAD 命令 break、trim 和 extend 等）需要该信息。

nentsel 函数常用于获取复杂图元（块、老式多段线和三维多段线等）中的各子图元的图元名。调用该函数的格式与 entsel 函数调用相同，它也可接受关键字输入。nentsel 函数的返回值也是一个关联表，下面是选择一个嵌套块中某图元后的返回值：

(<Entity name: 8018160>
(5.23647 2.88034 0.000000)
((1.00000 0.000000 0.000000) (0.000000 1.00000 0.000000) (0.000000 0.000000 1.00000) (3.20364 2.88034 0.000000))
((<Entity name: 8038230>
<Entity name: 80312e0>
<Entity name: 8012030>))

关联表中第一个元素为所选子图元的图元名；第二个元素为选择对象时拾取点坐标；第三个元素是一个 4×3 的矩阵，用于坐标转换（在下文“矩阵变换”小节介绍），其最后一个元素为当前 UCS 下块的插入点坐标；第四个元素为一个表，包含该对象所在的各级嵌套块的图元名（从最内层到最外层）。

通过指定参数获取图元名

可能在某些情况下用户希望在程序中静默地获取特定图元的图元名，如获取最新绘制的图元的图元名、依次获取块中各子图元的图元名、获取某点（该点可能是程序计算出来的）附近的图元的图元名等。AutoLISP 提供了 nentselp、entlast 和 entnext 函数实现这些功能，它们弥补了 entsel 和 nentsel 函数无法获取不可见图元和位于冻结图层上图元的缺陷。

entlast 函数用于获取最后一个添加到图纸中的图元（简单图元或复杂图元的主图元）的图元名，无需任何参数。

entnext 函数用于获取图形数据库中指定图元的下一个图元的图元名，调用的格式如下：

(entnext [图元名])

“图元名”参数用于指定遍历的起点，若未提供该参数，则返回添加到图形数据库中的第一个图元的图元名，若指定图元后无任何图元，该函数返回 nil。以下示例代码演示了如何遍历模型空间的所有图元：

(setq curent (entnext))   ;;;获取图形数据中第一个图元
(while curent
  ;;;其它图元处理表达式
  (setq curent (entnext curent))
)

handent 函数根据图元句柄返回图元名。在每次打开图纸时，中望CAD 都为图形数据库中的各图元重新分配图元名，但各图元的句柄始终与图元被添加到图形数据库时一致，因此可以通过句柄实现对图元的绝对引用。

nentselp 函数与 nentsel 函数类似，也用于获取复杂图元中各子图元的图元名，可通过用户交互或非交互方式选取图元，调用的格式如下：

(nentselp [提示] [点])

“点”参数为鼠标拾取点的坐标，是一个三维或二维点表，若未提供该参数，则以交互的方式提示用户选择图元。该函数也可接受关键字输入。nentselp 函数的返回值也是一个关联表，下面是选择一个嵌套块中某图元后的返回值：

(<Entity name: 7fec6f0>
(304.018 142.767 0.000000)
((1.00000 0.000000 0.000000 282.205) (0.000000 1.00000 0.000000 160.614) (0.000000 0.000000 1.00000 0.000000) (0.000000 0.000000 0.000000 1.00000))
(<Entity name: 7fdc8d0>
<Entity name: 7fee140>
<Entity name: 7fdfe00>))

和 nentsel 函数的返回值类似，关联表中第一个元素为所选子图元的图元名；第二个元素为选择对象时拾取点坐标；第三个元素是一个 4×4 的矩阵，也用于坐标转换（在下文“矩阵变换”小节介绍）；第四个元素为一个表，包含该对象所在的各级嵌套块的图元名（从最内层到最外层）。

删除图元

entdel 函数可从绘图区删除指定图元，包括不可见图元和执行 erase 命令无法删除的图元，调用的格式如下：

(entdel 图元名)

注意 该函数只能删除简单图元或复杂图元的主图元。若再次对相同图元调用该函数，可以在绘图区恢复该图元。

获取图元数据

entget 函数以表的形式返回指定图元的 dxf 数据，调用的格式如下：

(entget 图元名 [应用程序名称表])

“图元名”参数指定要查询的图元的图元名，可以是图元或符号表；“应用程序名称表”指定要查询的扩展数据所属应用程序的名称。调用该函数查询某个直线图元的 dxf 数据返回值如下：

((-1 . <Entity name: 7fe5890>) (0 . "LINE") (5 . "12F") (67 . 0) (8 . "0") (410 . "Model") (62 . 256) (6 . "ByLayer") (370 . -1) (48 . 1.00000) (60 . 0) (39 . 0.000000) (10 12.7434 5.04827 0.000000) (11 17.5946 6.80717 0.000000) (210 0.000000 0.000000 1.00000))

返回值为一个关联表，表中的每个元素又是表或点对，且以 dxf 组码作为每个子表或点对的第一个元素。例如，上面返回值中 -1 组码表示图元名，0 组码表示图元类型，5 组码表示图元的句柄等等，各个 dxf 组码的含义请参见《DXF 参考》。以下示例代码定义了一个命令 listen 调用 prtdxf 函数，用于查询所选图元的 dxf 数据：

(defun c:listen (/ pickdxf entname pt entdxf enttype op)
  (setq pickdxf (entsel "\n选择对象："))
  (setq entname (car pickdxf))
  (setq pt (cadr pickdxf))
  (setq entdxf (entget entname))
  (setq enttype (cdr (assoc 0 entdxf)))
  (if (or (= enttype "INSERT") (= enttype "POLYLINE"))     ;;;若所选图元为复杂图元（块或三维多段线）
    (progn
      (initget "Main Sub")             ;;;定义关键字列表，确定打印主图元数据还是子图元数据
      (setq op (getkword "\n选择主图元(M)/<子图元(S)>："))
      (if (or (= op "Sub") (null op))
    (prtdxf (car (nentselp pt)))      ;;;获取子图元名
    (prtdxf entname)
      )
    )
    (prtdxf entname)      ;;;若所选图元为简单图元
  )
  (princ)
)

(defun prtdxf (entname / dxflist elementnum i)        ;;;接受一个参数（图元名），打印图元 dxf 数据，每行打印一个 dxf 组码及其值
  (terpri)
  (setq dxflist (entget entname))
  (setq elementnum (length dxflist))
  (setq i 0)
  (while (< i elementnum)
    (princ (nth i dxflist))
    (princ "\n")
    (setq i (1+ i))
  )
)

修改图元

entmod 函数可用于修改图元（简单图元、复杂图元主图元或复杂图元子图元）、符号表，调用的格式如下：

(entmod dxf表)

“dxf表”参数即图元 dxf 数据，与 entget 函数返回值相同。一般修改图元的方法是，先调用 entget 函数获取图元 dxf 数据，然后调用 subst 函数修改 dxf 表中的元素，然后调用 entmod 函数将修改后的 dxf 数据传回到图形数据库中。对于简单图元、复杂图元的主图元，当修改后的 dxf 数据传回图形数据库后就可以自动更新图元的屏幕显示，但是对于复杂图元中的子图元，在调用 entmod 函数传回修改后的子图元 dxf 数据后，还必须调用 entupd 函数更新主图元，才能更新图元的屏幕显示。entupd 函数的调用格式如下：

(entupd 图元名)

“图元名”参数为需要更新屏幕显示的复杂图元的主图元名。有些复杂图元在调用 entupd 函数后可能仍不能更新屏幕显示，如嵌套块，此时需要调用 command 函数执行 regen 命令。以下示例代码定义了 chgltype 命令用于修改一个图元的线型：

(defun c:chgltype (/ filedia_old entname dxflist)
  (setq filedia_old (getvar "filedia"))
  (if (tblsearch "ltype" "center")    ;;;判断“center”线型是否已加载
    (princ "\n线型已加载。")
    (progn
      (setvar "filedia" 0)    ;;;阻止加载线型时弹出对话框
      (command "linetype" "l" "center" "zwcadiso" "")   ;;;加载“center”线型
    )
  )
  (setq entname (car (entsel "\n选择要改变线型的图元：")))
  (setq dxflist (entget entname))     ;;;获取图元 dxf 数据
  (setq dxflist (subst (cons 6 "center") (assoc 6 dxflist) dxflist))   ;;;修改图元 dxf 数据，组码 6 表示线型名
  (entmod dxflist)    ;;;将修改后的 dxf 数据传回图形数据库
  (setvar "filedia" filedia_old)   ;;;恢复系统变量的值
  (princ)
)

entmod 函数在修改图元的某些涉及符号表记录的属性时，如线型、文字样式、标注样式等，请确保对这些属性的赋值有效，如修改图元的线型时，请确保该线型已加载等，否则无法修改图元的相应属性。其中，图元的图层属性虽然也是涉及符号表记录的属性，但它是一个例外，在修改图元的图层时，无需事先检测该图层是否存在，若该图层不存在，中望CAD 会在改变图元的图层属性前自动创建该图层。

创建新图元

前面已经介绍过向图形数据库添加图元的一种方法，即调用 command 函数执行中望CAD 绘图命令绘制图元，但这是一种效率不高的方式，因为它需要先解析字符串，然后再调用中望CAD 命令，最后才将数据写入图形数据库，除此之外，这种方式还无法创建带有扩展数据的图元。这里我们介绍一种相对高效的向图形数据库添加新图元的方法，并且可以创建带扩展数据的图元（在下文“扩展数据”小节介绍），即调用 entmake 函数，调用的格式如下：

(entmake [dxf表])

“dxf表”参数即图元 dxf 数据，与 entget 函数返回值相同，但它可以是一个不完整的表，但必须符合以下规则：

  • 表的第一个元素必须指定图元类型，即表的第一个元素必须是由组码 0 及图元类型构成的点对

  • 对各个组码的赋值必须有效（类似于 entmod 函数）

下面分别以实例介绍如何调用 entmake 函数创建简单图元（以圆为例）和复杂图元（以无名块为例）：

  • 创建简单图元

    下面的示例代码创建一个圆心（组码 10）位于 (100.0 200.0 0.0) 点，半径（组码 40）为 50.0，线型（组码 6）为“center”，颜色（组码 62）为红色的圆 （假定已加载 center 线型）：

    • 命令：(entmake '((0 . "circle") (10 100.0 200.0 0.0) (40 . 50) (6 . "center") (62 . 1)))

  • 创建复杂图元

    中望CAD 的 block 命令无法创建无名块，下面的示例代码定义了 MakeUnameBlk 命令用于创建无名块，操作方式与 block 命令类似：

    (defun c:MakeUnameBlk ()
  (prompt "\n选择需要包含到块中的图元：")
  (setq sset (ssget))
  (setq entnum (sslength sset))
  (setq i 0)
  (setq basept (getpoint "\n指定基点："))
  (entmake      ;;;创建块表的表头
    (list '(0 . "BLOCK") '(2 . "*U") '(70 . 1) (cons 10 basept))
  )
  (while (< i entnum)    ;;;创建块表中包含的图元
    (entmake (entget (ssname sset i)))
    (setq i (1+ i))
  )
  (entmake '((0 . "ENDBLK")))     ;;;创建块表的表尾
  (while (setq blockdxf (tblnext "block"))   ;;;遍历块表找到刚创建的块表记录
    (setq myblock (assoc 2 blockdxf))
  )
  (entmake (list '(0 . "INSERT") myblock (cons 10 basept)))   ;;;插入块
  (command "erase" sset "")    ;;;删除已包含到无名块中的图元
)

    创建复杂图元时，需多次调用 entmake 函数创建每个子图元。首先调用 entmake 函数创建复杂图元的头部（如块表的表头），它创建一个临时文件，用于收集接下来的定义数据；然后每次调用 entmake 函数创建各子图元，在将子图元数据添加到图形数据库之前，entmake 函数都先检查临时文件是否存在，若存在则向文件中增加新的子图元，若不存在则创建简单图元；最后调用 entmake 函数创建复杂图元的尾部（即正确创建 seqend 或 entblk 子图元）并检查复杂图元头部和尾部的一致性，如合法，则将复杂图元显示到屏幕或添加到符号表中。当复杂图元创建完毕时，所创建的临时文件将自动被删除，用户也可在复杂图元创建过程中显式不带参数调用 entmake 函数删除该临时文件以取消复杂图元的创建。

矩阵变换

nentsel 和 nentselp 函数返回的关联表中第一个元素为鼠标选取的子图元的图元名，调用 entget 函数获取它的 dxf 数据都是 MCS （模型坐标系）中的，MCS 坐标系的原点是包含所选图元的块的插入点，而 MCS 轴的方向则由创建该块时 UCS 的方向决定；第三个元素分别是一个 4×3 和 4×4 的矩阵，其中记录了 MCS 的相关信息，结合该矩阵和块的插入点可以将所选子图元的 dxf 数据转换到 WCS 中。nentsel 和 nentselp 函数的矩阵转换又略有不同，下面分别对这两个函数进行介绍：

nentsel

该函数返回的关联表中第三个元素为一个 4×3 的矩阵，矩阵的前三行指定缩放比例和旋转角度，第四行指定了块的插入点（在 WCS 中），通过以下矩阵乘法运算规则，可将子图元的 dxf 数据从 MCS 转换到 WCS 中：

如下示例程序，定义了命令 GetInsPt 函数，它接受两个参数，ename 参数指定块中的单行文字，matrix 参数指定 4×3 的转换矩阵，返回在任意 UCS 下的块插入中的单行文字的插入点坐标：

(defun GetInspt (ename matrix / txtInsPt matrix x y z)
  (setq txtInsPt (cdr (assoc 10 (entget ename))))
  (setq x (+ (* (car (nth 0 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 X 坐标
         (* (car (nth 1 matrix)) (cadr txtInsPt))
         (* (car (nth 2 matrix)) (last txtInsPt))
         (car (nth 3 matrix))
      )
  )
  (setq y (+ (* (cadr (nth 0 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 Y 坐标
         (* (cadr (nth 1 matrix)) (cadr txtInsPt))
         (* (cadr (nth 2 matrix)) (last txtInsPt))
         (cadr (nth 3 matrix))
      )
  )
  (setq z (+ (* (last (nth 0 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 Z 坐标
         (* (last (nth 1 matrix)) (cadr txtInsPt))
         (* (last (nth 2 matrix)) (last txtInsPt))
         (last (nth 3 matrix))
      )
  )
  (setq txtInsPt (list x y z))	;;;构建文字插入点三维点表（WCS 中）
  (trans txtInsPt 0 1)	;;;将三维点由 WCS 转换到 UCS 下
)

nentselp

该函数返回的关联表中第三个元素为一个 4×4 的矩阵，矩阵的前三列指定缩放比例和旋转角度，第四列指定了块的插入点（在 WCS 中），通过以下矩阵乘法运算规则，可将子图元的 dxf 数据从 MCS 转换到 WCS 中：

如下示例程序，定义了命令 GetInsPt 函数，它接受两个参数，ename 参数指定块中的单行文字，matrix 参数指定 4×4 的转换矩阵，返回在任意 UCS 下的块插入中的单行文字的插入点坐标：

(defun GetInspt (ename matrix / txtInsPt matrix x y z)
  (setq txtInsPt (cdr (assoc 10 (entget ename))))
  (setq x (+ (* (car (nth 0 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 X 坐标
         (* (cadr (nth 0 matrix)) (cadr txtInsPt))
         (* (caddr (nth 0 matrix)) (last txtInsPt))
         (last (nth 0 matrix))
      )
  )
  (setq y (+ (* (car (nth 1 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 Y 坐标
         (* (cadr (nth 1 matrix)) (cadr txtInsPt))
         (* (caddr (nth 1 matrix)) (last txtInsPt))
         (last (nth 1 matrix))
      )
  )
  (setq z (+ (* (car (nth 2 matrix)) (car txtInsPt))	;;;通过矩阵转换获取 WCS 下文字插入点的 Z 坐标
         (* (cadr (nth 2 matrix)) (cadr txtInsPt))
         (* (caddr (nth 2 matrix)) (last txtInsPt))
         (last (nth 2 matrix))
      )
  )
  (setq txtInsPt (list x y z))	;;;构建文字插入点三维点表（WCS 中）
  (trans txtInsPt 0 1)	;;;将三维点由 WCS 转换到 UCS 下
)

扩展数据

每个图元都具有一套 dxf 数据，它们主要用于控制图元的屏幕显示。AutoLISP 提供了另外一套 dxf 组码用于定义图元的扩展数据，它们可以在图元的正常 dxf 数据之后附加更多信息但并不影响图元的屏幕显示，例如可以通过为图元附加扩展数据传达不同的专业信息以区分两个外观一致的图元，如两条外观一样的直线，一条表示照明系统的导线，另一条表示动力系统的导线。

在介绍为图元添加扩展数据的步骤之前，我们有必要了解扩展数据的定义方式和组织结构。dxf 组码中 -1、-2、0~239 用于定义图元的正常 dxf 数据，-3、1000~1071 用于定义图元的扩展 dxf 数据，关于各组码的含义请参见《DXF 参考》。以-3 组码作为第一个元素的关联表即用于定义图元的扩展数据，该表一般位于图元的正常 dxf 数据之后，表中每个元素又各是一个子表，分别包含各注册的应用程序名称及相应的扩展数据字段，形式如下所示：

((-1 . <Entity name: 5ea3840<)
(0 . "LINE")
(5 . "2EE")
(67 . 0)
(8 . "0")
(410 . "Model")
(62 . 256)
(6 . "ByLayer")
(370 . -1)
(48 . 1.00000)
(60 . 0)
(39 . 0.000000)
(10 207.466 86.7622 0.000000)
(11 292.240 122.952 0.000000)
(210 0.000000 0.000000 1.00000)    ;;;以上都是正常的 dxf 数据
(-3                                ;;;以下都是扩展数据
("ZWCAD_ELEC"                           ;;;应用程序名称 1
(1000 . "照明导线") (1000 . "铜"))
("ZWCAD_MECH"                            ;;;应用程序名称 2
(1000 . "前大灯") (1070 . 25)))
)

创建带扩展数据的图元或对现有图元附加扩展数据前，必须确保 appid 表中已存在所需的应用程序名称。AutoLISP 提供 regapp 函数注册应用程序名称，调用的格式如下：

(regapp 应用程序名称)

“应用程序名称”可以为任意字符串。若注册成功，该应用程序名称将被保存到 appid 表中，若 appid 表中已存在需注册的应用程序，该函数将不再向 appid 表中添加记录并返回 nil。以下示例程序为所选图元添加扩展数据：

(defun c:addxdata ()
  (if (tblsearch "appid" "ZWCAD_ELEC")      ;;;检测“ZWCAD_ELEC”应用程序是否被注册
    (princ "\n应用程序\"ZWCAD_ELEC\"已被注册。")
    (if (= (regapp "ZWCAD_ELEC") nil)      ;;;注册“ZWCAD_ELEC”应用程序
      (princ "\n注册应用程序失败。")
      (princ)
    )
  )
  (setq entname (car (entsel "\n选择需要附加扩展数据的图元：")))
  (setq dxflist (entget entname))
  (setq   ;;;创建扩展数据 dxf 表，为所选图元添加字符串类型的数据，需描述的专业信息为“铜质的照明导线”
    xdatalist '((-3 ("ZWCAD_ELEC" (1000 . "照明导线") (1000 . "铜"))))
  )
  (setq dxflist (append dxflist xdatalist))
  (entmod dxflist)
  (princ)
)

在中望CAD 中有两种方式可以查询图元的扩展数据：

1.执行中望CAD 内部命令 xdlist 命令选择需要查看扩展数据的图元，该方法仅适用于检查为图元附加扩展数据是否正确。

2.调用 entget 函数查询图元的扩展数据，如下所示：

命令：(entget entname '("ZWCAD_ELEC"))
((-1 . <Entity name: 8a93cb0>) (0 . "LINE") (5 . "412") (67 . 0) (8 . "0") (410 . "Model") (62 . 256) (6 . "ByLayer") (370 . -1) (48 . 1.00000) (60 . 0) (39 . 0.000000) (10 376.978 40.3657 0.000000) (11 459.095 122.482 0.000000) (210 0.000000 0.000000 1.00000) (-3 ("ZWCAD_ELEC" (1000 . "照明导线") (1000 . "铜"))))

若图元含有多个应用程序名称的扩展数据，则可以使用通配符，如 (entget entname (list "*"))，查询所有应用程序的扩展数据。


符号表处理函数

在中望CAD 的图形数据库中包含三种类型的记录：图元（图形对象）、符号表（非图形对象）和词典。本节介绍的符号表包括 appid（应用程序表）、block（块表）、dimstyle（标注样式表）、layer（图层表）、ltype（线型表）、style（文字样式表）、ucs（用户坐标系表）、view（视图表）和 vport（视口表）。每个符号表中包含零条或多条记录，如图层表中至少含有一条记录与图层 0 对应，文字样式表中至少有一条记录与“standard”文字样式对应等。中望CAD 提供了 tblnext、tblobjname 和 tblsearch 等 AutoLISP 函数专用于处理符号表，该类函数的详细列表请参见附录中的“符号表处理函数列表”主题。前面介绍过的图元处理函数中的 entget、entmake、entmod 和 handent 函数也可用于处理符号表。本节将以实例介绍如何遍历符号表、如何获取符号表中条目的数据。

遍历符号表

tblnext 函数用于遍历指定的符号表，调用的格式如下：

(tblnext 符号表名 [起点])

“符号表名”参数用于指定需要遍历的符号表，有效的符号表名称包括 appid（应用程序表）、block（块表）、dimstyle（标注样式表）、layer（图层表）、ltype（线型表）、style（文字样式表）、ucs（用户坐标系表）、view（视图表）和 vport（视口表）；若未提供“起点”参数或该参数为 nil，遍历的起点取决于 tblsearch 函数是否重新设置了遍历起点；若提供了“起点”参数为非 nil 值，则将从指定符号表的第一个条目开始遍历。

tblsearch 函数用于在指定符号表中查找特定条目，调用的格式如下：

(tblsearch 符号表名 项目名 [设置起点])

若设置了“设置起点”参数为非 nil 值，以后再省略“起点”参数调用 tblnext 函数遍历该符号表时，将从“项目名”参数指定的条目的下一个条目开始遍历。

获取符号表中条目的数据

tblnext 和 tblsearch 函数的返回值都是 dxf 表，与 entget 函数的返回值类似，但没有后者详细，通常情况下需要先调用 tblobjname 函数获取符号表条目的图元名，然后再调用 entget 函数获取详细的 dxf 数据。下面的示例代码定义了 statsColor 命令用于统计整个图形数据库的图元使用到的颜色种类，并对使用各个颜色的图元的数量进行统计，当图元的颜色为 bylayer 时需要获取图层表中相应图层条目的数据：

(defun c:statsColor (/ colorstatslist colorstatslist_1
             sset entnum i j entname
             dxflist  entcolor layername layercolor
             colornum string curcolor entqty
            )
  (setq colorstatslist nil)
  (setq	sset (ssget "X"       ;;;滤除标注和块图元
            '((60 . 0)
              (-4 . "<not")
              (-4 . "<or")
              (0 . "DIMENSION")
              (0 . "INSERT")
              (-4 . "or>")
              (-4 . "not>")
             )
         )
  )
  (setq entnum (sslength sset))
  (setq i 0)
  (while (< i entnum)
    (setq entname (ssname sset i))
    (setq dxflist (entget entname))
    (setq entcolor (cdr (assoc 62 dxflist)))
    (if (or (null entcolor) (= entcolor 256))
      (progn       ;;;图元颜色为 bylayer
         (setq layername (cdr (assoc 8 dxflist)))
         (setq layercolor
            (cdr (assoc 62 (tblsearch "layer" layername)))   ;;;获取图元所在图层的数据
         )
         (setq entcolor layercolor)
      )
    )
    (setq colorstatslist (append colorstatslist (list entcolor)))  ;;;将所有使用到的颜色值存储到 colorstatslist 表中
    (setq i (1+ i))
  )
  (setq colorstatslist_1 (vl-sort colorstatslist '<))  ;;;滤除存储在 colorstatslist 表中的相同颜色并存储到  colorstatslist_1 表中
  (setq colornum (length colorstatslist_1))
  (princ (strcat "\n图纸中的图元共使用了 " (itoa colornum) " 种颜色。"))
  (prompt "\n使用各种颜色的图元数量如下表所示：")    ;;;开始遍历 colorstatslist 和 colorstatslist_1 表，进行统计
  (setq j 0)
  (setq string "\n颜色\t图元数\n")
  (while (< j colornum)
    (setq entqty 0)
    (setq i 0)
    (setq curcolor (nth j colorstatslist_1))
    (while (< i entnum)
      (if (= (nth i colorstatslist) curcolor)
    (setq entqty (1+ entqty))
      )
      (setq i (1+ i))
    )
    (setq
      string (strcat string (itoa curcolor) "\t\t" (itoa entqty) "\n")
    )
    (setq j (1+ j))
  )
  (setq sset nil)
  (princ string)
  (princ)
)


选择集处理函数

中望CAD 提供了ssadd、ssdel、ssget、sslength 和 ssname 等 9 种 AutoLISP 选择集处理函数，该类函数的详细列表请参见附录中“选择集处理函数列表”主题，本节以实例说明 ssget 函数的用法。

选择模式

执行中望CAD 命令 select 选择对象时提供了众多选择模式，如全部(ALL)/增添(A)/除去(R)/前次(P)/上次(L)/窗口(W)/相交(C)/外部(O)/多边形窗口(WP)/相交多边形(CP)/外部多边形(OP)/圆形窗口(WC)/相交圆形(CC)/外部圆形(OC)/方形(B)/点(PO)/围栏(F)/自动(AU)/多次(M)/单个(S)/特性(PRO)/对话框(D)。ssget 函数作为 AutoLISP 程序中最基本的创建选择集的方法，调用的格式如下：

(ssget [模式] [点1 [点2]] [点表] [过滤表])

设置不同的“模式”参数可以实现 select 命令提供的部分选择模式，“模式”参数的有效值及其选择效果如下表所示：

模式参数 | 选择效果
省略 | 类似于 select 命令中的“自动(AU)”，若无“点1”或“点2”参数，则给出“选择对象”提示，允许用户多次交互式地构造选择集；若提供了“点1”或“点2”参数，则将指定点处的图元加入选择集中
C | 类似于 select 命令中的“相交(C)”，需提供参数“点1”和“点2”，全部或部分位于“点1”和“点2”构成的矩形区域内的图元都会被添加到选择集中
CP | 类似于 select 命令中的“相交多边形(CP)”，需要提供参数“点表”，全部或部分位于“点表”所围成的多边形区域内的图元都会被添加到选择集中
F | 类似于 select 命令中的“围栏(F)”，需要提供参数“点表”，与“点表”中各点的顺序连线相交的图元都会被添加到选择集中
I | 隐含窗口选择（PICKFIRST 系统变量为 1 时），适用于先选择图元，再执行命令
L | 类似于 select 命令中的“上次(L)”，将最新绘制的图元添加到选择集中
P | 类似于 select 命令中的“前次(P)”，获取上一个选择集
W | 类似于 select 命令中的“窗口(W)”，需提供参数“点1”和“点2”，仅当图元完全位于“点1”和“点2”构成的矩形区域内时才被添加到选择集中
WP | 类似于 select 命令中的“多边形窗口(WP)”，需提供参数“点表”，仅当图元完全位于“点表”所围成的多边形区域内才被添加到选择集中
X | 类似于 select 命令中的“全部(ALL)”，将图形数据库中的所有图元（包括位于关闭图层、冻结图层上的图元和隐藏的图元）添加到选择集中
:S | 仅允许通过光标的对象选择拾取框选择图元，类似于 entsel 函数

虽然“模式”、“点1”、“点2”、“点表”和“过滤表”都是可选参数，但它们之间存在依赖关系，如上表所述，当“模式”参数为 C 或 W 时，需要提供“点1”和“点2”参数；当“模式”参数为 F、CP 或 WP 时，需要提供“点表”参数；当“模式”参数为 I、L、P 或 X 时，无需提供“点1”或“点2”或“点表”参数。而“过滤表”参数对于以上任一选择模式都适用，使用过滤表选择对象类似于中望CAD 的 qselect 命令。

选择集过滤表

通过设置选择模式可以划定选择范围，但是绘图区中可能各种类型的图元混杂排列，如果我们只需要将某一个区域内的指定类型的图元添加到选择集中，则需要用过滤表来对该区域内的图元再次进行过滤。过滤表是一个 dxf 表，它的格式类似于 entget 函数的返回值，若其中含有图元名（组码 -1）、句柄（组码 5）和扩展数据（任何大于 1000 的组码），将会自动忽略，即 ssget 函数无法选择具有特定图元名、句柄和扩展数据的图元。

下面示例代码演示了几种通过比较单一的过滤条件创建选择集的方法：

示例代码 | 选择效果
(ssget '((0 . "LINE"))) | 提示用户多次交互式框选或窗选图元，但只将选择区域内的直线图元添加到选择集中
(ssget "X" '((0 . "LINE") (62 . 1))) | 将整个图形数据库中的颜色为红色的直线图元添加到选择集中

虽然不可使用扩展数据作为过滤条件，但可以用扩展数据所属的应用程序名称作为过滤条件，例如下面示例代码将整个图形数据库包含“ZWCAD_ELEC”应用程序名称的直线图元添加到选择集中：

命令：(ssget "X" '((0 . "LINE") (-3 ("ZWCAD_ELEC"))))

如果需要将包含两个应用程序名称（“ZWCAD_ELEC”和“ZWCAD_MECH”）的图元添加到选择集中，则需要按如下方式来调用 ssget 函数：

命令：(ssget "X" '((0 . "LINE") (-3 ("ZWCAD_ELEC") ("ZWCAD_MECH"))))

如果需要将包含两个应用程序名称中任一个（“ZWCAD_ELEC”或“ZWCAD_MECH”）的图元添加到选择集中，则需要按如下方式来调用 ssget 函数：

命令：(ssget "X" '((0 . "LINE") (-3 ("ZWCAD_ELEC, ZWCAD_MECH"))))

在过滤表中可以使用通配符、关系运算符或逻辑运算符来设置更复杂多样的过滤条件。

  • 在过滤表中使用通配符

    在过滤表中使用通配符与在 wcmatch 函数中使用通配符类似，如下面的示例代码用于将图形数据库中所有文字内容（组码 1）以“门”开头后接一位数字的单行文字或多行文字添加到选择集中：

    命令：(ssget "X" '((1 . "门[0-9]")))

    需引起注意的是，无名块的块名的形式为“*Unnn”，其中的星号（*）可用作通配符（表示任意多个字符），如何将块名为“*U1”的无名块添加到选择集中呢？如下所示：

    命令：(ssget "X" '((2 . "\*U1")))

    如上面示例代码所示，在通过块名过滤无名块时，必须在块名的星号（*）前添加一个反斜杠（\）屏蔽星号通配符。这里只是演示通过反斜杠（\）来屏蔽星号（*）的通配符作用，其实使用无名块的块名来作为过滤条件没有什么实际意义，因为在每次编辑任务时，图纸中的无名块的块名可能会变化，这里只是演示在过滤表中如何使用反斜杠屏蔽 * 通配符。

  • 在过滤表中使用关系运算符

    可在过滤表中使用的关系运算符包括：*（恒等）、=（等于）、!=（不等于）、/=（不等于）、<>（不等于）、<（小于）、<=（小于或等于）、>（大于）、>=（大于或等于）、&（按位与）和&=（按位异或）。若没有特别指定，对过滤表中的每一个元素都使用相等进行测试。在过滤表中必须将组码 -4 与上述各关系运算符的字符串形式构成点对。 例如，下面的示例代码将整个图形数据库中可见（组码 60）的半径（组码 40）大于 10.0 的圆图元添加到选择集中：

    命令：(ssget "X" '((0 . "CIRCLE") (60 . 0) (-4 . ">") (40 . 10.0)))

    若需要检测的是图元的特征点（组码 10 和 11），因为它们是三维点或二维点，可以将多个关系运算符组合成字符串对点表的各个元素进行检测，例如，下面的示例代码将选取整个图形数据库中带有三维信息的直线（起点和终点的 Z 坐标非零）：

    命令：(ssget "X" '((0 . "LINE") (-4 . "*,*,/=") (10 0.0 0.0 0.0) (-4 . "*,*,/=") (11 0.0 0.0 0.0)))

  • 在过滤表中使用逻辑运算符

    在过滤表中使用的逻辑运算符必须配对出现，和 AutoLISP 提供的逻辑运算函数一样，过滤表中的各组逻辑运算符的操作数也必须是返回值为真或假的表达式，可在过滤表中使用的逻辑运算符包括：“<and”（逻辑与）、“<or”（逻辑或）、“<xor”（逻辑异或）和“<not”（逻辑非），对应的结束运算符分别为：“and>”、“or>”、“xor>”和“not>”。若没有特别指定，对过滤表中的每一个元素都使用“<and”和“and>”进行测试。在过滤表中逻辑运算符也必须以字符串形式和组码 -4 构成点对使用。 例如，下面的示例代码将整个图形数据库中的直线图元和圆图元添加到选择集中：

    命令：(ssget "X" '((-4 . "<or") (0 . "LINE") (0 . "CIRCLE") (-4 . "or>")))

    将包含两个应用程序名称中任一个（“ZWCAD_ELEC”或“ZWCAD_MECH”）的图元添加到选择集中，也可以通过在过滤表中使用逻辑运算符来实现，如下所示：

    命令：(ssget "X" '((0 . "LINE") (-4 . "<or") (-3 ("ZWCAD_ELEC")) (-3 ("ZWCAD_MECH")) (-4 . "or>")))

选择集操作

本节主要介绍如何将图元添加到已创建的选择集中、如何将某图元从选择集中删除、如何释放选择集。

ssadd 函数用于将指定图元添加到指定选择集中，调用的格式如下：

(ssadd [图元名 [选择集]])

若未提供“图元名”参数和“选择集”参数调用 ssadd 函数将创建一个空选择集，这与 ssget 函数不同，虽然 ssget 函数也用于创建选择集，但若指定区域内没有满足条件的图元，ssget 函数将返回 nil，并不创建选择集。

ssdel 函数用于将指定图元从指定选择集中删除，调用的格式如下：

(ssdel 图元名 选择集)

若指定图元并不在该选择集中，则该函数返回 nil，但不报告错误。

释放选择集是一个重要的话题，但又经常容易被忽视。选择集会消耗中望CAD 的临时文件存储区，所以 AutoLISP 不允许同时打开多于 128 个选择集。如果选择集的数目达到了这个极限，中望CAD 会拒绝再创建新的选择集，以后调用 ssget 函数只会返回 nil。建议及时释放不再需要的选择集，可以通过将选择集设为 nil 来达到该目的，如下所示：

命令：(setq sset nil)


词典处理函数

词典是一个容器对象，在功能上相当于符号表。每个词典条目都由一个文本名称关键字和一个固定的句柄（所在的词典对象的句柄）组成。AutoLISP 提供了 dictsearch 和 dictnext 函数用于对词典中的各关键字进行查询，dictadd 和 entmakex 用于添加词典和向词典中添加记录，该类函数的详细列表请参见附录中的“词典处理函数列表”主题。

读取词典数据（访问编组）

图纸中的编组信息就是存储在词典中的，词典名称为“ACAD_GROUP”，每个编组的组名作为该词典下的一个关键字，每个关键字下存储了“ACAD_GROUP”词典对象的句柄和编组中的各个图元的句柄。以下示例代码定义了 chggpcolor 函数用于将指定组内的图元的颜色修改为指定颜色：

(defun chggpcolor (gpname color / gpdict gprecord gpdesc dxflist)
  (setq gpdict (cdar (dictsearch (namedobjdict) "ACAD_GROUP")))	;;;获取“ACAD_GROUP”词典
  (if (null (setq gprecord (dictsearch gpdict gpname)))	;;;获取 gpname 编组所在的词典条目
    (progn
      (princ (strcat "\n" gpname " 编组不存在。"))
      (exit)
    )
    (progn
      (setq gpdesc (cdr (assoc 300 gprecord)))
      (setq i 0)
      (repeat (length gprecord)		;;;遍历 gpname 编组所在词典条目中的数据
    (if (= (car (nth i gprecord)) 340)	;;;编码 340 表示为该编组中的图元
      (progn
        (setq dxflist (entget (cdr (nth i gprecord))))
        (setq dxflist
           (subst (cons 62 color) (assoc 62 dxflist) dxflist)
        )
        (entmod dxflist)
      )
    )
    (setq i (1+ i))
      )
      (princ (strcat "\n关于 " gpdesc " 的编组中的所有图元的颜色已被修改。"))
    )
  )
  (princ)
)

打开一张含有 test 编组的图纸，在中望CAD 命令行输入 (chggpcolor "test" 1)，则 test 编组内的所有图元的颜色都被更改为红色。

向词典中写入数据

将诸如产品名称、材料和生产单位等非图元信息写入词典中，对于 PDM 提取图纸信息非常有用。下面的示例代码定义了 writeprodinfo 函数用于将产品名称、材料和生产单位等信息写入词典中：

(defun writeprodinfo (size material weight factory address phonenum contactPerson
              /	productdict Dict XRecord xname prodinfoXrcd factinfoXrcd
             )
  (setq productdict (cdar (dictsearch (namedobjdict) "ZWCAD_PRODUCT")))
  (if (null productdict)	;;;判断 ZWCAD_PRODUCT 词典是否存在
    (progn
      (setq Dict
         (list '(0 . "DICTIONARY") '(100 . "AcDbDictionary"))
      )
      (setq xname (entmakex Dict))	;;;创建 ZWCAD_PRODUCT 词典
      (setq productdict
         (dictadd (namedobjdict) "ZWCAD_PRODUCT" xname)	;;;将 ZWCAD_PRODUCT 词典附着到图形数据库
      )
    )
  )
  (setq prodinfoXrcd (dictsearch productdict "PRODUCT_INFOMATION"))
  (if (null prodinfoXrcd)	;;;判断 PRODUCT_INFOMATION 扩展记录是否存在
    (progn
      (setq XRecord
         (append (list '(0 . "XRECORD") '(100 . "AcDbXrecord"))
             (list '(300 . "SIZE")
               (cons 300 size)
               '(300 . "MATERIAL")
               (cons 300 material)
               '(300 . "WEIGHT")
               (cons 300 weight)
             )
         )
      )
      (setq xname (entmakex XRecord))	;;;创建 PRODUCT_INFOMATION 扩展记录
      (dictadd productdict "PRODUCT_INFOMATION" xname)	;;;将 PRODUCT_INFOMATION 扩展记录附着到 ZWCAD_PRODUCT 词典
    )
  )
  (setq factinfoXrcd (dictsearch productdict "FACTORY_INFOMATION"))
  (if (null factinfoXrcd)	;;;判断 FACTORY_INFOMATION 扩展记录是否存在
    (progn
      (setq XRecord
         (append (list '(0 . "XRECORD") '(100 . "AcDbXrecord"))
             (list '(300 . "FACTORYNAME")
               (cons 300 factory)
               '(300 . "ADDRESS")
               (cons 300 address)
               '(300 . "PHONENUMBER")
               (cons 300 phonenum)
               '(300 . "CONTACTPERSON")
               (cons 300 contactPerson)
             )
         )
      )
      (setq xname (entmakex XRecord))	;;;创建 FACTORY_INFOMATION 扩展记录
      (dictadd productdict "FACTORY_INFOMATION" xname)	;;;将 FACTORY_INFOMATION 扩展记录附着到 ZWCAD_PRODUCT 词典
    )
  )
  (princ)
)

在中望CAD 中任意新建或打开一张图纸，在命令行输入 (writeprodinfo "12cm*30cm*45cm" "铜" "158.6kg" "广州中望龙腾软件股份有限公司" "广州市天河区珠江西路15号珠江城32层" "020-38289780" "张三")，即可将图纸相关的产品信息和生产单位信息写入词典中（建议用 OdaMfcApp.exe 软件查看词典数据）。


查询函数

中望CAD 提供了 getenv、getvar、setenv 和 setvar 等 6 种 AutoLISP 查询函数，可用于检查和修改操作系统环境变量、中望CAD 环境变量或中望CAD 系统变量的值。该类函数的详细列表请参见附录中“查询函数列表”主题。

检查和设置系统变量

setvar 函数按系统变量的类型指定其值，中望CAD 系统变量有几种类型：整数、实数、字符串、二维点和三维点，有些系统变量是保存在注册表中的，有些则仅在当前编辑任务中有效，一旦重启中望CAD 应用程序这些系统变量将被恢复成初始值（如 undoctl、clayer 等）。在编写 LISP 程序的时候往往需要修改一些系统变量为我们的程序服务，在程序结束的时候需要将这些被修改的系统变量恢复到程序运行前的状态，这就需要在修改前先把原来的值保存起来，如下面的代码所示：

(setq oldcecolor (getvar "cecolor"))
(if (/= oldcecolor "1")
  (setvar "cecolor" "1")
)
(setq oldceltype (getvar "celtype"))
(if (/= oldceltype "center")
  (setvar "celtype" "center")
)

上面代码的作用是把当前的新图元颜色和线型分别记录在 oldcecolor 和 oldceltype 变量中，然后设置新图元的颜色为红色，线型为 center（需确保已经加载了 center 线型），类似的保存和设置系统变量的操作一般会在程序的开始进行。

(setvar "cecolor" oldcecolor)
(setvar "celtype" oldceltype)

这两行代码的作用是根据 oldcecolor 和 oldceltype 变量中的值恢复新对象颜色和线型，类似的恢复系统变量的操作一般在程序的最后进行。

检查和设置环境变量

setenv 函数必须用字符串来指定各环境变量的值，设置的中望CAD 环境变量将保存到注册表 HKEY_CURRENT_USER\Software\ZWSoft\ZWCAD\2023\zh-CN\Profiles\Default\Config 中，若所设置的中望CAD 环境变量在注册表中已存在，则修改它的值。

getenv 函数用于获取操作系统或中望CAD 的环境变量，检索顺序是，先在注册表中 HKEY_CURRENT_USER\Software\ZWSoft\ZWCAD\2023\zh-CN\Profiles\Default\Config 键下查找，找到即返回该键下的相应值的数据，若未检索到，则继续在HKEY_CURRENT_USER\Environment 中检索，找到即返回该键下的相应值的数据，否则返回 nil。


命令函数

中望CAD 提供了 command 和 vl-cmdf 等 7 种 AutoLISP 命令函数，可以直接调用中望CAD 的内部命令进行绘图，是 AutoLISP 与中望CAD 通讯的最常见的方式。它的运行方式与在中望CAD 中执行相应的命令相似，只不过是以命令行方式进行用户交互，而非对话框方式。该类函数的详细列表请参见附录中“命令函数列表”主题。

命令提交

command 函数向中望CAD 命令行直接发送一个中望CAD 命令及对命令的响应序列。command 函数有一个可变长度的参数表，这些参数必须与命令的提示序列所要求的响应类型和值对应，它们可以是字符串、实数、整数、点坐标、图元名或选择集名，其中角度、距离和点坐标等数据既可以字符串方式传送，也可以数字方式（整数、实数或点表）传送。一个空字符串（""）与在键盘上按下空格键或回车（Enter）键是等价的。

以下示例代码说明了 command 函数的几种典型用法：

(command "line" "10.2,12.3" "@10<30" "")
(command "textstyle" "standard")
(setq pt1 '(100.0 100.0 0.0))
(command "circle" pt1 "6.0")

在中望CAD 命令提示符下执行上述代码，将执行以下操作：

1.第一个 command 函数调用将点坐标（绝对坐标和相对坐标）以字符串的形式传给 line 命令（绘制一条直线，该直线的起点坐标为 (10.2, 12.3, 0.0)，长度为 10.0，与当前 UCS 的 X 轴正方向成 30°角）。

2.第二个 command 函数调用将一个字符串传递给 textstyle 命令（将当前文字样式设置为 standard）。

3.最后一个 command 函数调用将一个三维点以变量形式传递给 circle 命令（绘制一个圆心在 (100.0, 100.0, 0.0)，半径为 6.0 的圆）。

暂停以等待用户输入

若一个中望CAD 命令在运行过程中遇到一个作为 command 函数参数的预定义符号 PAUSE，中望CAD 就会暂停该命令的运行而等待用户输入（通常是选择点或拖动），这一特性与菜单文件中提供的反斜杠（\）暂停机制很相似，当获取有效输入后 command 函数将继续执行。

当 command 函数处于挂起状态时，如果发出一条透明命令，command 函数会继续保持挂起状态，这样，在 command 暂停时用户可以使用 _nea、_mid 等命令设置临时对象捕捉点。例如，以下代码先运行 circle 命令绘制一个半径为 6.0 圆，再运行 move 命令以圆的圆心作为移动的基点，然后暂停以让用户指定圆的圆心的新位置。当用户指定了所需的点后，函数将继续执行，绘制与圆相切的六边形，如下所示：

命令：(command "circle" "0,0" 6.0 "move" "L" "" "@" pause "polygon" 6 "@" "C" 6.0)

如果在命令需要输入字符串或属性值时遇到 PAUSE，中望CAD 仅在 TextEval 系统变量非零时暂停以等待输入。否则，中望CAD 不等待用户输入，而将 PAUSE 符号解析为一个反斜杠（\）作为文字内容，如下所示：

命令：(command "-text" "10.2,15.3" "5.0 "0.0" PAUSE)

如果 command 函数对命令 select 指定 PAUSE 并且 PickFirst 系统变量被设置为 ON 时，仅当当前无任何图元被选中时，中望CAD 才会暂停以等待用户选择图元，否则不暂停而将当前被选中的图元传递给 select 命令。

放弃 command 函数中执行的命令

中望CAD 对 command 函数所运行的每一个命令都会创建一个单独的 undo 编组。如果用户在运行完 AutoLISP 程序后输入 U 命令，将仅放弃最后一个命令，而不是整个 command 函数调用的命令序列（可能包含多个中望CAD 内部命令），多次执行 U 命令，将逐一放弃 command 函数调用的中望CAD 内部命令。若用户希望在 command 函数调用后，执行一次 U 命令放弃 command 命令调用的所有中望CAD 内部命令，则需要使用 undo 命令的“开始”和“结束”选项，如下列示例代码：

命令：(command "undo" "be") (command "circle" "0,0" 6.0 "move" "L" "" "@" pause "polygon" 6 "@" "C" 6.0) (command "undo" "end")

这种用法经常被用于 *error* 函数捕获错误后，恢复绘图环境，并删除未绘制完的图形。


错误处理函数

中望CAD 提供了 alert、*error*、exit、quit、vl-catch-all-apply、vl-catch-all-error-p 和 vl-catch-all-error-message 等 7 种 AutoLISP 错误处理函数，可用于捕捉错误并向用户提供错误相关信息、恢复中望CAD 绘图环境和跳过错误并继续执行程序，该类函数的详细列表请参见附录中“错误处理函数列表”主题。

使用 *error* 函数

正确使用 *error* 函数可以确保中望CAD 在发生错误后返回到特定的状态。若未定义 *error* 函数或者已定义但为 nil，当发生错误时，ZWCAD 将停止执行 LISP 表达式打印错误信息并回显出错的 LISP 代码，例如，执行下面示例代码：

命令：(+ 12 "a")
错误: 错误参数类型
(+ 12 "a")

因 +（加）函数所需的参数为整数或实数，而上面代码中传递的第二个参数为字符串，所以中望CAD 检测到错误，返回出错误信息“错误参数类型”并回显出错的代码“(+ 12 "a")”，这一点对于调试程序非常有用。

最新错误的代号保存在中望CAD 系统变量 ERRNO 中，可用 getvar 函数来检索。

注意 在新建或打开图纸时，该变量会被清零，否则变量不被清零，这样保存在其中的错误代号所代表的错误很容易引起用户的误解。

跟系统变量一样，在自定义出错处理函数之前应该先保存原来的环境并在程序的最后恢复，如下所示：

(setq olderr *error*)    ;;;保存原来的出错处理函数
(defun err()
   ………………
   )
(setq *error* err)     ;;;设置 *error* 函数为自定义函数 err
………………
(setq *error* olderr)    ;;;恢复原来的出错处理函数

其中 err 是自定义的出错处理函数的函数名，其函数定义则跟普通的函数一样。

当存在错误时，中望CAD 调用当前定义的 *error* 函数并传给它一个参数，该参数是一个描述错误性质的文本字符串。*error* 函数应该设计为在用户按 Esc 键取消函数调用或 exit 函数调用后静默退出（不显示错误信息）。实现此功能的标准方法是在自定义的 *error* 函数中包括如下语句：

(if (or	(= msg "函数取消")            ;;;用户按下 Esc 键取消命令
    (= msg "停止 / 退出 终止")    ;;;执行 (exit) 函数终止当前 LISP 程序
    )
  (princ)
  (princ (strcat "\n错误: " msg))
)

此代码检查传送给它的错误信息，并向用户通知错误的类型。如果用户在运行 AutoLISP 程序时按 Esc 键取消了程序，或在 AutoLISP 程序中调用了 exit 函数，此代码不会返回任何内容。

这种错误处理方式的主要问题是，*error* 函数也是普通的 AutoLISP 函数，也有可能被用户取消。因此应使错误处理函数尽可能简短，且不要包含可交互的函数的调用。

使用警告对话框

alert 函数用于弹出一个只带有“确定”按钮的小对话框，直到用户按下“确定”按钮之后程序才会继续执行。下面示例代码将显示一个警告对话框：

命令：(alert "请输入一个正整数！")
警告对话框

捕获错误和继续执行程序

在大多数情况下，捕获应用程序中的错误并通知用户出错信息是远远不够的，用户的程序应该能够捕获错误并根据错误种类继续执行程序。vl-catch-all-apply 函数可调用任意函数、从函数返回值并且捕获可能发生的错误，该函数的调用格式如下：

(vl-catch-all-apply '函数名 表)

“函数名”参数指定一个待测函数或 lambda 表达式；“表”参数指定一个传递给待测函数的参数表。以下示例代码使用 vl-catch-all-apply 函数执行两个字符串变量的连接：

命令：(setq catchit (vl-catch-all-apply 'strcat '("Chinaweal" " " "Longteng")))
"Chinaweal Longteng"

本示例代码的运行结果与使用 apply 调用 strcat 函数的结果相同，但 vl-catch-all-apply 函数可以捕获错误，而 apply 函数不行。

下面是一个示例程序，实现连接两个字符串或字符串与整（实）数并返回合并后的新字符串的功能，展示了 vl-catch-all-apply 和 vl-catch-all-error-p 函数捕获错误的用法：

(defun NewCat (arg1 arg2 / catchit)
  (setq catchit (vl-catch-all-apply 'strcat '(arg1 ", " arg2)))
  (if (vl-catch-all-error-p catchit)
    (strcat (vl-princ-to-string arg1)
        ", "
        (vl-princ-to-string arg2)
    )
    catchit
  )
)

现在，用下列示例代码调用函数：

命令：(NewCat "Chinaweal" "Longteng")
"Chinaweal, Longteng"
命令：(NewCat "ZWCAD" 2023)
"ZWCAD, 2023"

若将上例中的 vl-catch-all-apply 变为 apply 函数，加载并运行连接字符串和整数的代码，当 apply 出错后，程序立即挂起，调用 *error* 函数，返回一个错误信息，如下所示：

命令：(Newcat "ZWCAD" 2023)
错误: 错误参数类型


使用 ActiveX 对象

ZWCAD 提供了一套接口，可以使用 ActiveX 功能来开发应用程序，我们称之为 VLISP。VLISP 可以和 AutoLISP 完美融合，也可以完全替代 AutoLISP，同时它还提供了一些高级功能，例如它可以用来实现反应器，还可以与支持 ActiveX 功能的其他 windows 应用程序（如 MS Excel、MS Word 等）交互。本章主要包括以下内容：

  • ActiveX 对象简介

  • 调用 ActiveX 对象的方法

  • 获取和修改 ActiveX 对象的属性

  • 使用集合对象

  • 使用 ActiveX 与其它应用程序交互


ActiveX 对象简介

使用 VLISP 来开发应用程序，它将各个 ZWCAD 图元封装成了对象，而使用 AutoLISP 开发应用程序则是建立在表操作基础上的，两者的关系类似于面向对象和面向过程的两种开发模式。在传统的 AutoLISP 编程中包含图元（图形对象）、符号表（非图形对象）和词典，它们都有一个 dxf 数据定义表，由一套通用的组码进行定义；在 ZWCAD 中引入 ActiveX 后，它们甚至连整个 ZWCAD 应用程序对象都被称为 VLA 对象，每个 VLA 对象具有各自的属性和方法。一般在操纵 ZWCAD 对象时，VLISP 的速度比传统的 AutoLISP 函数要快，访问对象特性更容易，而且开发出来的应用程序源代码可读性更好，更方便维护。例如，以下示例代码用 AutoLISP 函数获取图元的颜色：

(setq color (cdr (assoc 62 (entget ename))))

需要先调用 entget 函数获取图元 dxf 表，再调用 assoc 函数从 dxf 表中查找代表颜色的组码所在的元素，并调用 cdr 函数获取该组码的值，其实质是遍历链表，需要您知道与所要查找的特性相关的组码，下面用 VLISP 方式来改写上面这段代码：

(setq color (vla-get-color vla-obj))

只需要调用 vla-get-color 函数访问 VLA 对象的 color 属性即可，它将所有的细节都封装起来了。

如果要在 ZWCAD 中使用 ActiveX 对象，必须首先调用 vl-load-com 函数来初始化 ActiveX 环境。vl-load-com 函数首先检查是否已加载 ActiveX 对象的支持函数，如果已加载，就不做任何工作，否则将加载 ActiveX 对象的支持函数和其它辅助函数，且无任何返回值。


调用 ActiveX 对象的方法

本节以在模型空间创建一个圆心在 (120.0 130.0 0.0)，半径为 53.0 的圆为例介绍如何调用 ActiveX 对象的方法。

确定调用 ActiveX 对象的何种方法

前面介绍过用 vlax-dump-object 函数列出 ActiveX 对象的属性和方法，但它只是显示一些方法和属性的名称。本节示例程序的目标是绘制直线，从中望CAD 对象模型可知 Line 对象的上级对象有 Block、ModelSpace 和 PaperSpace，先需要获取 ModelSpace 对象，在中望CAD 对象模型中点击 ModelSpace 对象将进入 ModelSpace 对象的详细介绍页面，如下所示：

上图所示页面中列出了“ModelSpace”对象支持的方法和具有的属性，点击列表中“Methods”列中的“AddCircle”超链接，进入该方法的详细介绍页面，如下所示：

它告诉用户“AddCircle”方法的作用是“Creates a circle given a center point and radius.”，因此“AddCircle”正是所需的方法。

调用 ActiveX 对象方法的句法

在上图所示的页面中对 AddCircle 方法的语法定义如下：

RetVal = object.AddCircle(Center, Radius)

对于 LISP 用户来说，该语法很陌生，因为它是为 Visual Basic 用户定义的，但是只要了解了该描述表达的意思就可以触类旁通了。其中 object 是模型空间、图纸空间或块对象的集合；Center 表示圆的圆心，其数据类型为 VLA-OBJECT；Radius 表示圆的半径，是一个正数，其数据类型为双精度型；返回值是所创建的圆对象，数据类型为 VLA-OBJECT。

理解了用 Visual Basic 描述的 AddCircle 方法的语法后还需要做以下两件事：

1.将 Visual Basic 环境下的语句改写为 LISP 表达式

2.将 AutoLISP 数据转换为 ActiveX 所需的数据类型

在 ZWCAD 中，调用某 ActiveX 对象的方法有两种方式，其语法格式如下所示：

(vla-方法名 VLA对象 参数1 [参数2…])

(vlax-invoke-method VLA对象 方法名 参数1 [参数2…])

在中望CAD 对象模型中，模型空间对象是直线对象的上一级对象，模型空间作为 AddCircle 方法的第一个参数，Center 和 Radius 作为该方法的其余参数。vlax-3d-point 是中望CAD 提供的 VLISP 函数，用于将 AutoLISP 三维点转换为变体类型的数据，vlax-variant->vla-object 函数用于将双精度一维数组构成的变体数据转换为 VLA 对象，因此上面用 Visual Basic 描述的 AddCircle 方法的调用语法可改写为如下 LISP 表达式：

(setq circleObj (vla-AddCircle mspace (vlax-variant->vla-object (vlax-3d-point (list 120.0 130.0 0.0)) 3) 53.0))

或 (setq circleObj (vlax-invoke-method mspace 'AddCircle (vlax-variant->vla-object (vlax-3d-point (list 120.0 130.0 0.0)) 3) 53.0))

其中 mspace 是模型空间对象，获取该对象的表达式如下：

(setq *ZWCADOBJECT* (vlax-get-zwcad-object))

(setq *CurDoc* (vla-get-ActiveDocument *ZWCADOBJECT*))

(setq mspace (vla-get-ModelSpace *CurDoc*))

调用 ActiveX 对象方法时的参数传递

在 LISP 中使用 ActiveX 对象的关键是将 Visual Basic 描述的语法结构改写为 LISP 表达式，如上例所示，二者之间的转换关系如下图所示：

可以说，Visual Basic 中绝大多数 ActiveX 方法的语法描述都可以按上图所示的转换规则改写为 LISP 表达式，接下来最重要的工作就是将 AutoLISP 类型的参数转换为调用 ActiveX 方法所需的数据类型。

AutoLISP 和 ActiveX 有些数据类型的定义是类似的，当 ActiveX 遇到这些类型的数据时，能够自动进行转换。如下表所示，表的第一列为 ActiveX 要求的数据类型，第一行为 AutoLISP 的数据类型，在行和列的交叉处有勾号的表示这两种数据类型是可以自动转换的：

ActiveX 与 AutoLISP 数据类型自动转换

从上表可以看出主要是 VLA 对象与图元名（ename）、变体与其它数据类型、安全数组与其它数据类型之间无法自动转换，而这类转换在使用 ActiveX 对象的程序中经常会用到，下面就这三个方面进行详细介绍：

VLA 对象与图元名（ename）之间的转换

vlax-ename->vla-object 函数可将由 entsel、entmakex 等函数返回的图元名（ename）转换成供 VLISP 函数使用的 VLA 对象，该函数调用的格式如下：

(vlax-ename->vla-object 图元名)

“图元名”参数指定需要转换的中望CAD 对象的图元名，可用 entsel、nentsel、ssname、entmakex 等函数获取；该函数的返回值为 VLA 对象。下面的示例代码演示了如何以用户交互的方式获取一个 VLA 对象：

命令：(setq entname (car (entsel "\n选择直线：")))　　　　　　　　;;; 选择一条直线，返回所选直线的图元名 <Entity name: 56ba920>
命令：(setq vlaobj (vlax-ename->vla-object entname)) 　　　　　　　;;;将图元名转换为 VLA 对象，返回 VLA 对象的指针 #<VLA-OBJECT IZWCADLine 7c086d8>

vlax-vla-object->ename 函数可将 VLA 对象转换为供 AutoLISP 函数使用的中望CAD 图元名（ename），该函数调用的格式如下：

(vlax-vla-object->ename VLA对象)

“VLA对象”参数指定需要转换的 VLA 对象，可用集合对象的 item 方法获取，该函数的返回值为 AutoLISP 图元。如下所示，可以将上例中 vlaobj 再转换成中望CAD 图元：

命令：(setq entname-1 (vlax-vla-object->ename vlaobj))　　　　　　;;;将 VLA 对象转换为图元名，返回值为 <Entity name: 56ba920>

使用变体数据类型

变体实质上是自定义的数据结构，它可以存储不同类型的数据。变体的主要特点是数据的类型和值存储在一起。这种自定义的特点在向 ActiveX 服务器传递参数时非常有用，因为它使得基于任何语言的服务器都能理解这样的数据。

  • 创建变体

    vlax-make-variant 函数用于创建变体，其调用的格式如下：

    (vlax-make-variant [值] [类型])

    若提供了“值”参数，则在创建变体时即为其赋初值；“类型”参数指定创建的变体的实际数据类型，中望CAD 预定义的变体类型请参见《LISP 函数参考》的“vlax-make-variant”主题。若未提供“值”参数和“类型”参数，创建的变体将为 vlax-vbEmpty 类型（未初始化的变体类型）；若仅提供了“值”参数，则该函数根据“值”参数自动确定数据类型。以下是一些创建变体的表达式：

    表达式 | 返回值 | 说明
    (setq varnil (vlax-make-variant)) | #<variant 0 > | 创建了未初始化的变体，变体的实际数据类型为 0，即 vlax-vbEmpty，值为 nil
    (setq varint (vlax-make-variant 30 vlax-vbInteger)) | #<variant 2 30> | 创建了一个变体，实际数据类型为 2，即 vlax-vbInteger，值为 30
    (setq varstr (vlax-make-variant "ZWCAD")) | #<variant 8 ZWCAD> | 创建了一个变体，实际数据类型为 8，即 vlax-vbString，值为 "ZWCAD"

  • 查询变体的实际数据类型

    vlax-variant-type 函数用于查询变体的实际数据类型，该函数调用的格式如下：

    (vlax-variant-type 变体)

    若“变体”参数传递的不是变体类型的变量，则该函数会报告“参数类型错误”；该函数返回变体的数据类型代号，各代号所表示的数据类型，请参见《LISP 函数参考》的“vlax-variant-type”主题。以下是一些查询变体的实际数据类型的表达式：

    表达式 | 返回值 | 说明
    (vlax-variant-value varnil) | nil | varnil 为未初始化的变体，变体的值为 nil
    (vlax-variant-value varint) | 30 | varint 的值为 30
    (vlax-variant-value varstr) | "ZWCAD" | varstr 的值为 "ZWCAD"
    表达式 | 返回值 | 说明
    (vlax-variant-type varnil) | 0 | varnil 为未初始化的变体，变体的实际数据类型为 0，即 vlax-vbEmpty
    (vlax-variant-type varint) | 2 | varint 的实际数据类型为 2，即 vlax-vbInteger
    (vlax-variant-type varstr) | 8 | varstr 的实际数据类型为 8，即 vlax-vbString

  • 查询变体的值

    vlax-variant-value 函数用于查询变体的值，该函数调用的格式如下：

    (vlax-variant-value 变体)

    若“变体”参数传递的不是变体类型的变量，则该函数会报告“参数类型错误”；该函数返回变体的值。以下是一些查询变体的值的表达式：

  • 改变变体的数据类型

    vlax-variant-change-type 函数用于改变变体的实际数据类型，该函数调用的格式如下：

    (vlax-variant-change-type 变体 新类型)

    若“变体”参数传递的不是变体类型的变量，则该函数会报告“参数类型错误”；若不能将“变体”参数传递的变量转换为指定的类型，该函数返回 nil，若转换成功，则返回转换后的变体。以下是一些改变变体的实际数据类型的表达式：

    表达式 | 返回值 | 说明
    (vlax-variant-change-type varnil vlax-vbInteger) | #<variant 2 0> | varnil 为未初始化的变体，将其实际数据类型由 vlax-vbEmpty 转换为 vlax-vbInteger
    (vlax-variant-change-type varint vlax-vbString) | #<variant 8 30> | 将 varint 的实际数据类型由 vlax-vbInteger 转换为 vlax-vbString
    (vlax-variant-change-type varstr vlax-vbSingle) | nil | 因 varstr 的实际数据类型无法由 vlax-vbString 转换成 vlax-vbSingle，返回 nil

使用安全数组

前面介绍过 LISP 是一种弱数据类型的编程语言，在引入 ActiveX 对象后，AutoLISP 与 ActiveX 对象交换数据时，ActiveX 对象无法接收 AutoLISP 特有的 LIST 数据类型，安全数组便是为了解决这一矛盾而产生的，它是一种特殊的数组，它限制了不能在数组的边界之外赋值，因此不会引起数据异常，所以称之为安全数组。

  • 创建安全数组

    vlax-make-safearray 函数用于创建安全数组，调用的格式如下：

    (vlax-make-safearray 类型 '(下限 . 上限) [‘(下限 . 上限)…])

    “类型”参数用于指定所创建的安全数组中各元素的数据类型，可用的中望CAD 预定义数据类型请参见《LISP 函数参考》中“vlax-make-safearray”主题；各个 (下限 . 上限) 点对用于指定数组各维的大小，最多可以创建 16 维的安全数组。该函数的返回值为所创建的安全数组。下面是一些创建安全数组的表达式：

    表达式 | 返回值 | 说明
    (setq array1 (vlax-make-safearray vlax-vbDouble '(0 . 2))) | #<safearray...> | 创建一维数组 array1，包含 3 个元素，每个元素的数据类型都是双精度实数
    (setq array2 (vlax-make-safearray vlax-vbString '(0 . 1) '(1 . 3))) | #<safearray...> | 创建二维（2×3）数组 array2，第一维下限为 0，上限为 1，包含两个元素；第二维下限为 1，上限为 3，包含 3 个元素，每个元素的数据类型为字符串

    中望CAD 会根据“类型”参数自动初始化新创建的安全数组，规则如下表所示：

    数据类型 | 初始值
    vlax-vbInteger (2) | 0
    vlax-vbLong (3) | 0
    vlax-vbSingle (4) | 0.00000
    vlax-vbDouble (5) | 0.00000
    vlax-vbString (8) ""
    vlax-vbObject (9) | nil
    vlax-vbBoolean (11) | -1
    vlax-vbVariant (12) | #<variant 0 >

  • 为整个安全数组赋值

    vlax-safearray-fill 函数用于为整个安全数组赋值，调用的格式如下：

    (vlax-safearray-fill 变量 '数值表)

    若“变量”参数不是安全数组，该函数将报告“参数类型错误”；“数值表”参数是与创建安全数组时指定的数据类型兼容的数值构成的表，表的长度必须等于数组元素的个数，即不能仅为安全数组中的部分元素赋值，对于多维数组，数值表必须为嵌套表，其中每个表对应于数组中的一维。该函数返回赋值后的安全数组。下面两个表达式分别为前面创建的两个安全数组（array1 和 array2）赋值：

    表达式 | 返回值 | 说明
    (vlax-safearray-fill array1 '(120.0 130.0 0.0)) | #<safearray...> | array1 为包含 3 个元素的一维数组，赋值后数组中的各元素分别 为120.0、130.0 和 0.0
    (vlax-safearray-fill array2 '(("a" "b" "c") ("d" "e" "f"))) | #<safearray...> | array2 是一个 2 行 3 列的二维数组，赋值后第一行的三个元素的值分别为 "a"、"b" 和 "c"，第二行的三个元素的值分别为"d"、"e" 和 "f"

  • 列出安全数组的内容

    vlax-safearray->list 函数用于列出安全数组中各元素的值，调用的格式如下：

    (vlax-safearray->list 变量)

    若“变量”参数传递的不是安全数组，该函数将报告“参数类型错误”；该函数以表的形式返回安全数组中各元素的值。下面两个表达式列出赋值后的两个安全数组（array1 和 array2）中各元素的值：

    表达式 | 返回值
    (vlax-safearray->list array1) | (120.000 130.000 0.000000)
    (vlax-safearray->list array2) | (("a" "b" "c") ("d" "e" "f"))

  • 为安全数组中指定的元素赋值

    vlax-safearray-put-element 函数用于为安全数组中指定的元素赋值，调用的格式如下：

    (vlax-safearray-put-element 变量 一维索引 [二维索引…] 值)

    若“变量”参数传递的不是安全数组，该函数将报告“参数类型错误”；对于一维数组，应指定一个索引值，对于二维数组应指定两个索引值，依此类推；“值”参数是赋给安全数组内指定元素的数据，其数据类型必须与元素的数据类型兼容。该函数返回赋给指定元素的值。下面两个表达式分别修改上面两个安全数组（array1 和 array2）中指定元素的值：

    表达式 | 返回值 | 说明
    (vlax-safearray-put-element array1 1 140.0) | 140.000 | 将一维数组 array1 中的第 2 个元素（索引值为 1）的值由 130.0 更改为 140.0
    (vlax-safearray-put-element array2 0 2 "B") | "B" | 将二维数组 array2 中的第 1 行的第 2 个元素（索引值为 0 2）的值由 "b" 更改为 "B"

  • 在变体中使用安全数组

    在 AutoLISP 和 ActiveX 对象之间的数据交换中并不能直接使用安全数组，而必须用变体传递给 ActiveX。若需要为一个安全数组创建一个变体，就必须先调用 vlax-make-safearray 函数创建安全数组，再调用 vlax-make-variant 函数用所创建的安全数组作为初始值创建变体。下面用 vlax-make-safearray 函数和 vlax-make-variant 函数完成 vlax-3d-point 函数的功能，即创建一个实际数据类型为安全数组的变体，安全数组包含 3 个元素，每个元素都是双精度实数：

    1.给安全数组分配空间：

    (setq array (vlax-make-safearray vlax-vbDouble '(0 . 2)))

    2.给数组赋值

    (vlax-safearray-fill array '(120.0 130.0 0.0))

    3.由安全数组创建变体

    (setq var_array (vlax-make-variant array))

    这样变量 var_array 就是一个包含 3 个双精度实数的安全数组的变体了。

调用 ActiveX 对象的用参数带回返回值的方法

在《ZWCAD VBA 参考》中有些 ActiveX 对象的方法要求用户给它们提供变量，以使调用这些方法时可以对这些变量赋值，例如大多数 ActiveX 对象都支持的 GetBoundingBox 方法，下图是《ZWCAD VBA 参考》对该方法的描述：

从上图可知，用 GetBoundingBox 方法可以获取在 WCS 下包含图形对象的最小矩形的左下角点 MinPoint 和右上角点 MaxPoint。VBA 中调用该方法的语法格式如下：

object.GetBoundingBox MinPoint, MaxPoint

与上面介绍过的 AddCircle 方法的语法不一样，所以不能用前面介绍的转换规则来改写该 VBA 语句。下面的示例代码以获取在 WCS 下包含模型空间单行文字对象的最小矩形的左下角点和右上角点为例介绍应该如何使用 VBA 中的这类用参数带回返回值的 ActiveX 方法：

1.获取单行文字图元的图元名

(setq ename_txt (car (entsel "\n选择文本对象：")))

2.将图元名转换为 VLA 对象

(setq txtObj (vlax-ename->vla-object ename_txt))

3.调用 GetBoundingBox 方法获取包含单行文字对象的最小矩形的左下角点和右上角点：

(vla-getboundingbox txtObj 'minpoint 'maxpoint)

vla-getboundingbox 函数返回值为 nil，但它以 minpoint 和 maxpoint 变量带回返回值（将该单行文字对象边界框的左下角点和右上角点的坐标分别存储在这两个变量中），注意这两个带回返回值的变量前要添加单引号（'），两者都是 VLA 对象，可以调用 vlax-vla-object->variant 函数将其转换成变体，变体的实际数据类型为包含 3 个双精度实数的安全数组，再调用 vlax-variant-value 和 vlax-safearray->list 函数列出 minpoint 和 manpoint 变量的值：

(vlax-safearray->list (vlax-variant-value (vlax-vla-object->variant minpoint))) 　　　　　;;;返回值为 (190.726 123.380 0.000000)
(vlax-safearray->list (vlax-variant-value (vlax-vla-object->variant maxpoint))) 　　　　 ;;;返回值为 (235.406 128.060 0.000000)

确定 ActiveX 对象是否支持某方法

如果使用了某对象不支持的方法，就会导致错误。如果不能确定某 ActiveX 对象是否支持某方法，可调用 vlax-method-applicable-p 函数进行测试，调用的格式如下：

(vlax-method-applicable-p VLA对象 方法名)

“方法名”参数用于指定需要测试的 ActiveX 对象的方法，可以为符号或字符串。若该对象支持该方法，该函数返回 T，否则返回 nil。下面两个表达式演示了该函数的用法（假定 line-obj 变量存储的是一个直线对象的指针）：

表达式 | 返回值 | 说明
(vlax-method-applicable-p line-obj "copy") | T | “方法名”参数为字符串，测试直线对象是否支持“copy”方法
(vlax-method-applicable-p line-obj 'extend) | nil | “方法名”参数为符号，测试直线对象是否支持“extend”方法


获取和修改 ActiveX 对象的属性

要想获取 ActiveX 对象某属性的值，首先必须获取该对象。有两种方式可以获取 ActiveX 对象：

1.沿着中望CAD 对象模型获取 ActiveX 对象

2.通过 AutoLISP 先获取图元名，再调用 vlax-ename->vla-object 函数将其转换为 VLA 对象

在“ZWCAD 对象模型”主题中介绍过可以用 vlax-dump-object 函数查询 ActiveX 对象的属性，但它只是简单的列出该对象具有的属性的名称，本节将介绍如何获取 ActiveX 对象属性的值、如何修改 ActiveX 对象属性的值、如何检测某对象是否具有某属性。

获取 ActiveX 对象的属性

如果在当前程序运行的同时，其它应用程序也在操作某些中望CAD 对象，当前的程序可能无法访问那些对象，比如文字样式表中某文字样式被 purge 了，将无法再访问该“TextStyle”对象；若某选择集被释放（赋值为 nil），将无法再访问该“SelectionSet”对象等。在查询某 ActiveX 对象的属性的值时，需要先检测该对象是否可读以及该对象是否具有待查询的属性。

vlax-read-enabled-p 函数用于检测指定对象是否可读，调用的格式如下：

(vlax-read-enabled-p VLA对象)

若对象可读，该函数返回 T；若对象已被删除或被 vlax-release-object 函数释放等，该对象不可读，该函数返回 nil。

vlax-property-available-p 函数用于检测某对象是否具有指定的属性，调用的格式如下：

(vlax-property-available-p VLA对象 属性名 [T])

“属性名”参数指定要检测的 VLA 对象的属性名称，为字符串或符号；若未提供“T”参数，当指定的 VLA 对象具有指定属性时，该函数返回 T，否则返回 nil；若提供了“T”参数，仅当指定的 VLA 对象具有指定属性，且该属性的值可被修改时，该函数才返回 T。下面两个表达式演示了该函数的用法（假定 circle-obj 变量存储的是一个圆对象的指针）：

表达式 | 返回值 | 说明
(vlax-get-available-p circle-obj "radius") | T | “属性名”参数为字符串，检测圆对象是否具有“半径”属性
(vlax-get-available-p circle-obj 'area) | T | “属性名”参数为符号，检测圆对象是否具有“面积”属性
(vlax-get-available-p circle-obj 'area T) | nil | “属性名”参数为符号，检测圆对象是否具有“面积”属性，且“面积”属性是否可被修改

vlax-get-property 函数用于获取 ActiveX 对象的指定属性的值，调用的格式如下：

(vlax-get-property VLA对象 属性名)

“属性名”参数指定要获取的 VLA 对象的属性名称，为字符串或符号。该函数返回 VLA 对象的指定属性的值。下面的几个表达式演示了该函数的用法（假定 circle-obj 变量存储的是一个圆对象的指针）：

表达式 | 返回值 | 说明
(vlax-get-property circle-obj "radius") | 9.37200 | “属性名”参数为字符串，获取圆对象的半径
(vlax-get-property circle-obj 'area) | 275.940 | “属性名”参数为符号，获取圆对象的面积

注意 也可以用以下方式获取某对象的属性：

(vla-get-属性名 VLA对象)

上面的两个表达式可以改写为：(vla-get-radius circle-obj) 和 (vla-get-area circle-obj)。

修改 ActiveX 对象的属性

同样，在修改 ActiveX 对象的属性之前，也需要先检测该对象是否可写，该对象是否具有指定的属性，以及该对象的指定属性的值是否可被修改（用 vlax-dump-object 函数列出的 VLA 对象的属性中带有 RO 标志的是只读属性，不能被修改）。

vlax-write-enabled-p 函数用于检测某 VLA 对象是否可写，调用的格式如下：

(vlax-write-enabled-p VLA对象)

若对象可写，该函数返回 T；若对象被删除或被放置在冻结图层上等，该对象都不可被修改，该函数返回 nil。

vlax-property-available-p 函数用于检测某对象是否具有指定的属性以及该属性值可否被修改，在上面已经介绍过，在此不再赘述。

vlax-put-property 函数用于修改 VLA 对象的指定属性，调用的格式如下：

(vlax-put-property VLA对象 属性名 值)

“属性名”参数指定要修改的 VLA 对象的属性名称，为字符串或符号；“值”参数的数据类型必须与 VLA 对象的指定属性所需的数据类型兼容，否则该函数报错，无法修改指定属性的值。若属性值修改成功，该函数返回 nil。下面几个表达式演示了该函数的用法（假定 circle-obj 变量存储的是一个圆对象的指针）：

表达式 | 返回值 | 说明
(vlax-put-property circle-obj "Center" (vlax-variant->vla-object (vlax-3d-point (list 0.0 0.0 0.0)))) | nil | “属性名”参数为字符串，将圆的圆心移到坐标原点，因“Center”参数的数据类型为 VLA 对象，所以需要调用 vlax-variant->vla-object 函数将变体转换成 VLA 对象
(vlax-put-property circle-obj 'Color 1) | nil | “属性名”参数为符号，修改圆对象的颜色为红色（索引值为 1）

注意 在某些情况下，修改了 VLA 对象属性的值后，ZWCAD 不会立即更新图形的屏幕显示，此时需要调用该对象的 update 方法。

也可以用以下方式修改某对象的属性：

(vla-put-属性名 VLA对象 值)

上面两个表达式可改写为：(vla-put-Center circle-obj (vlax-variant->vla-object (vlax-3d-point (list 0.0 0.0 0.0)) 3)) 和 (vla-put-Color circle-obj 1)。


使用集合对象

集合的概念是在 ZWCAD 对象模型中引入的，ZWCAD 对象模型中的所有对象都是用集合来分组的，例如，Layers 集合对象是由 ZWCAD 图纸中所有的 layer 对象组成的，在 AutoLISP 中也有类似的关系，层表中包含图纸中所有的图层数据。VLISP 用于处理集合对象的函数主要有 vlax-map-collection 和 vlax-for。

每一个集合对象都提供了 item 方法用于遍历集合中的各个对象，例如，下面的示例代码定义了 PrtLayerInfo 命令用于打印图纸中所有的图层名称和各图层的颜色：

(vl-load-com)　　　　　;;;初始化 ActiveX 环境
(if (null *ZWCADOBJECT*)　　　　　;;;获取 ZWCAD 应用程序对象
  (setq *ZWCADOBJECT* (vlax-get-zwcad-object))
)
(if (null *ACTIVEDOCUMENT*)　　　　　;;;获取当前活动文档
  (setq *ACTIVEDOCUMENT* (vla-get-ActiveDocument *ZWCADOBJECT*))
)
(defun c:PrtLayerInfo (/ string layers layernum i
                      layerobj layername layercolor)
  (setq string "图层名\t颜色\n")
  (setq layers (vla-get-layers *ACTIVEDOCUMENT*))　　　　　;;;获取 Layers 集合对象
  (setq layernum (vla-get-Count layers))
  (setq i 0)
  (repeat layernum
    (setq layerobj (vla-item layers i))　　　　　;;;调用 Layers 集合对象的 Item 方法获取各个 layer 对象
    (setq layername (vla-get-Name layerobj))　　　;;;获取 layer 对象的 Name 属性（图层名）
    (setq layercolor (vla-get-Color layerobj))　　　;;;获取 layer 对象的 color 属性（图层颜色）
    (setq string (strcat string layername "\t" (itoa layercolor) "\n"))
    (setq i (1+ i))
  )
  (princ string)
  (princ)
)

将某一个函数应用到集合中的每一个对象

vlax-map-collection 函数用于将某一个函数应用到集合中的每一个对象，调用的格式如下：

(vlax-map-collection 集合对象 函数)

“函数”参数指定需要应用到集合中的每一个对象的函数，可以是函数名或 lambda 定义的无名函数，且它只能接受一个 VLA-OBJECT 类型的参数。若集合对象遍历成功，该函数返回集合对象的指针（VLA 对象）。下面的示例代码定义了 ChgLayerName 命令用于批量修改图纸中所有图层的图层名（添加“ZW_”前缀）：

(vl-load-com)   　　　　 ;;;初始化 ActiveX 环境
(if (null *ZWCADOBJECT*)
  (setq *ZWCADOBJECT* (vlax-get-zwcad-object))  　　　 ;;;获取 ZWCAD 应用程序对象
)
(if (null *ACTIVEDOCUMENT*)
  (setq *ACTIVEDOCUMENT* (vla-get-ActiveDocument *ZWCADOBJECT*))   　　　 ;;;获取当前活动文档对象
)
(defun c:ChgLayerName (/ layers layername)
  (setq layers (vla-get-layers *ACTIVEDOCUMENT*))
  (vlax-map-collection
    layers
    '(lambda (x)  　　　　　 ;;;lambda 定义无名函数，接受的参数 x 为一个 VLA 对象
       (setq layername (vla-get-Name x))  　　　　 ;;;获取集合对象中各对象的 Name 属性（图层名）
       (if (/= layername "0")   　　　　  ;;;因图层 0 的图层名不能被修改，将其滤除
     (vla-put-Name x (strcat "ZW_" layername))   　　　;;;修改集合对象中各对象的 Name 属性（图层名）
       )
     )
  )
  (princ)
)

将一系列函数应用到集合中的每一个对象

vlax-for 函数用于将多个表达式应用到集合中的每个对象，调用的格式如下：

(vlax-for 形参 集合对象 表达式1 [表达式2…])

在调用该函数时，将集合对象中的各个对象先赋给“形参”，然后通过“形参”再参与各个表达式进行计算求值。若成功遍历集合对象，该函数返回最后一个表达式的值。下面的示例程序定义了 statsCircle 命令用于统计图纸中圆的数量和总面积（包括块插入中的圆）：

(vl-load-com)		;;;初始化 ActiveX 环境
(if (null *ZWCADOBJECT*)		;;;获取 ZWCAD 应用程序对象
  (setq *ZWCADOBJECT* (vlax-get-zwcad-object))
)
(if (null *ACTIVEDOCUMENT*)		;;;获取当前活动文档
  (setq *ACTIVEDOCUMENT* (vla-get-ActiveDocument *ZWCADOBJECT*))
)
(defun c:statsCircle (/	cmdecho_old	MSpace num area
                        vlaObj objType sset subObj
                        subObjType i resultstr
                        )
  (setq cmdecho_old (getvar "cmdecho"))		;;;备份 cmdecho 系统变量
  (setvar "cmdecho" 0)		;;;关闭 cmdecho 系统变量，屏蔽命令行回显
  (setq MSpace (vla-get-ModelSpace *ACTIVEDOCUMENT*))
  (setq num 0)
  (setq area 0.0)
  (vlax-for vlaObj MSpace
    (setq objType (vla-get-EntityName vlaObj))
    (cond ((= objType "Circle")		;;;当图元为 circle 时
       (setq num (1+ num))
       (setq area (+ area (vla-get-Area vlaObj)))
      )
      ((= objType "BlockInsert")	;;;当图元为块插入时，遍历块中的各个子图元
       (setq i 0)
       (command "Explode" (vlax-vla-object->ename vlaObj))	;;;炸开块，当块的 X、Y、Z缩放比例不等时，圆子图元将炸开为椭圆
       (setq sset (ssget "P"))
       (repeat (sslength sset)
         (setq subObj (vlax-ename->vla-object (ssname sset i)))
         (setq subObjType (vla-get-EntityName subObj))
         (if (= subObjType "Circle")	;;;当子图元为 circle 时
           (progn
         (setq num (1+ num))
         (setq area (+ area (vla-get-Area subObj)))
           )
         )
         (setq i (1+ i))
       )
       (command "undo" 1)	;;;撤销对块的炸开操作
      )
      (T (princ))
    )
  )
  (setq	resultstr (strcat "\n图纸中共有 "
              (itoa num)
              " 个圆\n总面积为："
              (rtos area 2 3)
          )
  )
  (princ resultstr)
  (setvar "cmdecho" cmdecho_old)	;;;恢复 cmdecho 系统变量
  (princ)
)


使用 ActiveX 与其他应用程序交互

前面已经介绍过用 ActiveX 与标准的 ZWCAD 对象交互，如获取和修改 ZWCAD 对象的属性、调用 ZWCAD 对象的方法等，其实 ActiveX 还可以与其它支持 ActiveX 的应用程序交互，如 Microsoft Word、Microsoft Excel 等，特别是与 Microsoft Excel 应用程序的交互是非常实用的，它可以根据 dwg 图纸自动生成报表，供 ERP 系统使用。

正如您在 ZWCAD 中使用 ActiveX 开发应用程序您需要了解 ZWCAD 对象模型及各对象的属性和方法一样，在使用 ActiveX 与其它应用程序交互时，您也需要了解那些应用程序的对象模型和各对象的属性和方法，可以参考它们的帮助文档获取相关信息。

与其它支持 ActiveX 的应用程序交互需要遵循以下步骤：

1.输入类型库

前面介绍过调用 ZWCAD 对象的方法有两种方式，调用“vla-方法名”或“vlax-invoke-method”函数；获取 ZWCAD 对象的属性也有两种方式，调用“vla-get-属性名”或“vlax-get-property”函数；修改 ZWCAD 对象的属性也有两种方式，调用“vla-put-属性名”或“vlax-put-property”函数。其中“vla-方法名”、“vlax-get-属性名”和“vlax-put-属性名”等函数在 LISP 函数参考中没有介绍，它们是由 ZWCAD 类型库创建的包装函数。若要与其它支持 ActiveX 的应用程序交互，先调用 vlax-import-type-library 函数输入类型库，这些导入的类型库也会在 ZWCAD 将创建相应的包装函数，调用的格式如下：

(vlax-import-type-library :tlb-filename 类型库 [:methods-prefix 方法调用前缀 :properties-prefix 属性调用前缀 :constants-prefix 常量前缀])

“类型库”为一个字符串，用于指定类型库，可以为类型库文件（*.tlb）、对象库文件（*.olb）、可执行文件（*.exe）、包含类型库资源的动态链接库文件（*.dll）等，如果省略了该文件的路径，ZWCAD 将在支持路径中查找该文件；“方法调用前缀”为一个字符串，用于指定调用该应用程序中 ActiveX 对象的方法的包装函数的前缀，比如在与 ZWCAD 应用程序交互时该参数为“vla-”；“属性调用前缀”为一个字符串，用于指定获取和修改该应用程序中 ActiveX 对象的属性的包装函数的前缀，比如在与 ZWCAD 应用程序交互时该参数为“vla-get-”和“vla-put-”；“常量前缀”也是一个字符串，用于指定引用应用程序中的常量时的前缀标识。这三个前缀标识参数的作用是确定所调用的方法、所访问的属性或常量是归属于该应用程序的，类似于 C++ 中的名字空间的作用。以下示例代码定义了 Load-TypeLib-Excel 函数输入 Microsoft Excel 2003 类型库：

(defun Load-TypeLib-Excel ()
  (if (/= msxlc-xl24HourClock nil)    ;;;检测 Microsoft Excel 2003 常量 msxlc-xl24HourClock 的值是否为空
    (princ "\nMicrosoft Excel 2003 类型库已加载。")
    (progn
      (setq xlsInstDir
         (vl-registry-read       ;;;从注册表获取 Microsoft Excel 2003 的安装路径
           "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\Office\\11.0\\Excel\\InstallRoot"
           "Path"
         )
      )
      (if (null xlsInstDir)
    (progn
      (princ "\n尚未安装 Microsoft Excel 2003。")
      (exit)
    )
    (progn
      (setq tblfile (strcat xlsInstDir "\\Excel.exe"))
      (vlax-import-type-library       ;;;输入 Microsoft Excel 2003 类型库
        :tlb-filename	  tblfile
        :methods-prefix	  "msxlm-"
        :properties-prefix "msxlp-"
        :constants-prefix   "msxlc-"
       )
      (princ "\nMicrosoft Excel 2003 类型库加载成功。")
    )
      )
    )
  )
  (princ)
)

输入类型库后，您在调用 Microsoft Excel 2003 中 ActiveX 对象的方法时需要添加“msxlp-”前缀，在获取 Microsoft Excel 2003 中 ActiveX 对象的属性的属性值时需要添加“msxlp-get-”前缀，在修改 Microsoft Excel 2003 中 ActiveX 对象的属性的属性值时需要添加“msxlp-put-”前缀，引用 Microsoft Excel 2003 中的常量时需要添加“msxlc-”前缀，例如上面函数定义中以“msxlc-xl24HourClock”引用 Microsoft Excel 2003 中的“xl24HourClock”常量。

为了使编写的代码能在不同的机器上运行，应避免在传递“类型库”参数时使用绝对路径，建议通过注册表查找相关应用程序的安装路径，然后再将该路径与类型库文件名组成完整的类型库文件并传递给“类型库”参数，如上面函数定义中调用 vl-registry-read 函数从注册表中读取 Microsoft Excel 2003 的安装路径。

2.建立与应用程序的连接

所谓建立与应用程序的连接，实际上就是获取需要交互的支持 ActiveX 的应用程序的根对象，正如与 ZWCAD 交互前需要先获取 ZWCAD 应用程序对象（对象模型的根对象）。若当前正运行着应用程序的一个实例，可调用 vlax-get-object 函数获取该应用程序的根对象，其调用的格式如下：

(vlax-get-object 可编程标识)

“可编程标识”参数指定需要连接的 ActiveX 对象，其格式如下：

程序名.组件名.版本号

例如，"Excel.Application.11"表示需要连接的应用程序为 Miscrosoft Excel 2003。若成功获取应用程序对象，该函数以 VLA 对象返回该应用程序根对象，否则返回 nil。

若当前没有运行欲建立连接的应用程序，则需要调用 vlax-create-object 函数创建一个该应用程序的新实例，其调用的格式如下：

(vlax-create-object 可编程标识)

参数的意义与 vlax-get-object 函数的相同，在此不再赘述。若成功创建应用程序新实例，该函数以 VLA 对象返回所创建的应用程序根对象，否则返回 nil。

vlax-get-or-create-object 函数集成了上面两个函数的功能，它先试图获取当前正在运行着的应用程序实例根对象，若获取失败，就创建一个应用程序的新实例，其调用的格式如下：

(vlax-get-or-create-object 可编程标识)

在调用 vlax-create-object 或 vlax-get-or-create-object 函数创建应用程序新实例后，该应用程序进程在后台执行，需要将对象的 visible 属性设为 True 来显示应用程序窗口，例如执行下面代码将启动并显示 Miscrosoft Excel 2003 窗口：

(setq xlsobj (vlax-create-object "Excel.Application.11"))
(vla-put-visible xlsobj :vlax-true)


附录

在《库函数分类介绍》章节中仅对各类库函数中使用频率较高的函数进行了较详细的介绍，本章将各类库函数中其它函数的调用格式和主要功能进行简要介绍，主要包括以下内容：

  • 数学运算函数列表

  • 关系运算函数列表

  • 逻辑运算函数列表

  • 位运算函数列表

  • 转换函数列表

  • 字符串处理函数列表

  • 表处理函数列表

  • 数据输入函数列表

  • 几何实用函数列表

  • 文件处理函数列表

  • 注册表操作函数列表

  • 图元处理函数列表

  • 符号表处理函数列表

  • 选择集处理函数列表

  • 词典处理函数列表

  • 查询函数列表

  • 命令函数列表

  • 错误处理函数列表

  • 曲线操作函数列表


数学函数列表
函数名及语法 | 功能及返回值
(+ 数值1 数值2…) | 返回多个实数或整数之和
(- 数值1 数值2…) | 返回第一个数值减去第二个及以后各数值的差
(* 数值1 数值2…) | 返回多个实数或整数之积
(/ 数值1 数值2…) | 返回第一个数值除以第二个及以后各数值的商
(1+ 数值) | 数值递增 1
(1- 数值) | 数值递减 1
(abs 数值) | 返回某数值的绝对值
(atan 数值1 [数值2]) | 返回某数值的反正切值
(cos 角度) | 返回某角度（弧度表示）的余弦值
(exp 数值) | 返回常数 e (2.718282...) 的指定次幂
(expt 底数 指数) | 返回以某指定数为底数的若干次幂的值
(fix 数值) | 截去实数的小数部分，将它转换成整数后返回该整数
(float 数值) | 将数值转换为实数值
(gcd 数值1 数值2) | 返回两个整数的最大公约数
(log 数值) | 返回一个实数的自然对数 （以e为底数）
(log10 数值) | 返回一个实数的自然对数 （以10为底数）
(max [数值1 数值2…]) | 返回多个实数或整数中的最大值
(min [数值1 数值2…]) | 返回多个实数或整数中的最小值
(minusp 数值) | 判断某数值是否为负数
(numberp 数值) | 判断某变量的值是否为整数或实数
(rem 数值1 数值2…) | 返回数值 1 除以数值 2 的余数（取模）
(sin 角度) | 返回某角度（弧度表示）的正弦值
(sqrt 数值) | 返回某数值的平方根
(zerop 数值) | 检查某变量的值是否为 0（零）


关系运算函数列表
函数名及语法 | 功能及返回值
(= 表达式1 [表达式2…]) | 比较两个或多个变量、常量或表达式的值是否相等
(/= 表达式1 [表达式2…]) | 比较两个或多个变量、常量或表达式的值是否不相等
(< 表达式1 [表达式2…]) | 比较参数表中是否每一个表达式（变量或常量）的值都小于它右边的表达式（变量或常量）的值
(<= 表达式1 [表达式2…]) | 比较参数表中是否每一个表达式（变量或常量）的值都小于或等于它右边的表达式（变量或常量）的值
(> 表达式1 [表达式2…]) | 比较参数表中是否每一个表达式（变量或常量）的值都大于它右边的表达式（变量或常量）的值
(>= 表达式1 [表达式2…]) | 比较参数表中是否每一个表达式（变量或常量）的值都大于或等于它右边的表达式（变量或常量）的值
(eq 表达式1 表达式2) | 确定表达式 1 和表达式 2 是否具有相同的约束条件（适用于返回值为 LIST 类型的表达式的比较）
(equal 表达式1 表达式2 [误差范围]) | 在指定误差范围内比较两表达式（变量或常量）的值是否相等
(wcmatch 字符串 模板字符串) | 以通配符模式比较两字符串是否相等


逻辑运算函数列表
函数名及语法 | 功能及返回值
(and [表达式1 表达式2…]) | 返回多个表达式逻辑与的结果
(not 表达式) | 返回表达式逻辑非的结果
(null 表达式) | 检查某表达式的值是否为 nil
(or [表达式1 表达式2…]) | 返回多个表达式逻辑或的结果


位操作函数列表
函数名及语法 | 功能及返回值
(~ 数值) | 返回某整数的按位非（即 1 的补码）
(boole 操作代码 数值1 [数值2…]) | 返回多个整数按“操作代码”指定的逻辑运算的结果
(logand [数值1 数值2…]) | 返回多个整数按位逻辑与（AND）的结果
(logior [数值1 数值2…]) | 返回多个整数按位逻辑或（OR）的结果
(lsh 数值1 数值2) | 返回数值 1 按数值 2 指定次逻辑移位后的结果


转换函数列表
函数名及语法 | 功能及返回值
(angtof 字符串 [模式]) | 将表示角度的字符串转换为实数（浮点数）形式的弧度值
(angtos 角度 [模式 [精度]]) | 将一个以弧度为单位的角度值转换成字符串
(ascii 字符串) | 将字符串中的第一个字符转换成其 ASCII 码（整数）后返回
(atof 字符串) | 将一个字符串转换成实数
(atoi 字符串) | 将一个字符串转换成整数
(chr 数值) | 将代表字符 ASCII 码的整数转换成包含相应的单一字符的字符串
(cvunit 数值 原始单位 转换单位) | 在不同测量单位之间进行换算
(distof 字符串 [模式]) | 将一个表示实（浮点）数的字符串转换成一个实数
(itoa 整数) | 将整数转换成字符串
(rtos 数值 [模式 [精度]]) | 将数字转换成字符串
(trans 点 原位置 新位置 [位移]) | 将一个点（或位移量）从一个坐标系转换成另一个坐标系


字符串处理函数列表
函数名及语法 | 功能及返回值
(read 字符串) | 以相应的数据类型返回字符串的第一个“词”
(strcase 字符串 [模式]) | 将字符串中的所有字符转换成大写或小写后返回
(strcat [字符串1 [字符串2]…]) | 将多个字符串拼接成一个长字符串后返回
(strlen [字符串1 [字符串2]…]) | 返回字符串中字符的个数
(substr 字符串 起始位置 [长度]) | 返回字符串中的一个子字符串
(vl-prin1-to-string 变量) | 返回变量的字符串表示形式（反解析控制字符）
(vl-princ-to-string 变量) | 返回变量的字符串表示形式
(vl-string->list 字符串) | 返回由字符串中各字符的 ASCII 码值组成的表
(vl-string-elt 字符串 位置) | 返回字符串中指定位置的字符的 ASCII 码
(vl-string-left-trim 字符集 字符串) | 从字符串的开始删除指定字符集内的字符
(vl-string-mismatch 字符串1 字符串2 [起始位置1 起始位置2 模式]) | 返回两个字符串中从指定位置开始，最长的共同前缀的长度
(vl-string-position 整数 字符串 [起始位置 [模式]]) | 在字符串查找具有指定 ASCII 码的字符，并返回它在字符串中的位置
(vl-string-right-trim 字符集 字符串) | 从字符串的末尾删除指定字符集内的字符
(vl-string-search 子字符串 字符串 [起始位置]) | 在字符串中搜索指定的子串，并返回它在字符串中的位置
(vl-string-subst 新子字符串 旧子字符串 字符串 [起始位置]) | 在字符串中用新子字符串替换旧子字符串
(vl-string-translate 源字符集 目标字符集 字符串) | 在字符串中用目标字符集中的字符替换源字符集中的字符
(vl-string-trim 字符集 字符串) | 从字符串的开始和末尾删除指定字符集内的字符


表处理函数列表
函数名及语法 | 功能及返回值
(acad_strlsort 表) | 对由字符或字符串构成的表的元素按 ASCII 码大小进行排序
(append [表1 表2…]) | 将任意多个表组合成一个表
(assoc 关键元素 表) | 返回表中包含关键元素的元素（子表）
(car 表) | 返回表中第一个元素
(cadr 表) | 返回表中第二个元素
(caddr 表) | 返回表中第三个元素
(cdr 表) | 返回表中除第一个元素外的其它元素（子表）
(cons 新元素 变量) | 向表的头部添加新元素，或构造一个点对
(foreach 形参 表 [表达式]) | 将表中的所有元素以指定形参的身份逐个带入表达式求值
(last 表) | 返回表的最后一个元素
(length 表) | 返回表中元素的个数
(list [元素1 元素2…]) | 将任意数目的表达式组合成一个表
(listp 变量) | 检查变量是否为表
(mapcar 函数 表1 表2…) | 将一个或多个表的各个元素传递给函数进行求值，并返回由求值结果构成的表
(member 表达式 表) | 搜索表中是否包含某表达式，并从该元素的第一次出现处返回表的其余部分
(nth 索引号 表) | 返回表中的指定索引位置处的元素
(reverse 表) | 将表中的元素顺序倒置后返回
(subst 新元素 旧元素 表) | 用新元素替换表中的每一个旧元素，返回修改后的表
(vl-consp 变量) | 检测表是否为 nil
(vl-every 测试函数 表1 [表2]…) | 将一个或多个表的每一个元素传递给测试函数，检测是否返回值都为真
(vl-list* 变量1 [变量2]…) | 构造并返回表
(vl-list->string 整数表) | 将由整数元素构成的表的各个元素按 ASCII 码转换成字符并组合成字符串返回
(vl-list-length 表) | 计算表中的元素个数
(vl-member-if 测试函数 表) | 将一个表的每一个元素传递给测试函数，从第一次使测试函数返回值为真处返回表的其余部分
(vl-member-if-not 测试函数 表) | 将一个表的每一个元素传递给测试函数，从第一次使测试函数返回值为假处返回表的其余部分
(vl-position 元素 表) | 返回某元素在表中的位置
(vl-remove 元素 表) | 从表中删除某元素
(vl-remove-if 测试函数 表) | 返回由表中所有使测试函数返回值为假的元素组成的表
(vl-remove-if-not 测试函数 表) | 返回由表中所有使测试函数返回值为真的元素组成的表
(vl-some 测试函数 [表1 [表2]…]) | 将各个表的对应元素传递给测试函数，检测函数的返回值是否非 nil
(vl-sort 表 比较函数) | 按指定的比较函数对表中的元素排序
(vl-sort-i 表 比较函数) | 按指定的比较函数对表中的元素排序，并返回元素的索引号


数据输入函数列表
函数名及语法 | 功能及返回值
(getangle [基点] [提示]) | 暂停以等待用户输入角度（相对当前 UCS X 轴正方向的弧度值）
(getcorner 基点 [提示]) | 暂停等待用户输入矩形第二个角点的坐标
(getdist [基点] [提示]) | 暂停以等待用户输入距离
(getint [提示]) | 暂停以等待用户输入一个整数
(getkword [提示]) | 暂停以等待用户输入一个关键字
(getorient [基点] [提示]) | 暂停以等待用户输入角度（相对 WCS X 轴正方向的弧度值）
(getpoint [基点] [提示]) | 暂停以等待用户输入点
(getreal [提示]) | 暂停以等待用户输入一个实数
(getstring [模式] [提示]) | 暂停以等待用户输入字符串
(initget [位] [关键字列表]) | 为随后的用户输入函数调用创建关键字列表


几何实用函数列表
函数名及语法 | 功能及返回值
(angle 点1 点2) | 返回两点连线的角度（相对于当前 UCS 下 X 轴正方向）
(distance 点1 点2) | 返回两点间的三维距离
(inters 点1 点2 点3 点4 [模式]) | 返回点12的连线与点34连线的交点或其延长线的交点
(osnap 点 模式字符串) | 返回对指定的点应用对象捕捉得到的三维点
(polar 基点 弧度 距离) | 返回距基点指定距离，并且与基点的连线与当前 UCS 下 X 轴正方向成指定角度的三维点
(textbox dxf表) | 测量指定的文字对象的边框（左下角点和右上角点）


文件处理函数列表
函数名及语法 | 功能及返回值
(close 文件描述符) | 关闭已打开的文件
(findfile 文件名) | 在指定目录或中望CAD 搜索路径下搜索指定文件
(getfiled 标题 默认文件名 扩展名 行为模式) | 用标准的中望CAD 文件对话框提示用户选择一个文件名
(open 文件名 模式) | 以读、写或追加方式打开指定文件
(read-char [文件描述符]) | 从键盘输入缓冲区或已打开的文件中读取一个字符，并返回该字符的 ASCII 码
(read-line [文件描述符]) | 从键盘输入缓冲区或已打开的文件读取一个字符串
(vl-directory-files [目录 文件名模式 模式]) | 列出指定目录中的符合文件名模式的所有文件
(vl-file-copy 源文件名 目标文件名 [模式]) | 将源文件的内容复制或附加到目标文件中
(vl-file-delete 文件名) | 删除指定文件
(vl-file-directory-p 文件名) | 判断一个文件名是否包含文件路径
(vl-file-rename 旧文件名 新文件名) | 重命名文件
(vl-file-size 文件名) | 返回指定文件的大小（以字节为单位）
(vl-file-systime 文件名) | 返回指定文件的最后修改时间
(vl-filename-base 文件名) | 返回主文件名，去掉文件路径和扩展名
(vl-filename-directory 文件名) | 去掉文件的名称和扩展名，返回文件的路径
(vl-filename-extension 文件名) | 返回文件的扩展名
(vl-filename-mktemp [文件名模式 目录 扩展名]) | 指定临时文件使用的唯一文件名
(vl-mkdir 目录名) | 创建指定目录
(write-char 整数 [文件描述符]) | 将一个 ASCII 码为指定整数的字符写到屏幕或指定文件中
(write-line 字符串 [文件描述符]) | 将指定字符串写到屏幕或指定文件中


注册表操作函数列表
函数名及语法 | 功能及返回值
(vl-registry-delete 键 [值名称]) | 从 Windows 注册表中删除指定的键或键值
(vl-registry-descendents 键 [值名称]) | 返回 Windows 注册表中指定键或值名称的列表
(vl-registry-read 键 [值名称]) | 返回 Windows 注册表中指定键下某值名称的数据
(vl-registry-write 键 [值名称 数据]) | 在 Windows 注册表中创建键


图元处理函数列表
函数名及语法 | 功能及返回值
(entdel 图元名) | 删除图元或恢复上一个被删除的图元
(entget 图元名 [应用程序名称表]) | 返回图元的 dxf 数据
(entlast) | 返回最后一个被添加到图纸中的主图元名（图形对象）
(entmake [dxf表]) | 在图纸中创建新图元（图形对象），返回新图元的 dxf 表
(entmakex [dxf表]) | 创建一个新图元（图形对象或非图形对象），返回新图元的图元名
(entmod dxf表) | 通过 dxf 数据修改图元
(entnext [图元名]) | 返回图纸中指定图元的下一个图元名
(entsel [提示]) | 提示用户指定点来选择单个图元（仅用于简单图元）
(entupd 图元名) | 更新指定图元的屏幕显示
(handent 句柄) | 根据图元句柄返回图元名
(nentsel [提示]) | 提示用户指定点来选择单个图元（适用于简单或复杂图元）
(nentselp [提示] [点]) | 提示用户指定点来选择单个图元或在没有用户输入的情况下选择单个图元（适用于简单或复杂图元）


符号表处理函数列表
函数名及语法 | 功能及返回值
(layoutlist) | 返回当前图形中所有布局空间名称列表
(setview dxf表 视口id) | 为指定视口建立一个视图
(tblnext 符号表名 [起点]) | 在指定符号表中查找下一个项目
(tblobjname 符号表名 项目名) | 返回指定符号表中指定项目的图元名
(tblsearch 符号表名 项目名 [设置起点]) | 在指定的符号表中搜索指定项目


选择集处理函数列表
函数名及语法 | 功能及返回值
(ssadd [图元名 [选择集]]) | 创建新选择集或将指定图元添加到指定选择集中
(ssdel 图元名 选择集) | 将指定图元从选择集中删除
(ssget [模式] [点1 [点2]] [点表] [过滤表]) | 提示用户选择图元并返回包含所选图元的选择集
(ssgetfirst) | 判断哪些对象是被选取的和被夹取的
(sslength 选择集) | 返回选择集中包含的图元的数量
(ssmemb 图元名 选择集) | 检测某图元是否被包含在选择集中
(ssname 选择集 索引) | 获取选择集中某图元的图元名
(ssnamex 选择集 [索引]) | 获取关于选择集创建方式的信息
(sssetfirst 选择集1 [选择集2]) | 设置哪些图元既是被选取的又是被夹取的


词典处理函数列表
函数名及语法 | 功能及返回值
(dictadd 词典名称 关键字 图元名) | 将一个非图形对象添加到指定的词典中
(dictnext 词典名称 [模式]) | 在词典中查找下一个项目
(dictremove 词典名称 关键字) | 从指定的词典中删除一个关键字
(dictrename 词典名称 旧关键字 新关键字) | 重命名词典中某关键字
(dictsearch 词典名称 关键字 [设置起点]) | 在词典中搜索某关键字
(namedobjdict) | 返回当前图形的命名对象词典，它是所有词典对象的根
(vlax-ldata-delete 词典名称 关键字) | 从图形词典中删除 LISP 数据
(vlax-ldata-get 词典名称 关键字 [默认数据]) | 从图形词典中获取 LISP 数据
(vlax-ldata-list 词典名称) | 在图形词典中列出 LISP 数据
(vlax-ldata-put 词典名称 关键字 数据) | 将指定关键字和 LISP 数据存储到图形词典中
(vlax-ldata-test 数据) | 判断是否可以跨任务边界保存 LISP 数据


查询函数列表
函数名及语法 | 功能及返回值
(getcfg 参数名称) | 从注册表中返回用户数据
(getenv 环境变量名) | 返回操作系统或中望CAD 中指定环境变量的值
(getvar 系统变量名) | 返回中望CAD 系统变量的值
(setcfg 参数名称 值) | 将用户数据写入注册表中
(setenv 环境变量名 值) | 设置操作系统或中望CAD 环境变量的值
(setvar 系统变量名 值) | 设置中望CAD 系统变量的值


命令函数列表
函数名及语法 | 功能及返回值
(acad_colordlg 缺省颜色值 [模式]) | 显示标准的中望CAD 颜色选择对话框
(command ZWCAD命令名 命令选项…) | 执行中望CAD 命令
(help [帮助文件 [主题 [窗口状态]]]) | 调用帮助程序
(setfunhelp 命令名 [帮助文件 [主题 [窗口状态]]]) | 将一个用户自定义函数与帮助文档中的某主题关联
(vl-cmdf ZWCAD命令名 命令选项…) | 先计算各参数的值后再执行中望CAD 命令
(vlax-add-cmd 命令名 函数名) | 将某自定义函数添加为中望CAD 内置命令
(vlax-remove-cmd 命令名) | 将已添加到中望CAD 内置命令的自定义函数从内置命令中删除


错误处理函数列表
函数名及语法 | 功能及返回值
(alert 字符串) | 弹出警告对话框显示错误或警告信息
(*error* 字符串) | 出错时自动调用的函数
(exit) | 强制退出当前 LISP 程序，返回到中望CAD 命令行提示处
(quit) | 强制退出当前 LISP 程序，返回到中望CAD 命令行提示处
(vl-catch-all-apply '函数名 表) | 调用指定函数并捕捉错误
(vl-catch-all-error-message 错误对象) | 从错误对象中返回出错信息
(vl-catch-all-error-p 变量) | 判断某变量是否为错误对象


曲线测量函数列表
函数名及语法 | 功能及返回值
(vlax-curve-getArea VLA对象) | 返回曲线的面积
(vlax-curve-getClosestPointTo VLA对象 点表 [是否扩展]) | 返回 WCS 中曲线上或曲线的延长线上离指定点最近的点
(vlax-curve-getDistAtParam VLA对象 参数) | 返回曲线从开始到指定参数的曲线段的长度
(vlax-curve-getDistAtPoint VLA对象 点表) | 返回曲线从开始点到指定点的曲线段的长度
(vlax-curve-getEndParam VLA对象) | 返回曲线终点的参数
(vlax-curve-getEndPoint VLA对象) | 返回 WCS 中曲线的终点坐标
(vlax-curve-getFirstDeriv VLA对象 参数) | 返回 WCS 中曲线在指定位置的第一个衍生
(vlax-curve-getParamAtDist VLA对象 距离) | 返回距曲线开始点为指定距离的曲线参数
(vlax-curve-getParamAtPoint VLA对象 点表) | 返回曲线在指定点的参数
(vlax-curve-getPointAtDist VLA对象 距离) | 返回 WCS 中曲线上距开始点为指定距离的点
(vlax-curve-getPointAtParam VLA对象 参数) | 返回曲线上具有指定参数值的点
(vlax-curve-getSecondDeriv VLA对象 参数) | 返回 WCS 中曲线在指定位置的第二个衍生
(vlax-curve-getStartParam VLA对象) | 返回曲线的起点参数
(vlax-curve-getStartPoint VLA对象) | 返回 WCS 中曲线的起点
(vlax-curve-isClosed VLA对象) | 检测指定曲线是否闭合
(vlax-curve-isPeriodic VLA对象) | 确定指定曲线是否在各个方向上都是无限的，同时存在一个周期值 dT，使得对于任何参数 u，都有曲线上的点 (u + dT) = 点 (u) 成立
(vlax-curve-isPlanar VLA对象) | 检测是否存在包含曲线的平面


What's new in lisp?

Version Overview:

The new version of LISP enhances and improves the quality of original interfaces to some extent compared for previous version,especially in part of Curve Measurement Functions.At the same time,it adds operator function and authoration function.And we have to tell you that some unusual interfaces have not been implemented yet currently.Please refer to the next chapter contents of the updated version for detail.

Contents of Updated Version:

1.New implemented VLAX- interfaces:

  • vlax-Curve-getPointAtDist

  • vlax-Curve-getParamAtPoint

  • vlax-Curve-getParamAtDist

  • vlax-create-object

  • vlax-erased-p

  • vlax-property-available-p

  • vlax-typeinfo-available-enable-p

  • vlax-method-available-p

2.New implemented VL- interfaces:

  • vl-cmdf

  • vl-filename-mktemp

  • vl-sort-i

3.New improved VLR- interfaces:

  • vlr-acdb-reactor

  • vlr-beep-reaction

  • vlr-docmanager-reactor

  • vlr-object-reactor

  • vlr-reactors

  • vlr-type

  • vlr-types

4.New implemented LISP interfaces:

  • log10

  • lsplicense

5.New improved LISP interfaces:

  • cmdecho

  • eq

  • erase

  • entmod

  • dictnext

  • get_attr

  • menuunload

  • ssget

  • tblnext

Top

6.Not implemented interfaces:

  • acad_pop_dbmod

  • acad_push_dbmod

  • acad_truecolorcli

  • acad_truecolordlg

  • acdimenableupdate

  • acet-layerp-mode

  • acet-layerp-mark

  • acet-laytrans

  • acet-ms-to-ps

  • acet-ps-to-ms

  • defun-q-list-ref

  • defu-q-list-sef

  • layerstate-addlayers

  • layerstate-delete

  • layerstate-getlayers

  • layerstate-has

  • layerstate-import

  • layerstate-rename

  • layerstate-restore

  • layerstate-save

  • mem

  • vl-acad-defun

  • vl-acad-undefun

  • vl-arx-import

  • vl-bb-ref

  • vl-bb-set

  • vl-doc-export

  • vl-doc-import

  • vl-doc-ref

  • vl-doc-sef

  • vl-exit-with-error

  • vl-exit-with-value

  • vl-get-resource

  • vl-list-exported-functions

  • vl-list-loaded-vlx

  • vl-propagate

  • vl-unload-vlx

  • vl-vlx-loaded-p

  • vl-curve-getClosestPointToProjection

  • vlr-current-reaction-name

  • vlr-data-set

  • vlr-deepclone-reactor

  • vlr-dwg-reactor

  • vlr-dxf-reactor

  • vlr-editor-reactor

  • vlr-insert-reactor

  • vlr-lisp-reactor

  • vlr-miscellaneous-reactor

  • vlr-notification

  • vlr-owner-add

  • vlr-owner-remove

  • vlr-owners

  • vlr-pers-list

  • vlr-pers-p

  • vlr-pers-release

  • vlr-reaction-name

  • vlr-reaction-set

  • vlr-reactions

  • vlr-remove-all

  • vlr-set-notification

  • vlr-sysvar-reactor

  • vlr-trace-reaction

  • vlr-undo-reactor

  • vlr-wblock-reactor

  • vlr-window-reactor

  • vlr-xref-reactor

  • zrx

  • zrxload

  • zrxunload

Top

7.Minor part of LISP interfaces still have problems currently in some special conditions.

For more details, please refer to http://www.zwcad-doc.com/helppad/Lisp function.htm


ZWCAD 对象模型

ZWCAD 是用层次结构来组织所有对象的，ZWCAD 应用程序对象是所有对象的根对象。该层次结构的视图被称为对象模型，它显示出各个对象提供了对下一级哪些对象的访问，如下图所示：

ZWCAD 通过集合来将对象模型中的所有对象进行分类，例如，“blocks”就是一个集合对象，它包含 ZWCAD 图形中所有的块插入，在对象模型中以方框标识出集合对象。

ZWCAD 对象模型中的所有对象都有一个或多个属性，用于描述对象的特征，例如，圆对象具有“半径”、“面积”等属性。

ZWCAD 对象模型中的所有对象也具有各自的方法，它们是为特定类型对象所提供的动作。某些方法可应用于大多数图形对象，例如，mirror（镜像）和 copy（复制）方法，而另一些方法则只能应用于特定的对象，如offset（偏移）等。

要想了解某特定类型的 ZWCAD 对象的属性和方法，可查阅《ZWCAD VBA 参考》。

下面以获取图纸的模型空间中第一个图形对象为例介绍访问 ZWCAD 对象的一般步骤：

1.初始化 ActiveX 对象环境

如果要访问 ZWCAD 对象，必须调用 VLISP 函数，要使用这些函数，必须先调用 vl-load-com 函数加载支持代码：

(vl-load-com)

2.获取对象模型的根对象

ZWCAD 应用程序对象是 ZWCAD 对象模型的根对象，访问其它对象都必须从它开始，沿着层次结构逐层查找。可以调用 vlax-get-zwcad-object 函数获取 ZWCAD 应用程序对象，调用的格式如下：

(setq *ZWCADOBJECT* (vlax-get-zwcad-object))

该函数没有参数，返回指向 ZWCAD 应用程序的指针 #<VLA-OBJECT IZWCADApplication 57a9de4>，返回值的类型为 VLA-OBJECT。为了保持 LISP 程序的兼容性，ZWCAD 也支持 vlax-get-acad-object 函数，与该函数是等效的。

3.了解 ZWCAD 应用程序对象的属性和方法

vlax-dump-object 函数用于列出指定的 VLA 对象的属性和方法，调用的格式如下：

(vlax-dump-object VLA对象 [T])

若未提供“T”参数，则只列出指定 VLA 对象的属性，否则列出 VLA 对象的属性和方法。执行下面的代码列出 ZWCAD 应用程序对象的属性和方法：

(vlax-dump-object *ZWCADOBJECT*)

命令行显示如下：

Property values:
; ActiveDocument (RO)
; Application (RO)
; Caption (RO)
; DefaultFilePath
; Documents (RO)
; FullName (RO)
; Height
; LocaleID (RO)
; WindowLeft
; Library (RO)
; Name (RO)
; Path (RO)
; PlotManager (RO)
; Preferences (RO)
; StatusBar
; WindowTop
; VBE (RO)
; Version (RO)
; Visible
; Width
; HWND (RO)
; WindowState
; LockHardID (RO)
; StatusID (RO)
Methods supported:
; LoadZVB (1)
; UnloadZVB (1)
; RunMacro (1)
; ZoomAll ()
; ZoomCenter (2)
; ZoomExtents ()
; ZoomPrevious ()
; ZoomPickWindow ()
; ZoomScaled (2)
; ZoomWindow (2)
; Update ()
; DefineFunction (2)
; UndefineFunction (1)
; Help (3)
; Quit ()
; ListZDS ()
; LoadZDS (1)
; UnloadZDS (1)
; LoadLISP (1)
; RunCommand (1)
; RunScript (1)
; GetInterfaceObject (1)
; IsFunctionLoaded (1)
; SetAppForMPrint (1)
; MPrint (1)

在“Property values”（属性）部分列出了 ZWCAD 应用程序对象具有 ActiveDocument（活动文档）、Application（ZWCAD 应用程序）、Caption（标题）等属性，其中带有“(RO)”标记的属性是只读的，值不能被修改，例如属性 Caption (RO)，表示当前 ZWCAD 应用程序对话框的标题栏显示信息是不能被修改的。

在“Methods supported”（支持的方法）部分列出了可以对 ZWCAD 应用程序对象执行 Update（更新）、ZoomWindow（窗口缩放）、Quit（退出应用程序）等方法，方法名后带括号的数字，表示调用方法需要传递的参数的个数，例如方法 Quit ()，表示调用 ZWCAD 应用程序的 Quit 方法时不需要传递参数。

从对象模型可以看出“模型空间”对象的上层是“document”对象，所以 ZWCAD 应用程序对象的 ActiveDocument 属性是我们需要的。

4.获取当前活动文档对象

获取一般文档对象需要先获得文档集合对象“Documents”，然后再遍历集合对象查找所需的文档对象。因活动文档对象是需要经常访问的，在对象模型中将当前活动文档对象作为 ZWCAD 应用程序对象的一个属性，可以直接由根对象获取，如下所示：

(setq curdocument (vlax-get-property *ZWCADOBJECT* 'ActiveDocument))

返回当前活动文档对象的指针 #<VLA-OBJECT IZWCADDocument 21440c>。

5.了解活动文档对象的属性

(vlax-dump-object curdocument)

返回的属性信息中有以下一行：

; ModelSpace (RO)

ModelSpace 是活动文档对象的属性之一，它也是一个 VLA 对象。

6.获取模型空间对象

(setq mspace (vlax-get-property curdocument 'ModelSpace))

返回模型空间对象的指针 #<VLA-OBJECT IZWCADModelSpace 20cd5c>。

7.了解模型空间对象的属性和方法

(vlax-dump-object mspace T)

返回的方法信息中有以下一行：

; Item (1)

Item 是模型空间对象的方法之一，它接受一个参数，用于获取模型空间指定索引的图形对象，它也是一个 VLA 对象。

8.获取模型空间的第一个图形对象

(setq circleObj (vlax-invoke mspace 'Item 0))

返回模型空间的第一个图形对象的指针 #<VLA-OBJECT IZWCADCircle 21d974>。

注意 以上示例中的步骤3、5 和 7，并非必需步骤，若用户对 ZWCAD 对象模型已相当熟悉，可以省略这三个步骤。