第12章-脚本编程进阶
第十二章:脚本编程进阶
12.1 创建自定义命令
12.1.1 命令结构
QCAD命令脚本的基本结构:
// MyCommand.js
include("scripts/EAction.js");
/**
* 自定义命令类
*/
function MyCommand(guiAction) {
EAction.call(this, guiAction);
}
// 继承EAction
MyCommand.prototype = new EAction();
// 命令初始化信息
MyCommand.init = function(basePath) {
var action = new RGuiAction(qsTr("我的命令"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/MyCommand.js");
action.setIcon(basePath + "/MyCommand.svg");
action.setStatusTip(qsTr("执行我的自定义命令"));
action.setDefaultShortcut(new QKeySequence("m,c"));
action.setDefaultCommands(["mycommand", "mc"]);
action.setGroupSortOrder(10000);
action.setSortOrder(100);
action.setWidgetNames(["MiscMenu", "MiscToolBar"]);
};
// 开始事件
MyCommand.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
// 在这里执行命令逻辑
var di = this.getDocumentInterface();
var doc = this.getDocument();
// 添加一条直线作为示例
addLine(0, 0, 100, 100);
// 结束命令
this.terminate();
};
12.1.2 命令生命周期
// 命令状态常量
MyCommand.State = {
SettingFirstPoint: 0,
SettingSecondPoint: 1
};
MyCommand.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
// 设置初始状态
this.setState(MyCommand.State.SettingFirstPoint);
// 显示提示
this.setCommandPrompt(qsTr("指定第一点"));
// 启用捕捉
this.setLeftMouseTip(qsTr("指定第一点"));
this.setRightMouseTip(qsTr("取消"));
};
MyCommand.prototype.setState = function(state) {
EAction.prototype.setState.call(this, state);
switch (state) {
case MyCommand.State.SettingFirstPoint:
this.setCommandPrompt(qsTr("指定第一点"));
break;
case MyCommand.State.SettingSecondPoint:
this.setCommandPrompt(qsTr("指定第二点"));
break;
}
};
MyCommand.prototype.finishEvent = function() {
EAction.prototype.finishEvent.call(this);
// 清理资源
};
12.1.3 处理用户输入
// 处理坐标输入
MyCommand.prototype.coordinateEvent = function(event) {
var pos = event.getModelPosition();
switch (this.state) {
case MyCommand.State.SettingFirstPoint:
this.firstPoint = pos;
this.setState(MyCommand.State.SettingSecondPoint);
break;
case MyCommand.State.SettingSecondPoint:
this.secondPoint = pos;
this.createEntity();
this.terminate();
break;
}
};
// 处理鼠标移动(预览)
MyCommand.prototype.coordinateEventPreview = function(event) {
var pos = event.getModelPosition();
switch (this.state) {
case MyCommand.State.SettingSecondPoint:
if (!isNull(this.firstPoint)) {
// 创建预览
this.updatePreview();
}
break;
}
};
// 处理右键(取消)
MyCommand.prototype.escapeEvent = function() {
switch (this.state) {
case MyCommand.State.SettingFirstPoint:
EAction.prototype.escapeEvent.call(this);
break;
case MyCommand.State.SettingSecondPoint:
this.setState(MyCommand.State.SettingFirstPoint);
break;
}
};
12.2 交互式工具开发
12.2.1 完整的绘图工具示例
// DrawRectangle.js - 绘制矩形工具
include("scripts/EAction.js");
function DrawRectangle(guiAction) {
EAction.call(this, guiAction);
this.corner1 = undefined;
this.corner2 = undefined;
}
DrawRectangle.prototype = new EAction();
DrawRectangle.State = {
SettingCorner1: 0,
SettingCorner2: 1
};
DrawRectangle.init = function(basePath) {
var action = new RGuiAction(qsTr("矩形"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/DrawRectangle.js");
action.setIcon(basePath + "/DrawRectangle.svg");
action.setStatusTip(qsTr("通过两个角点绘制矩形"));
action.setDefaultCommands(["rectangle", "rect"]);
action.setGroupSortOrder(2000);
action.setSortOrder(100);
action.setWidgetNames(["DrawShapeMenu", "DrawShapeToolBar"]);
};
DrawRectangle.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
this.setState(DrawRectangle.State.SettingCorner1);
};
DrawRectangle.prototype.setState = function(state) {
EAction.prototype.setState.call(this, state);
switch (state) {
case DrawRectangle.State.SettingCorner1:
this.setCommandPrompt(qsTr("指定第一个角点"));
this.setLeftMouseTip(qsTr("指定第一个角点"));
this.setRightMouseTip(qsTr("取消"));
break;
case DrawRectangle.State.SettingCorner2:
this.setCommandPrompt(qsTr("指定对角点"));
this.setLeftMouseTip(qsTr("指定对角点"));
this.setRightMouseTip(qsTr("返回"));
break;
}
};
DrawRectangle.prototype.coordinateEvent = function(event) {
var pos = event.getModelPosition();
switch (this.state) {
case DrawRectangle.State.SettingCorner1:
this.corner1 = pos;
this.setState(DrawRectangle.State.SettingCorner2);
break;
case DrawRectangle.State.SettingCorner2:
this.corner2 = pos;
this.createRectangle();
// 准备绘制下一个
this.corner1 = undefined;
this.corner2 = undefined;
this.setState(DrawRectangle.State.SettingCorner1);
break;
}
};
DrawRectangle.prototype.coordinateEventPreview = function(event) {
var pos = event.getModelPosition();
if (this.state === DrawRectangle.State.SettingCorner2 &&
!isNull(this.corner1)) {
this.updatePreview(pos);
}
};
DrawRectangle.prototype.updatePreview = function(corner2) {
var di = this.getDocumentInterface();
// 清除之前的预览
di.clearPreview();
// 获取四个角点
var c1 = this.corner1;
var c2 = new RVector(corner2.x, this.corner1.y);
var c3 = corner2;
var c4 = new RVector(this.corner1.x, corner2.y);
// 创建预览多段线
var shapes = [];
shapes.push(new RLine(c1, c2));
shapes.push(new RLine(c2, c3));
shapes.push(new RLine(c3, c4));
shapes.push(new RLine(c4, c1));
di.addPreview(shapes);
};
DrawRectangle.prototype.createRectangle = function() {
var di = this.getDocumentInterface();
var doc = this.getDocument();
// 创建多段线矩形
var polyline = new RPolyline();
polyline.appendVertex(this.corner1);
polyline.appendVertex(new RVector(this.corner2.x, this.corner1.y));
polyline.appendVertex(this.corner2);
polyline.appendVertex(new RVector(this.corner1.x, this.corner2.y));
polyline.setClosed(true);
var entity = new RPolylineEntity(doc, new RPolylineData(polyline));
var operation = new RAddObjectOperation(entity);
di.applyOperation(operation);
};
DrawRectangle.prototype.escapeEvent = function() {
switch (this.state) {
case DrawRectangle.State.SettingCorner1:
EAction.prototype.escapeEvent.call(this);
break;
case DrawRectangle.State.SettingCorner2:
this.corner1 = undefined;
this.setState(DrawRectangle.State.SettingCorner1);
break;
}
};
12.2.2 工具选项面板
// 添加选项面板
DrawRectangle.prototype.showUiOptions = function(resume) {
EAction.prototype.showUiOptions.call(this, resume);
var optionsToolBar = EAction.getOptionsToolBar();
// 添加宽度输入框
var widthLabel = new QLabel(qsTr("宽度:"));
optionsToolBar.addWidget(widthLabel);
this.widthEdit = new RMathLineEdit(optionsToolBar);
this.widthEdit.setToolTip(qsTr("矩形宽度"));
optionsToolBar.addWidget(this.widthEdit);
// 添加高度输入框
var heightLabel = new QLabel(qsTr("高度:"));
optionsToolBar.addWidget(heightLabel);
this.heightEdit = new RMathLineEdit(optionsToolBar);
this.heightEdit.setToolTip(qsTr("矩形高度"));
optionsToolBar.addWidget(this.heightEdit);
// 连接信号
this.widthEdit.valueChanged.connect(this, "widthChanged");
this.heightEdit.valueChanged.connect(this, "heightChanged");
};
DrawRectangle.prototype.hideUiOptions = function() {
EAction.prototype.hideUiOptions.call(this);
var optionsToolBar = EAction.getOptionsToolBar();
// 清理选项控件
optionsToolBar.clear();
};
12.3 对话框开发
12.3.1 创建Qt对话框
// MyDialog.js
include("scripts/EAction.js");
function MyDialog(guiAction) {
EAction.call(this, guiAction);
}
MyDialog.prototype = new EAction();
MyDialog.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
// 创建对话框
var dialog = new QDialog(RMainWindowQt.getMainWindow());
dialog.setWindowTitle(qsTr("我的对话框"));
dialog.setModal(true);
// 创建布局
var layout = new QVBoxLayout();
dialog.setLayout(layout);
// 添加标签
var label = new QLabel(qsTr("请输入值:"));
layout.addWidget(label);
// 添加输入框
var lineEdit = new QLineEdit();
lineEdit.setPlaceholderText(qsTr("在此输入..."));
layout.addWidget(lineEdit);
// 添加按钮
var buttonBox = new QDialogButtonBox();
buttonBox.setStandardButtons(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
);
layout.addWidget(buttonBox);
// 连接信号
buttonBox.accepted.connect(dialog, "accept");
buttonBox.rejected.connect(dialog, "reject");
// 显示对话框
if (dialog.exec() === QDialog.Accepted) {
var value = lineEdit.text;
print("输入值: " + value);
// 处理输入...
}
this.terminate();
};
12.3.2 使用UI文件
// 加载.ui文件
MyDialog.prototype.showDialog = function() {
var dialog = WidgetFactory.createDialog(
"scripts/MyModule/MyDialog.ui",
RMainWindowQt.getMainWindow()
);
// 获取控件
var lineEdit = dialog.findChild("lineEdit");
var spinBox = dialog.findChild("spinBox");
var comboBox = dialog.findChild("comboBox");
// 设置初始值
lineEdit.text = "默认值";
spinBox.value = 100;
comboBox.currentIndex = 0;
// 显示对话框
if (dialog.exec() === QDialog.Accepted) {
this.processInput(lineEdit.text, spinBox.value);
}
dialog.destroy();
};
12.3.3 表单对话框
// 创建参数输入表单
MyCommand.prototype.showParameterDialog = function() {
var dialog = new QDialog(RMainWindowQt.getMainWindow());
dialog.setWindowTitle(qsTr("参数设置"));
var formLayout = new QFormLayout();
dialog.setLayout(formLayout);
// 添加各种输入控件
var nameEdit = new QLineEdit();
formLayout.addRow(qsTr("名称:"), nameEdit);
var widthSpin = new QDoubleSpinBox();
widthSpin.setRange(0, 10000);
widthSpin.setValue(100);
formLayout.addRow(qsTr("宽度:"), widthSpin);
var heightSpin = new QDoubleSpinBox();
heightSpin.setRange(0, 10000);
heightSpin.setValue(50);
formLayout.addRow(qsTr("高度:"), heightSpin);
var colorCombo = new QComboBox();
colorCombo.addItems(["红色", "绿色", "蓝色", "黄色"]);
formLayout.addRow(qsTr("颜色:"), colorCombo);
var filledCheck = new QCheckBox(qsTr("填充"));
formLayout.addRow("", filledCheck);
// 按钮
var buttonBox = new QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
);
formLayout.addRow(buttonBox);
buttonBox.accepted.connect(dialog, "accept");
buttonBox.rejected.connect(dialog, "reject");
if (dialog.exec() === QDialog.Accepted) {
return {
name: nameEdit.text,
width: widthSpin.value,
height: heightSpin.value,
color: colorCombo.currentText,
filled: filledCheck.checked
};
}
return null;
};
12.4 文件和配置
12.4.1 读写配置
// 保存配置
function saveSettings() {
var settings = RSettings.getQSettings();
settings.setValue("MyPlugin/width", 100);
settings.setValue("MyPlugin/height", 50);
settings.setValue("MyPlugin/color", "red");
settings.setValue("MyPlugin/enabled", true);
settings.sync();
}
// 读取配置
function loadSettings() {
var settings = RSettings.getQSettings();
var width = settings.value("MyPlugin/width", 100);
var height = settings.value("MyPlugin/height", 50);
var color = settings.value("MyPlugin/color", "blue");
var enabled = settings.value("MyPlugin/enabled", false);
return {
width: width,
height: height,
color: color,
enabled: enabled
};
}
12.4.2 文件操作
// 读取文本文件
function readTextFile(filePath) {
var file = new QFile(filePath);
if (!file.open(QIODevice.ReadOnly | QIODevice.Text)) {
print("无法打开文件: " + filePath);
return null;
}
var stream = new QTextStream(file);
var content = stream.readAll();
file.close();
return content;
}
// 写入文本文件
function writeTextFile(filePath, content) {
var file = new QFile(filePath);
if (!file.open(QIODevice.WriteOnly | QIODevice.Text)) {
print("无法写入文件: " + filePath);
return false;
}
var stream = new QTextStream(file);
stream.writeString(content);
file.close();
return true;
}
// 选择文件对话框
function selectFile() {
var fileName = QFileDialog.getOpenFileName(
RMainWindowQt.getMainWindow(),
qsTr("选择文件"),
"",
qsTr("DXF文件 (*.dxf);;所有文件 (*.*)")
);
return fileName;
}
12.4.3 JSON数据处理
// 解析JSON
function parseJson(jsonString) {
try {
return JSON.parse(jsonString);
} catch (e) {
print("JSON解析错误: " + e);
return null;
}
}
// 生成JSON
function toJson(obj) {
return JSON.stringify(obj, null, 2);
}
// 保存数据到JSON文件
function saveDataToJson(filePath, data) {
var jsonString = toJson(data);
return writeTextFile(filePath, jsonString);
}
// 从JSON文件加载数据
function loadDataFromJson(filePath) {
var content = readTextFile(filePath);
if (content === null) {
return null;
}
return parseJson(content);
}
12.5 事件处理
12.5.1 文档事件
// 监听文档变化
function DocumentListener(doc) {
this.doc = doc;
}
DocumentListener.prototype.documentChanged = function() {
print("文档已更改");
};
DocumentListener.prototype.entityAdded = function(entityId) {
print("添加实体: " + entityId);
};
DocumentListener.prototype.entityRemoved = function(entityId) {
print("删除实体: " + entityId);
};
DocumentListener.prototype.entityModified = function(entityId) {
print("修改实体: " + entityId);
};
// 注册监听器
var listener = new DocumentListener(doc);
doc.documentChanged.connect(listener, "documentChanged");
12.5.2 选择事件
// 监听选择变化
function SelectionListener(di) {
this.di = di;
}
SelectionListener.prototype.selectionChanged = function() {
var ids = this.di.getSelectedIds();
print("选中 " + ids.length + " 个实体");
};
// 注册
var selListener = new SelectionListener(di);
di.selectionChanged.connect(selListener, "selectionChanged");
12.5.3 视图事件
// 监听视图变化
function ViewListener(view) {
this.view = view;
}
ViewListener.prototype.viewportChanged = function() {
var factor = this.view.getZoomFactor();
print("缩放因子: " + factor);
};
12.6 调试技巧
12.6.1 打印调试
// 打印到脚本Shell
print("调试信息");
// 打印对象
print("对象: " + JSON.stringify(obj));
// 打印向量
var v = new RVector(10, 20);
print("向量: " + v.x + ", " + v.y);
// 打印实体信息
function printEntityInfo(entity) {
print("类型: " + entity.getType());
print("图层ID: " + entity.getLayerId());
print("颜色: " + entity.getColor().name());
print("包围盒: " + entity.getBoundingBox().toString());
}
12.6.2 错误处理
// try-catch处理
function safeOperation() {
try {
// 可能出错的代码
var entity = doc.queryEntity(invalidId);
entity.setColor(new RColor("red"));
} catch (e) {
print("错误: " + e);
print("位置: " + e.lineNumber);
// 记录错误或显示提示
}
}
// 参数验证
function validateInput(value, min, max) {
if (isNull(value) || isNaN(value)) {
throw new Error("无效的数值");
}
if (value < min || value > max) {
throw new Error("数值超出范围 [" + min + ", " + max + "]");
}
return true;
}
12.6.3 性能分析
// 计时
function measureTime(func, label) {
var start = Date.now();
func();
var elapsed = Date.now() - start;
print(label + ": " + elapsed + "ms");
}
// 使用
measureTime(function() {
// 要测量的操作
for (var i = 0; i < 1000; i++) {
addLine(0, 0, i, i);
}
}, "绘制1000条直线");
12.7 实战项目
12.7.1 参数化图形生成器
// 参数化螺栓绘制
function drawBolt(params) {
var di = getDocumentInterface();
var doc = getDocument();
var d = params.diameter; // 螺杆直径
var l = params.length; // 螺杆长度
var h = params.headHeight; // 头部高度
var s = params.headWidth; // 头部对边距离
var cx = params.centerX || 0;
var cy = params.centerY || 0;
var operation = new RMixedOperation();
// 绘制螺杆
var shaftLeft = cx - d / 2;
var shaftRight = cx + d / 2;
// 左边线
var line1 = new RLineEntity(doc, new RLineData(
new RVector(shaftLeft, cy),
new RVector(shaftLeft, cy - l)
));
operation.addObject(line1);
// 右边线
var line2 = new RLineEntity(doc, new RLineData(
new RVector(shaftRight, cy),
new RVector(shaftRight, cy - l)
));
operation.addObject(line2);
// 底部弧
var arc1 = new RArcEntity(doc, new RArcData(
new RVector(cx, cy - l),
d / 2,
Math.PI,
0,
false
));
operation.addObject(arc1);
// 绘制头部(六边形简化为矩形视图)
var headLeft = cx - s / 2;
var headRight = cx + s / 2;
// 头部轮廓
var head = new RPolyline();
head.appendVertex(new RVector(headLeft, cy));
head.appendVertex(new RVector(headLeft, cy + h));
head.appendVertex(new RVector(headRight, cy + h));
head.appendVertex(new RVector(headRight, cy));
head.setClosed(false);
var headEntity = new RPolylineEntity(doc, new RPolylineData(head));
operation.addObject(headEntity);
di.applyOperation(operation);
}
// 使用
drawBolt({
diameter: 10,
length: 50,
headHeight: 8,
headWidth: 17,
centerX: 0,
centerY: 0
});
12.7.2 图形统计工具
// 统计选中对象信息
function analyzeSelection() {
var ids = getSelectedIds();
var doc = getDocument();
var stats = {
total: ids.length,
byType: {},
byLayer: {},
totalLength: 0,
totalArea: 0
};
for (var i = 0; i < ids.length; i++) {
var entity = doc.queryEntity(ids[i]);
var type = entity.getType();
var layerId = entity.getLayerId();
var layer = doc.queryLayer(layerId);
var layerName = layer ? layer.getName() : "未知";
// 按类型统计
stats.byType[type] = (stats.byType[type] || 0) + 1;
// 按图层统计
stats.byLayer[layerName] = (stats.byLayer[layerName] || 0) + 1;
// 计算长度
if (typeof entity.getLength === "function") {
stats.totalLength += entity.getLength();
}
// 计算面积
if (typeof entity.getArea === "function") {
stats.totalArea += entity.getArea();
}
}
// 输出报告
print("=== 选择分析报告 ===");
print("总数: " + stats.total);
print("\n按类型:");
for (var t in stats.byType) {
print(" " + t + ": " + stats.byType[t]);
}
print("\n按图层:");
for (var l in stats.byLayer) {
print(" " + l + ": " + stats.byLayer[l]);
}
print("\n总长度: " + stats.totalLength.toFixed(2));
print("总面积: " + stats.totalArea.toFixed(2));
return stats;
}
12.8 本章小结
本章介绍了QCAD脚本编程的进阶内容:
-
自定义命令:
- 命令结构和生命周期
- 处理用户输入
-
交互式工具:
- 完整工具示例
- 工具选项面板
-
对话框开发:
- Qt对话框创建
- UI文件使用
- 表单对话框
-
文件和配置:
- 配置读写
- 文件操作
- JSON处理
-
事件处理:
- 文档事件
- 选择事件
- 视图事件
-
调试技巧:
- 打印调试
- 错误处理
- 性能分析
-
实战项目:
- 参数化图形
- 统计工具
下一章预告:第十三章将介绍QCAD C++插件开发。

浙公网安备 33010602011771号