基于MxCAD实现命令行驱动与参数化绘图进阶
前言
在前几篇文章中,我们已经完成了MxCAD Web端开发的“筑基”阶段。通过《MxCAD产品介绍》与《云开发包使用说明》,我们了解了MxCAD的核心能力并配置好了开发环境;借助《图纸转换详细说明》与《MxCAD/MxDraw Web预览批注实战》,我们实现了DWG图纸的云端转换与基础浏览批注功能;最后,通过《视图控制与图层管理系统实现》,我们学会了如何像专业CAD软件一样管理图纸的显示与结构。
至此,我们的Web CAD项目已经能够“看”图纸了。但目前的程序还停留在“静态浏览”的层面,缺乏主动“画”图的能力。用户无法通过指令来创建图形,也无法精确控制绘图的参数。
本文将带领大家进入“进阶”阶段,重点攻克CAD软件最核心的交互逻辑:命令行系统与参数化绘图。我们将不再依赖鼠标凭感觉绘图,而是通过构建命令行驱动机制,让用户能够输入指令、输入数值,实现精确、动态的图形绘制。
一、为什么要引入命令行机制?
1.1 传统开发的痛点:混乱与低效
在早前的MxCAD开发实践中,我们往往采用如下模式:
- 代码分散:画圆的逻辑写在
Circle.js,画线的逻辑写在Line.js,调用时通过UI按钮直接绑定函数。 - 难以管理:当项目拥有数百个绘图命令时,成百上千个函数散布在各个文件中,缺乏统一的管理入口。
- 交互受限:用户只能通过点击UI按钮触发功能,无法像使用AutoCAD那样通过键盘输入指令,也无法在绘图过程中通过键盘输入参数(如长度、半径)。
这种“面条式”的代码结构在小型Demo中尚可,但在企业级CAD项目中是致命的。我们需要一种机制,将所有功能像“插件”一样注册到系统中,统一管理,统一调用。
1.2 命令行的核心价值
MxCAD提供的命令行机制,正是为了解决上述问题而生。
- 集中管理:所有功能通过唯一的名称(Name)进行注册和索引。
- 多入口调用:注册后的命令,既可以通过代码调用,也可以通过UI菜单、快捷键,甚至是底部的命令行输入框直接触发。
- 解耦:UI层与业务逻辑层解耦,UI只负责发送“指令名”,具体的绘图逻辑由命令系统分发。
二、命令行的构建与注册
在MxCAD中,实现一套完整的命令行系统并非一蹴而就,它需要经历UI搭建、事件监听、命令注册、指令执行四个核心步骤。这就好比我们要造一辆车,先要有方向盘(UI),再连接转向柱(监听),然后安装发动机(注册),最后才能点火起步(执行)。
2.1 步骤一:搭建命令行UI界面
我们在 UI界面中创建黑色的消息显示区和底部的输入框。这是用户与我们程序交互的“窗口”。
<!-- 绘图容器,占据大部分高度 -->
<div style="height: 80vh; overflow: hidden;">
<canvas id="myCanvas"></canvas>
</div>
<!-- 命令行容器,底部固定区域 -->
<div style="width: 100%; height: 12vh;">
<!-- 只读文本域,用于显示历史消息和提示(黑色背景,绿色文字风格) -->
<textarea
id="myArea"
style="width: 100%; height: 8vh; background-color: #000; color: #fff; border-radius: 5px"
readonly="true">
</textarea>
<!-- 输入框,用户在此输入命令 -->
<input
id="myInput"
style="width:100%; height: 2vh; background-color: #000; color: #fff;"
/>
</div>
2.2 步骤二:监听命令(连接输入与输出)
有了界面,我们需要让程序“听懂”用户在输入框里敲了什么,并且把程序的反馈显示在消息区。
根据图一的代码逻辑,我们需要做两件事:
- 监听用户输入:当用户在输入框按键时,通过
MxFun.setCommandLineInputData()告诉MxCAD内核。 - 监听消息变化:当MxCAD内核有提示(比如“请点击起点”)时,通过
MxFun.listenForCommandLineInput()把这些话显示在我们的消息区里。
import { MxFun } from "mxdraw"
// 1. 获取DOM元素:获取我们在HTML中定义的输入框和消息显示区
const inputBox = document.getElementById("myInput")
const cmdWindow = document.getElementById("myArea")
// 2. 监听用户输入:准备一个变量存储当前输入的内容
let inputText = ""
inputBox.oninput = () => {
inputText = inputBox.value
}
// 3. 监听键盘事件:当用户按下键盘(回车或普通按键)时
inputBox.onkeydown = (e) => {
// 将用户的输入内容和按键码传给MxCAD内核进行处理
MxFun.setCommandLineInputData(inputText, e.keyCode)
// 如果按下的是回车键 (keyCode 13),代表命令发送
if (e.keyCode === 13) {
// 清空输入框,准备下一次输入
inputText = inputBox.value = ""
}
}
// 4. 监听内核消息:监听MxCAD内核返回的消息变化
MxFun.listenForCommandLineInput(({
msCmdTip, // 提示信息(如:指定下一点)
msCmdDisplay, // 历史消息显示(之前的所有对话)
msCmdText // 当前命令文本
}) => {
// 将内核返回的消息拼接并显示在我们的HTML元素中
cmdWindow.value = msCmdDisplay + "\n" + msCmdTip
// 自动滚动到底部,确保用户看到最新消息
cmdWindow.scrollTop = cmdWindow.scrollHeight
})2.3 步骤三:注册命令(安装发动机)
监听做好了,现在我们需要告诉MxCAD:“如果用户输入了某个特定的词(比如 Mx_test),你应该去执行哪段代码”。这就是命令注册。
我们需要使用 MxFun.addCommand() 方法。这就像是在前台注册一个服务,告诉系统:“当听到‘画线’指令时,请调用‘画线功能’”。
import { MxFun } from "mxdraw"
// 注册命令:名称为 "Mx_test"
// 以后用户在底部输入框输入 Mx_test,就会执行后面的箭头函数
MxFun.addCommand("Mx_test", async () => {
// 这里暂时留空,具体的绘图逻辑写在下一节
alert("命令已触发!");
});2.4 步骤四:执行命令(点火启动)
完成上一步操作后,mxcad对象中就已经写入了CAD命令Mx_test,接下来用户只需要调用MxFun.sendStringToExecute()函数就能在CAD项目中的任意时机或位置执行绘制方法了。
import { MxFun } from "mxdraw"
// 模拟用户输入执行:这行代码会触发上面注册的命令
// 相当于用户在命令行输入了 "Mx_test" 并按下了回车
MxFun.sendStringToExecute("Mx_test");通过以上四个步骤,我们就成功打通了从“用户输入指令”到“程序响应绘图”的完整链路。

三、参数化绘制功能介绍
通过上文的操作步骤,我们学会了如何通过命令行与用户进行“对话”。但现在的程序还有一个问题:它虽然听懂了指令,但画图时还是太“死板”。
所谓的参数化绘制,简单来说,就是让绘图过程变得“可控”和“精确”。我们将这部分内容分为两个阶段来讲解:
- 静态参数化绘制:直接在代码里写死参数(适合简单场景,但不够灵活)。
- 动态UI交互绘制:用户鼠标拖动时,图形实时变化,且能输入具体数值(专业CAD的标准)。
3.1 静态参数化绘制:代码写死的局限
所谓“静态”,就是我们在代码里直接规定好图形的所有属性。
场景模拟: 我们要画一条线。在代码里直接写 :
import { MxCpp } from "mxcad";
const mxcad = MxCpp.getCurrentMxCAD(); // 获取当前CAD编辑器实例
mxcad.newFile(); // 创建新画布
// 绘制直线:坐标和长度完全写死
// 这条线永远是从 (1000, 800) 画到 (-1000, -800)
mxcad.drawLine(1000, 800, -1000, -800)缺点:
- 位置固定:直线坐标固定,无法根据用户鼠标点击的位置变化。
- 长度固定:直线长度固定,用户无法根据需求自助修改。
- 体验极差:这就像是给用户一张打印好的图纸,而不是一个绘图工具。
为了解决这个问题,我们需要引入动态UI交互绘制。
3.2 动态UI交互绘制:让图形“活”起来
在CAD中,当你点击“画圆”命令后,移动鼠标,圆的大小会跟随鼠标实时变化,直到你点击确认。这种“所见即所得”的过程,就是动态交互。
要实现这个过程,我们需要用到MxCAD提供的核心武器——UI交互API。
3.3 核心UI交互API介绍
在MxCAD中,所有的交互(画线、画圆、选点)都依赖于MxCADUiPrBase及其子类(如MxCADUiPrPoint、MxCADUiPrDist)。
核心工作流:
- 实例化:创建一个交互对象(比如“获取点”对象)。
- 设置提示:告诉用户该做什么(如“请指定圆心”)。
- 执行(go):启动交互,程序暂停等待用户操作。
- 获取结果:用户操作完成后,获取坐标或数值。
常用API一览:
| 类名 (Class Name) | 功能描述 (Description) | 典型应用场景 (Use Case) |
|---|---|---|
| MxCADUiPrPoint | 点输入。请求用户在屏幕上点击一个点,或输入坐标。 | 确定直线的起点、圆心位置、文字插入点。 |
| MxCADUiPrDist | 距离输入。请求用户输入一个数值距离,通常配合两点测量或直接键盘输入。 | 设置圆的半径、矩形的宽度、阵列的间距。 |
| MxCADUiPrAngl | 角度输入。请求用户输入一个角度值,或通过两点确定角度。 | 绘制斜线、旋转对象、扇形的起始角。 |
| MxCADUiPrInt | 整数输入。仅接受整数值输入。 | 设置阵列的行数/列数、线的数量、多边形的边数。 |
| MxCADUiPrKeyWord | 关键字输入。提供一组预定义的选项供用户选择(如 "Yes/No", "Circle/Arc")。 | 命令中的子选项切换(例如画圆时的“三点(3P)/两点(2P)/相切(T)”)。 |
| MxCADUiPrString | 字符串输入。请求用户输入文本字符串。 | 输入文字内容、块名称、图层名称、文件名。 |
| MxCADUiPrEntity | 实体选择。请求用户在图中选择一个现有的图形对象。 | 修改对象属性、延伸/修剪命令的目标选择、复制/移动命令的源对象。 |
3.4 动态绘制核心:setUserDraw 函数详解

动态绘制是实现用户交互绘制的重中之重。
很多新手会问:“我如何在用户移动鼠标时,画布中显示当下图形的实时状态?”
答案就是 setUserDraw 函数。
- 原理:在用户确定最终点之前(比如鼠标移动过程中),MxCAD会不断调用这个函数。
- 作用:我们在这个函数里编写“临时绘图”的逻辑。因为是在交互过程中绘制的,所以这些图形通常被称为“橡皮筋图形”或“预览图形”。
- 参数解析:
currentPoint:当前鼠标光标所在的坐标点。pWorldDraw:绘图上下文对象。我们需要调用它的drawMcDbEntity方法把图形画出来。
注意:在
setUserDraw中绘制的图形是临时的,一旦交互结束(用户点击确认或取消),这些临时图形会自动清除,不会污染数据库。
import { MxCADUiPrPoint, McDbLine } from "mxcad"
const getPoint = new MxCADUiPrPoint() // 创建获取点对象
getPoint.go().then((point)=> { // 第一次获取点(起点)
// 设置动态绘制回调函数
getPoint.setUserDraw((currentPoint, pWorldDraw)=> {
// currentPoint: 实时变化的鼠标坐标
// pWorldDraw: 绘图工具
if(!point) return // 防止起点为空
// 1. 创建图形:创建一条从起点到当前鼠标点的线
const line = new McDbLine(point, currentPoint)
// 2. 绘制图形:将这条线画在屏幕上(临时)
pWorldDraw.drawMcDbEntity(line)
})
// 等待第二次点击(终点)
// 在等待期间,上面的 setUserDraw 会高频触发,实现“拖拽预览”效果
getPoint.go()
})3.5 完整动态绘制实例详解:绘制可交互文字
在前面的介绍中,我们了解了动态绘制的原理。现在,让我们通过一个具体的例子——“绘制文字”,将所有知识点串联起来。
在这个案例中,我们将实现一个完整的交互流程:
- 输入内容:用户通过键盘输入文字内容。
- 输入高度:用户通过鼠标拖拽或键盘输入来确定文字高度。
- 动态预览:用户移动鼠标时,文字跟随光标实时预览。
- 定点生成:用户点击鼠标,将文字永久绘制在图纸上。

代码实现
import { McDbText, MxCADUiPrDist, MxCADUiPrPoint, MxCADUiPrString, MxCADUiPrBase, MxCpp } from "mxcad";
async function Mx_drawText() {
// 1. 初始化:创建一个空的文字对象,稍后我们将填充它的数据
const text = new McDbText();
// --- 第一步:获取文字内容 ---
// 创建“获取字符串”的交互对象
const getStr = new MxCADUiPrString();
// 设置命令行提示语
getStr.setMessage("请输入文字内容:");
// 等待用户输入(await会暂停函数,直到用户按下回车或取消)
const str = await getStr.go();
// 如果用户按了Esc取消,则直接退出函数
if (!str) return;
// 将用户输入的内容赋值给文字对象
text.textString = str;
// --- 第二步:获取文字高度 ---
// 创建“获取距离”的交互对象
const getDist = new MxCADUiPrDist();
getDist.setMessage("请输入文字高度:");
// 等待用户输入距离(可以通过鼠标拉距或键盘输入数值)
const height = await getDist.go();
if (!height) return;
// 将获取到的数值赋值给文字高度
text.height = getDist.value();
// --- 第三步:动态预览与定点 ---
// 创建“获取点”的交互对象
const getPoint = new MxCADUiPrPoint();
getPoint.setMessage("请点击确定文字位置:");
// 【核心】设置动态绘制回调
// 当用户移动鼠标时,这个函数会被反复调用
getPoint.setUserDraw((pt, pw) => {
// 1. 实时更新文字的位置为当前鼠标坐标(pt)
text.position = pt;
text.alignmentPoint = pt;
// 2. 在预览窗口(pw)中绘制这个临时的文字
// 用户看到的“橡皮筋”效果就是在这里产生的
pw.drawMcDbEntity(text);
});
// 等待用户点击鼠标确定最终位置
const position = await getPoint.go();
if (!position) return;
// --- 第四步:正式绘制 ---
// 获取当前的CAD编辑器实例
const mxcad = MxCpp.getCurrentMxCAD();
// 将最终确定的文字对象添加到图纸数据库中(这一步才是永久保存)
mxcad.drawEntity(text);
}
// --- 注册命令 ---
// 将上面写好的函数注册为一个命令
// 用户在命令行输入 "Mx_drawText" 即可触发
MxFun.addCommand("Mx_drawText", Mx_drawText);代码逻辑解析

这段代码完美展示了参数化绘制的“三部曲”:
参数收集(静态阶段): 我们使用了
MxCADUiPrString和MxCADUiPrDist。这两个API专门用于在绘图前收集必要的参数。注意这里使用了await,这意味着程序会“暂停”在这里等待用户输入,而不是直接跑完,这保证了绘图的逻辑顺序。动态预览(交互阶段): 这是最精彩的部分。在
getPoint.setUserDraw中,我们并没有创建新的图形,而是不断修改同一个text对象的坐标,并让它画在pWorldDraw(预览层)上。这就像是拿着一张写有字的透明胶片,在屏幕上跟着鼠标移动。实体入库(完成阶段): 当
getPoint.go()返回最终坐标后,我们最后一次更新位置,并调用mxcad.drawEntity(text)。这一步相当于把透明胶片上的字,真正“印”在了图纸上。
通过这种方式,我们不仅实现了绘图,还赋予了绘图过程极强的灵活性和专业感。
四、总结
通过本文的学习,我们完成了从“静态展示”到“动态交互”的跨越:
- 命令行搭建:我们学会了如何通过HTML布局配合JS注册,构建一个标准的CAD命令行输入环境。
- 静态参数化:理解了通过代码计算坐标进行精确绘图的基础。
- 动态UI交互:掌握了
MxCADUiPrPoint、MxCADUiPrInt等核心API,实现了“程序提问、用户回答、程序绘图”的完整闭环。
现在,你的CAD项目已经具备了基本的专业骨架,用户可以像操作AutoCAD一样,在底部输入指令,并通过键盘和鼠标配合来精确绘图了。
