第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脚本编程的进阶内容:

  1. 自定义命令

    • 命令结构和生命周期
    • 处理用户输入
  2. 交互式工具

    • 完整工具示例
    • 工具选项面板
  3. 对话框开发

    • Qt对话框创建
    • UI文件使用
    • 表单对话框
  4. 文件和配置

    • 配置读写
    • 文件操作
    • JSON处理
  5. 事件处理

    • 文档事件
    • 选择事件
    • 视图事件
  6. 调试技巧

    • 打印调试
    • 错误处理
    • 性能分析
  7. 实战项目

    • 参数化图形
    • 统计工具

下一章预告:第十三章将介绍QCAD C++插件开发。

posted @ 2026-01-11 01:40  我才是银古  阅读(4)  评论(0)    收藏  举报