前言

在前几篇文章中,我们已经完成了MxCAD Web端开发的“筑基”阶段。通过《MxCAD产品介绍》与《云开发包使用说明》,我们了解了MxCAD的核心能力并配置好了开发环境;借助《图纸转换详细说明》与《MxCAD/MxDraw Web预览批注实战》,我们实现了DWG图纸的云端转换与基础浏览批注功能;最后,通过《视图控制与图层管理系统实现》,我们学会了如何像专业CAD软件一样管理图纸的显示与结构。

至此,我们的Web CAD项目已经能够“看”图纸了。但目前的程序还停留在“静态浏览”的层面,缺乏主动“画”图的能力。用户无法通过指令来创建图形,也无法精确控制绘图的参数。

本文将带领大家进入“进阶”阶段,重点攻克CAD软件最核心的交互逻辑:命令行系统参数化绘图。我们将不再依赖鼠标凭感觉绘图,而是通过构建命令行驱动机制,让用户能够输入指令、输入数值,实现精确、动态的图形绘制。


一、为什么要引入命令行机制?

1.1 传统开发的痛点:混乱与低效

在早前的MxCAD开发实践中,我们往往采用如下模式:

  1. 代码分散:画圆的逻辑写在Circle.js,画线的逻辑写在Line.js,调用时通过UI按钮直接绑定函数。
  2. 难以管理:当项目拥有数百个绘图命令时,成百上千个函数散布在各个文件中,缺乏统一的管理入口。
  3. 交互受限:用户只能通过点击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 步骤二:监听命令(连接输入与输出)

有了界面,我们需要让程序“听懂”用户在输入框里敲了什么,并且把程序的反馈显示在消息区。
根据图一的代码逻辑,我们需要做两件事:

  1. 监听用户输入:当用户在输入框按键时,通过 MxFun.setCommandLineInputData() 告诉MxCAD内核。
  2. 监听消息变化:当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");

通过以上四个步骤,我们就成功打通了从“用户输入指令”到“程序响应绘图”的完整链路。


三、参数化绘制功能介绍

通过上文的操作步骤,我们学会了如何通过命令行与用户进行“对话”。但现在的程序还有一个问题:它虽然听懂了指令,但画图时还是太“死板”。

所谓的参数化绘制,简单来说,就是让绘图过程变得“可控”和“精确”。我们将这部分内容分为两个阶段来讲解:

  1. 静态参数化绘制:直接在代码里写死参数(适合简单场景,但不够灵活)。
  2. 动态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及其子类(如MxCADUiPrPointMxCADUiPrDist)。

核心工作流

  1. 实例化:创建一个交互对象(比如“获取点”对象)。
  2. 设置提示:告诉用户该做什么(如“请指定圆心”)。
  3. 执行(go):启动交互,程序暂停等待用户操作。
  4. 获取结果:用户操作完成后,获取坐标或数值。

常用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 完整动态绘制实例详解:绘制可交互文字

在前面的介绍中,我们了解了动态绘制的原理。现在,让我们通过一个具体的例子——“绘制文字”,将所有知识点串联起来。
在这个案例中,我们将实现一个完整的交互流程:

  1. 输入内容:用户通过键盘输入文字内容。
  2. 输入高度:用户通过鼠标拖拽或键盘输入来确定文字高度。
  3. 动态预览:用户移动鼠标时,文字跟随光标实时预览。
  4. 定点生成:用户点击鼠标,将文字永久绘制在图纸上。

代码实现

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);

代码逻辑解析

这段代码完美展示了参数化绘制的“三部曲”:

  1. 参数收集(静态阶段)
    我们使用了 MxCADUiPrStringMxCADUiPrDist。这两个API专门用于在绘图前收集必要的参数。注意这里使用了 await,这意味着程序会“暂停”在这里等待用户输入,而不是直接跑完,这保证了绘图的逻辑顺序。
  2. 动态预览(交互阶段)
    这是最精彩的部分。在 getPoint.setUserDraw 中,我们并没有创建新的图形,而是不断修改同一个 text 对象的坐标,并让它画在 pWorldDraw(预览层)上。这就像是拿着一张写有字的透明胶片,在屏幕上跟着鼠标移动。
  3. 实体入库(完成阶段)
    getPoint.go() 返回最终坐标后,我们最后一次更新位置,并调用 mxcad.drawEntity(text)。这一步相当于把透明胶片上的字,真正“印”在了图纸上。

通过这种方式,我们不仅实现了绘图,还赋予了绘图过程极强的灵活性和专业感。

四、总结

通过本文的学习,我们完成了从“静态展示”到“动态交互”的跨越:

  • 命令行搭建:我们学会了如何通过HTML布局配合JS注册,构建一个标准的CAD命令行输入环境。
  • 静态参数化:理解了通过代码计算坐标进行精确绘图的基础。
  • 动态UI交互:掌握了MxCADUiPrPointMxCADUiPrInt等核心API,实现了“程序提问、用户回答、程序绘图”的完整闭环。

现在,你的CAD项目已经具备了基本的专业骨架,用户可以像操作AutoCAD一样,在底部输入指令,并通过键盘和鼠标配合来精确绘图了。


梦想云图网页CAD
5 声望4 粉丝