
在前几篇文章中,我们已经完成了MxCAD Web端开发的“筑基”阶段。通过《MxCAD产品介绍》与《云开发包使用说明》,我们了解了MxCAD的核心能力并配置好了开发环境;借助《图纸转换详细说明》与《MxCAD/MxDraw Web预览批注实战》,我们实现了DWG图纸的云端转换与基础浏览批注功能;最后,通过《视图控制与图层管理系统实现》,我们学会了如何像专业CAD软件一样管理图纸的显示与结构。
至此,我们的Web CAD项目已经能够“看”图纸了。但目前的程序还停留在“静态浏览”的层面,缺乏主动“画”图的能力。用户无法通过指令来创建图形,也无法精确控制绘图的参数。
本文将带领大家进入“进阶”阶段,重点攻克CAD软件最核心的交互逻辑:命令行系统与参数化绘图。我们将不再依赖鼠标凭感觉绘图,而是通过构建命令行驱动机制,让用户能够输入指令、输入数值,实现精确、动态的图形绘制。
在早前的MxCAD开发实践中,我们往往采用如下模式:
Circle.js,画线的逻辑写在Line.js,调用时通过UI按钮直接绑定函数。这种“面条式”的代码结构在小型Demo中尚可,但在企业级CAD项目中是致命的。我们需要一种机制,将所有功能像“插件”一样注册到系统中,统一管理,统一调用。
MxCAD提供的命令行机制,正是为了解决上述问题而生。
在MxCAD中,实现一套完整的命令行系统并非一蹴而就,它需要经历UI搭建、事件监听、命令注册、指令执行四个核心步骤。这就好比我们要造一辆车,先要有方向盘(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>
有了界面,我们需要让程序“听懂”用户在输入框里敲了什么,并且把程序的反馈显示在消息区。
根据图一的代码逻辑,我们需要做两件事:
MxFun.setCommandLineInputData() 告诉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
})监听做好了,现在我们需要告诉MxCAD:“如果用户输入了某个特定的词(比如 Mx_test),你应该去执行哪段代码”。这就是命令注册。
我们需要使用 MxFun.addCommand() 方法。这就像是在前台注册一个服务,告诉系统:“当听到‘画线’指令时,请调用‘画线功能’”。
import { MxFun } from "mxdraw"
// 注册命令:名称为 "Mx_test"
// 以后用户在底部输入框输入 Mx_test,就会执行后面的箭头函数
MxFun.addCommand("Mx_test", async () => {
// 这里暂时留空,具体的绘图逻辑写在下一节
alert("命令已触发!");
});完成上一步操作后,mxcad对象中就已经写入了CAD命令Mx_test,接下来用户只需要调用MxFun.sendStringToExecute()函数就能在CAD项目中的任意时机或位置执行绘制方法了。
import { MxFun } from "mxdraw"
// 模拟用户输入执行:这行代码会触发上面注册的命令
// 相当于用户在命令行输入了 "Mx_test" 并按下了回车
MxFun.sendStringToExecute("Mx_test");通过以上四个步骤,我们就成功打通了从“用户输入指令”到“程序响应绘图”的完整链路。

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

代码实现
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)。这一步相当于把透明胶片上的字,真正“印”在了图纸上。通过这种方式,我们不仅实现了绘图,还赋予了绘图过程极强的灵活性和专业感。
通过本文的学习,我们完成了从“静态展示”到“动态交互”的跨越:
MxCADUiPrPoint、MxCADUiPrInt等核心API,实现了“程序提问、用户回答、程序绘图”的完整闭环。现在,你的CAD项目已经具备了基本的专业骨架,用户可以像操作AutoCAD一样,在底部输入指令,并通过键盘和鼠标配合来精确绘图了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。