第14章-自定义工具开发

第十四章:自定义工具开发

14.1 工具开发概述

14.1.1 工具类型

QCAD中的工具按功能分类:

类型 说明 示例
绘图工具 创建新实体 直线、圆、矩形
编辑工具 修改现有实体 移动、旋转、缩放
选择工具 选择实体 框选、交叉选择
查询工具 获取信息 测量、统计
视图工具 控制视图 缩放、平移

14.1.2 工具开发流程

  1. 确定功能需求
  2. 设计用户交互流程
  3. 创建工具类
  4. 实现状态机
  5. 添加预览功能
  6. 创建UI(选项面板)
  7. 注册工具
  8. 测试和调试

14.1.3 工具基类

所有交互式工具继承自EAction

// 工具基类结构
function MyTool(guiAction) {
    EAction.call(this, guiAction);
}

MyTool.prototype = new EAction();

// 必须实现的方法
MyTool.init = function(basePath) { ... }  // 工具注册
MyTool.prototype.beginEvent = function() { ... }  // 开始事件

14.2 绘图工具实战

14.2.1 星形绘制工具

完整的星形绘制工具实现:

// DrawStar.js
include("scripts/EAction.js");
include("scripts/ShapeAlgorithms.js");

/**
 * 星形绘制工具
 */
function DrawStar(guiAction) {
    EAction.call(this, guiAction);
    
    // 工具属性
    this.center = undefined;
    this.outerRadius = undefined;
    this.innerRadius = undefined;
    this.points = 5;  // 默认五角星
    this.rotation = -Math.PI / 2;  // 默认一个角朝上
}

DrawStar.prototype = new EAction();

// 状态定义
DrawStar.State = {
    SettingCenter: 0,
    SettingOuterRadius: 1,
    SettingInnerRadius: 2
};

/**
 * 工具初始化和注册
 */
DrawStar.init = function(basePath) {
    var action = new RGuiAction(qsTr("星形"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/DrawStar.js");
    action.setIcon(basePath + "/DrawStar.svg");
    action.setStatusTip(qsTr("绘制星形"));
    action.setDefaultShortcut(new QKeySequence("d,s"));
    action.setDefaultCommands(["star", "drawstar"]);
    action.setGroupSortOrder(2100);
    action.setSortOrder(100);
    action.setWidgetNames(["DrawShapeMenu", "DrawShapeToolBar"]);
};

/**
 * 开始事件
 */
DrawStar.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    
    // 重置状态
    this.center = undefined;
    this.outerRadius = undefined;
    this.innerRadius = undefined;
    
    // 设置初始状态
    this.setState(DrawStar.State.SettingCenter);
};

/**
 * 设置状态
 */
DrawStar.prototype.setState = function(state) {
    EAction.prototype.setState.call(this, state);
    
    var di = this.getDocumentInterface();
    di.setClickMode(RAction.PickCoordinate);
    
    switch (state) {
        case DrawStar.State.SettingCenter:
            this.setCommandPrompt(qsTr("指定星形中心"));
            this.setLeftMouseTip(qsTr("指定中心点"));
            this.setRightMouseTip(qsTr("取消"));
            break;
            
        case DrawStar.State.SettingOuterRadius:
            this.setCommandPrompt(qsTr("指定外半径(角顶点)"));
            this.setLeftMouseTip(qsTr("指定外半径"));
            this.setRightMouseTip(qsTr("返回"));
            break;
            
        case DrawStar.State.SettingInnerRadius:
            this.setCommandPrompt(qsTr("指定内半径(凹点)"));
            this.setLeftMouseTip(qsTr("指定内半径"));
            this.setRightMouseTip(qsTr("返回"));
            break;
    }
};

/**
 * 坐标事件处理
 */
DrawStar.prototype.coordinateEvent = function(event) {
    var pos = event.getModelPosition();
    
    switch (this.state) {
        case DrawStar.State.SettingCenter:
            this.center = pos;
            this.setState(DrawStar.State.SettingOuterRadius);
            break;
            
        case DrawStar.State.SettingOuterRadius:
            this.outerRadius = this.center.getDistanceTo(pos);
            this.rotation = this.center.getAngleTo(pos);
            this.setState(DrawStar.State.SettingInnerRadius);
            break;
            
        case DrawStar.State.SettingInnerRadius:
            this.innerRadius = this.center.getDistanceTo(pos);
            this.createStar();
            
            // 准备绘制下一个
            this.center = undefined;
            this.outerRadius = undefined;
            this.innerRadius = undefined;
            this.setState(DrawStar.State.SettingCenter);
            break;
    }
};

/**
 * 坐标预览
 */
DrawStar.prototype.coordinateEventPreview = function(event) {
    var pos = event.getModelPosition();
    var di = this.getDocumentInterface();
    
    switch (this.state) {
        case DrawStar.State.SettingOuterRadius:
            if (!isNull(this.center)) {
                var r = this.center.getDistanceTo(pos);
                var rot = this.center.getAngleTo(pos);
                this.previewStar(r, r * 0.4, rot);  // 默认内半径为外半径的40%
            }
            break;
            
        case DrawStar.State.SettingInnerRadius:
            if (!isNull(this.center) && !isNull(this.outerRadius)) {
                var innerR = this.center.getDistanceTo(pos);
                this.previewStar(this.outerRadius, innerR, this.rotation);
            }
            break;
    }
};

/**
 * 预览星形
 */
DrawStar.prototype.previewStar = function(outerR, innerR, rotation) {
    var di = this.getDocumentInterface();
    di.clearPreview();
    
    var shapes = this.createStarShapes(this.center, outerR, innerR, 
                                       this.points, rotation);
    
    for (var i = 0; i < shapes.length; i++) {
        di.addShapeToPreview(shapes[i], RColor(0, 0, 255), 
                            di.getDocument().getCurrentLayerId());
    }
};

/**
 * 创建星形
 */
DrawStar.prototype.createStar = function() {
    var di = this.getDocumentInterface();
    var doc = this.getDocument();
    
    var shapes = this.createStarShapes(
        this.center, this.outerRadius, this.innerRadius,
        this.points, this.rotation
    );
    
    // 创建多段线实体
    var polyline = new RPolyline();
    var vertices = this.getStarVertices(
        this.center, this.outerRadius, this.innerRadius,
        this.points, this.rotation
    );
    
    for (var i = 0; i < vertices.length; i++) {
        polyline.appendVertex(vertices[i]);
    }
    polyline.setClosed(true);
    
    var entity = new RPolylineEntity(doc, new RPolylineData(polyline));
    
    var operation = new RAddObjectOperation(entity);
    di.applyOperation(operation);
};

/**
 * 获取星形顶点
 */
DrawStar.prototype.getStarVertices = function(center, outerR, innerR, 
                                              points, rotation) {
    var vertices = [];
    var angleStep = Math.PI / points;
    
    for (var i = 0; i < points * 2; i++) {
        var angle = rotation + i * angleStep;
        var r = (i % 2 === 0) ? outerR : innerR;
        
        vertices.push(new RVector(
            center.x + r * Math.cos(angle),
            center.y + r * Math.sin(angle)
        ));
    }
    
    return vertices;
};

/**
 * 创建星形形状(用于预览)
 */
DrawStar.prototype.createStarShapes = function(center, outerR, innerR, 
                                               points, rotation) {
    var shapes = [];
    var vertices = this.getStarVertices(center, outerR, innerR, points, rotation);
    
    for (var i = 0; i < vertices.length; i++) {
        var next = (i + 1) % vertices.length;
        shapes.push(new RLine(vertices[i], vertices[next]));
    }
    
    return shapes;
};

/**
 * Escape事件
 */
DrawStar.prototype.escapeEvent = function() {
    switch (this.state) {
        case DrawStar.State.SettingCenter:
            EAction.prototype.escapeEvent.call(this);
            break;
        case DrawStar.State.SettingOuterRadius:
            this.center = undefined;
            this.setState(DrawStar.State.SettingCenter);
            break;
        case DrawStar.State.SettingInnerRadius:
            this.outerRadius = undefined;
            this.setState(DrawStar.State.SettingOuterRadius);
            break;
    }
};

/**
 * 显示选项面板
 */
DrawStar.prototype.showUiOptions = function(resume) {
    EAction.prototype.showUiOptions.call(this, resume);
    
    var optionsToolBar = EAction.getOptionsToolBar();
    
    // 角数设置
    var pointsLabel = new QLabel(qsTr("角数:"));
    optionsToolBar.addWidget(pointsLabel);
    
    this.pointsSpin = new QSpinBox();
    this.pointsSpin.setRange(3, 36);
    this.pointsSpin.setValue(this.points);
    this.pointsSpin.setToolTip(qsTr("星形角的数量"));
    this.pointsSpin.valueChanged.connect(this, "pointsChanged");
    optionsToolBar.addWidget(this.pointsSpin);
};

DrawStar.prototype.pointsChanged = function(value) {
    this.points = value;
    
    // 如果正在预览,更新预览
    if (this.state > DrawStar.State.SettingCenter) {
        this.updatePreview();
    }
};

14.2.2 工具图标(SVG)

<!-- DrawStar.svg -->
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 24 24" 
     xmlns="http://www.w3.org/2000/svg">
  <polygon 
    points="12,2 15,9 22,9 17,14 19,21 12,17 5,21 7,14 2,9 9,9"
    fill="none" 
    stroke="currentColor" 
    stroke-width="1.5"/>
</svg>

14.3 编辑工具实战

14.3.1 均匀分布工具

// DistributeEvenly.js
include("scripts/EAction.js");

/**
 * 均匀分布工具
 * 将选中的对象在两个端点对象之间均匀分布
 */
function DistributeEvenly(guiAction) {
    EAction.call(this, guiAction);
    this.direction = "horizontal";  // horizontal 或 vertical
}

DistributeEvenly.prototype = new EAction();

DistributeEvenly.State = {
    SettingDirection: 0
};

DistributeEvenly.init = function(basePath) {
    var action = new RGuiAction(qsTr("均匀分布"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setRequiresSelection(true);  // 需要预先选择
    action.setScriptFile(basePath + "/DistributeEvenly.js");
    action.setIcon(basePath + "/DistributeEvenly.svg");
    action.setStatusTip(qsTr("将选中对象均匀分布"));
    action.setDefaultCommands(["distribute", "dist"]);
    action.setGroupSortOrder(5000);
    action.setSortOrder(100);
    action.setWidgetNames(["ModifyMenu", "ModifyToolBar"]);
};

DistributeEvenly.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    
    var ids = this.getSelectedIds();
    
    if (ids.length < 3) {
        this.showMessage(qsTr("至少需要选择3个对象"));
        this.terminate();
        return;
    }
    
    // 显示选项对话框
    this.showOptionsDialog();
};

DistributeEvenly.prototype.showOptionsDialog = function() {
    var dialog = new QDialog(RMainWindowQt.getMainWindow());
    dialog.setWindowTitle(qsTr("均匀分布"));
    
    var layout = new QVBoxLayout();
    dialog.setLayout(layout);
    
    // 方向选择
    var groupBox = new QGroupBox(qsTr("分布方向"));
    var vbox = new QVBoxLayout();
    groupBox.setLayout(vbox);
    
    var radioH = new QRadioButton(qsTr("水平分布"));
    radioH.setChecked(true);
    vbox.addWidget(radioH);
    
    var radioV = new QRadioButton(qsTr("垂直分布"));
    vbox.addWidget(radioV);
    
    layout.addWidget(groupBox);
    
    // 按钮
    var buttonBox = new QDialogButtonBox(
        QDialogButtonBox.Ok | QDialogButtonBox.Cancel
    );
    buttonBox.accepted.connect(dialog, "accept");
    buttonBox.rejected.connect(dialog, "reject");
    layout.addWidget(buttonBox);
    
    if (dialog.exec() === QDialog.Accepted) {
        this.direction = radioH.checked ? "horizontal" : "vertical";
        this.distributeObjects();
    }
    
    this.terminate();
};

DistributeEvenly.prototype.distributeObjects = function() {
    var di = this.getDocumentInterface();
    var doc = this.getDocument();
    var ids = this.getSelectedIds();
    
    // 收集对象信息
    var objects = [];
    for (var i = 0; i < ids.length; i++) {
        var entity = doc.queryEntityDirect(ids[i]);
        if (isNull(entity)) continue;
        
        var box = entity.getBoundingBox();
        var center = box.getCenter();
        
        objects.push({
            id: ids[i],
            entity: entity,
            center: center,
            box: box
        });
    }
    
    // 按位置排序
    var self = this;
    objects.sort(function(a, b) {
        if (self.direction === "horizontal") {
            return a.center.x - b.center.x;
        } else {
            return a.center.y - b.center.y;
        }
    });
    
    // 计算均匀间距
    var first = objects[0];
    var last = objects[objects.length - 1];
    var totalDistance;
    
    if (this.direction === "horizontal") {
        totalDistance = last.center.x - first.center.x;
    } else {
        totalDistance = last.center.y - first.center.y;
    }
    
    var spacing = totalDistance / (objects.length - 1);
    
    // 应用移动
    var operation = new RMixedOperation();
    
    for (var i = 1; i < objects.length - 1; i++) {  // 跳过首尾
        var obj = objects[i];
        var targetPos;
        
        if (this.direction === "horizontal") {
            targetPos = first.center.x + i * spacing;
            var dx = targetPos - obj.center.x;
            obj.entity.move(new RVector(dx, 0));
        } else {
            targetPos = first.center.y + i * spacing;
            var dy = targetPos - obj.center.y;
            obj.entity.move(new RVector(0, dy));
        }
        
        operation.addObject(obj.entity, false);
    }
    
    di.applyOperation(operation);
};

14.4 查询工具实战

14.4.1 材料清单生成工具

// GenerateBOM.js
include("scripts/EAction.js");

/**
 * 材料清单(BOM)生成工具
 */
function GenerateBOM(guiAction) {
    EAction.call(this, guiAction);
}

GenerateBOM.prototype = new EAction();

GenerateBOM.init = function(basePath) {
    var action = new RGuiAction(qsTr("生成材料清单"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/GenerateBOM.js");
    action.setIcon(basePath + "/GenerateBOM.svg");
    action.setStatusTip(qsTr("生成材料清单报告"));
    action.setDefaultCommands(["bom", "材料清单"]);
    action.setGroupSortOrder(8000);
    action.setSortOrder(100);
    action.setWidgetNames(["MiscMenu"]);
};

GenerateBOM.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    
    var doc = this.getDocument();
    var bom = this.collectBOMData(doc);
    
    // 显示结果
    this.showBOMDialog(bom);
    
    this.terminate();
};

GenerateBOM.prototype.collectBOMData = function(doc) {
    var bom = {
        blocks: {},
        entities: {
            lines: { count: 0, totalLength: 0 },
            circles: { count: 0, totalCircumference: 0, totalArea: 0 },
            arcs: { count: 0, totalLength: 0 },
            polylines: { count: 0, totalLength: 0 },
            texts: { count: 0 }
        },
        layers: {}
    };
    
    // 统计块参照
    var blockRefIds = doc.queryAllBlockReferences();
    for (var i = 0; i < blockRefIds.length; i++) {
        var entity = doc.queryEntityDirect(blockRefIds[i]);
        if (isNull(entity)) continue;
        
        var blockId = entity.getReferencedBlockId();
        var block = doc.queryBlock(blockId);
        if (isNull(block)) continue;
        
        var blockName = block.getName();
        if (isNull(bom.blocks[blockName])) {
            bom.blocks[blockName] = 0;
        }
        bom.blocks[blockName]++;
    }
    
    // 统计实体
    var entityIds = doc.queryAllEntities();
    for (var i = 0; i < entityIds.length; i++) {
        var entity = doc.queryEntityDirect(entityIds[i]);
        if (isNull(entity)) continue;
        
        var type = entity.getType();
        var layerId = entity.getLayerId();
        var layer = doc.queryLayer(layerId);
        var layerName = layer ? layer.getName() : "未知";
        
        // 按图层统计
        if (isNull(bom.layers[layerName])) {
            bom.layers[layerName] = 0;
        }
        bom.layers[layerName]++;
        
        // 按类型统计
        switch (type) {
            case RS.EntityLine:
                bom.entities.lines.count++;
                bom.entities.lines.totalLength += entity.getLength();
                break;
                
            case RS.EntityCircle:
                bom.entities.circles.count++;
                bom.entities.circles.totalCircumference += entity.getLength();
                bom.entities.circles.totalArea += entity.getArea();
                break;
                
            case RS.EntityArc:
                bom.entities.arcs.count++;
                bom.entities.arcs.totalLength += entity.getLength();
                break;
                
            case RS.EntityPolyline:
                bom.entities.polylines.count++;
                bom.entities.polylines.totalLength += entity.getLength();
                break;
                
            case RS.EntityText:
            case RS.EntityTextBased:
                bom.entities.texts.count++;
                break;
        }
    }
    
    return bom;
};

GenerateBOM.prototype.showBOMDialog = function(bom) {
    var dialog = new QDialog(RMainWindowQt.getMainWindow());
    dialog.setWindowTitle(qsTr("材料清单"));
    dialog.resize(500, 400);
    
    var layout = new QVBoxLayout();
    dialog.setLayout(layout);
    
    // 创建文本显示
    var textEdit = new QTextEdit();
    textEdit.setReadOnly(true);
    
    var html = this.generateBOMHtml(bom);
    textEdit.setHtml(html);
    
    layout.addWidget(textEdit);
    
    // 按钮
    var buttonLayout = new QHBoxLayout();
    
    var exportBtn = new QPushButton(qsTr("导出"));
    exportBtn.clicked.connect(function() {
        this.exportBOM(bom);
    }.bind(this));
    buttonLayout.addWidget(exportBtn);
    
    var closeBtn = new QPushButton(qsTr("关闭"));
    closeBtn.clicked.connect(dialog, "close");
    buttonLayout.addWidget(closeBtn);
    
    layout.addLayout(buttonLayout);
    
    dialog.exec();
};

GenerateBOM.prototype.generateBOMHtml = function(bom) {
    var html = "<h2>材料清单报告</h2>";
    
    // 块统计
    html += "<h3>块参照统计</h3>";
    html += "<table border='1' cellpadding='5'>";
    html += "<tr><th>块名称</th><th>数量</th></tr>";
    for (var name in bom.blocks) {
        html += "<tr><td>" + name + "</td><td>" + bom.blocks[name] + "</td></tr>";
    }
    html += "</table>";
    
    // 实体统计
    html += "<h3>实体统计</h3>";
    html += "<table border='1' cellpadding='5'>";
    html += "<tr><th>类型</th><th>数量</th><th>总长度/面积</th></tr>";
    
    var e = bom.entities;
    html += "<tr><td>直线</td><td>" + e.lines.count + "</td>";
    html += "<td>" + e.lines.totalLength.toFixed(2) + "</td></tr>";
    
    html += "<tr><td>圆</td><td>" + e.circles.count + "</td>";
    html += "<td>周长: " + e.circles.totalCircumference.toFixed(2) + 
            " / 面积: " + e.circles.totalArea.toFixed(2) + "</td></tr>";
    
    html += "<tr><td>圆弧</td><td>" + e.arcs.count + "</td>";
    html += "<td>" + e.arcs.totalLength.toFixed(2) + "</td></tr>";
    
    html += "<tr><td>多段线</td><td>" + e.polylines.count + "</td>";
    html += "<td>" + e.polylines.totalLength.toFixed(2) + "</td></tr>";
    
    html += "<tr><td>文本</td><td>" + e.texts.count + "</td><td>-</td></tr>";
    html += "</table>";
    
    // 图层统计
    html += "<h3>图层统计</h3>";
    html += "<table border='1' cellpadding='5'>";
    html += "<tr><th>图层</th><th>对象数</th></tr>";
    for (var layer in bom.layers) {
        html += "<tr><td>" + layer + "</td><td>" + bom.layers[layer] + "</td></tr>";
    }
    html += "</table>";
    
    return html;
};

GenerateBOM.prototype.exportBOM = function(bom) {
    var fileName = QFileDialog.getSaveFileName(
        RMainWindowQt.getMainWindow(),
        qsTr("导出材料清单"),
        "",
        qsTr("CSV文件 (*.csv);;文本文件 (*.txt)")
    );
    
    if (fileName === "") return;
    
    var content = this.generateCSV(bom);
    
    var file = new QFile(fileName);
    if (file.open(QIODevice.WriteOnly | QIODevice.Text)) {
        var stream = new QTextStream(file);
        stream.writeString(content);
        file.close();
        
        this.showMessage(qsTr("已导出到: ") + fileName);
    }
};

GenerateBOM.prototype.generateCSV = function(bom) {
    var lines = [];
    
    lines.push("材料清单报告");
    lines.push("");
    
    lines.push("块参照统计");
    lines.push("块名称,数量");
    for (var name in bom.blocks) {
        lines.push(name + "," + bom.blocks[name]);
    }
    lines.push("");
    
    lines.push("实体统计");
    lines.push("类型,数量,总长度");
    var e = bom.entities;
    lines.push("直线," + e.lines.count + "," + e.lines.totalLength.toFixed(2));
    lines.push("圆," + e.circles.count + "," + e.circles.totalCircumference.toFixed(2));
    lines.push("圆弧," + e.arcs.count + "," + e.arcs.totalLength.toFixed(2));
    lines.push("多段线," + e.polylines.count + "," + e.polylines.totalLength.toFixed(2));
    lines.push("");
    
    lines.push("图层统计");
    lines.push("图层名,对象数");
    for (var layer in bom.layers) {
        lines.push(layer + "," + bom.layers[layer]);
    }
    
    return lines.join("\n");
};

14.5 批处理工具实战

14.5.1 图层批量处理工具

// BatchLayerProcessor.js
include("scripts/EAction.js");

/**
 * 图层批量处理工具
 */
function BatchLayerProcessor(guiAction) {
    EAction.call(this, guiAction);
}

BatchLayerProcessor.prototype = new EAction();

BatchLayerProcessor.init = function(basePath) {
    var action = new RGuiAction(qsTr("批量图层处理"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/BatchLayerProcessor.js");
    action.setIcon(basePath + "/BatchLayerProcessor.svg");
    action.setStatusTip(qsTr("批量处理图层"));
    action.setDefaultCommands(["batchlayer"]);
    action.setGroupSortOrder(8100);
    action.setSortOrder(100);
    action.setWidgetNames(["LayerMenu"]);
};

BatchLayerProcessor.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    this.showProcessDialog();
    this.terminate();
};

BatchLayerProcessor.prototype.showProcessDialog = function() {
    var doc = this.getDocument();
    var di = this.getDocumentInterface();
    
    var dialog = new QDialog(RMainWindowQt.getMainWindow());
    dialog.setWindowTitle(qsTr("批量图层处理"));
    dialog.resize(400, 500);
    
    var layout = new QVBoxLayout();
    dialog.setLayout(layout);
    
    // 图层列表
    var layerList = new QListWidget();
    layerList.setSelectionMode(QAbstractItemView.ExtendedSelection);
    
    // 添加图层到列表
    var layerIds = doc.queryAllLayers();
    for (var i = 0; i < layerIds.length; i++) {
        var layer = doc.queryLayer(layerIds[i]);
        if (!isNull(layer)) {
            var item = new QListWidgetItem(layer.getName());
            item.setData(Qt.UserRole, layerIds[i]);
            layerList.addItem(item);
        }
    }
    
    layout.addWidget(new QLabel(qsTr("选择要处理的图层:")));
    layout.addWidget(layerList);
    
    // 操作选项
    var operationGroup = new QGroupBox(qsTr("操作"));
    var opLayout = new QVBoxLayout();
    operationGroup.setLayout(opLayout);
    
    var radioFreeze = new QRadioButton(qsTr("冻结"));
    var radioThaw = new QRadioButton(qsTr("解冻"));
    var radioLock = new QRadioButton(qsTr("锁定"));
    var radioUnlock = new QRadioButton(qsTr("解锁"));
    var radioDelete = new QRadioButton(qsTr("删除(空图层)"));
    var radioMerge = new QRadioButton(qsTr("合并到当前图层"));
    
    radioFreeze.setChecked(true);
    
    opLayout.addWidget(radioFreeze);
    opLayout.addWidget(radioThaw);
    opLayout.addWidget(radioLock);
    opLayout.addWidget(radioUnlock);
    opLayout.addWidget(radioDelete);
    opLayout.addWidget(radioMerge);
    
    layout.addWidget(operationGroup);
    
    // 按钮
    var buttonBox = new QDialogButtonBox();
    var applyBtn = buttonBox.addButton(qsTr("应用"), QDialogButtonBox.AcceptRole);
    buttonBox.addButton(qsTr("取消"), QDialogButtonBox.RejectRole);
    
    applyBtn.clicked.connect(function() {
        var selectedItems = layerList.selectedItems();
        var layerIds = [];
        
        for (var i = 0; i < selectedItems.length; i++) {
            layerIds.push(selectedItems[i].data(Qt.UserRole));
        }
        
        var operation;
        if (radioFreeze.checked) operation = "freeze";
        else if (radioThaw.checked) operation = "thaw";
        else if (radioLock.checked) operation = "lock";
        else if (radioUnlock.checked) operation = "unlock";
        else if (radioDelete.checked) operation = "delete";
        else if (radioMerge.checked) operation = "merge";
        
        this.processLayers(layerIds, operation);
        dialog.accept();
    }.bind(this));
    
    buttonBox.rejected.connect(dialog, "reject");
    layout.addWidget(buttonBox);
    
    dialog.exec();
};

BatchLayerProcessor.prototype.processLayers = function(layerIds, operation) {
    var doc = this.getDocument();
    var di = this.getDocumentInterface();
    
    var mixedOp = new RMixedOperation();
    
    for (var i = 0; i < layerIds.length; i++) {
        var layer = doc.queryLayer(layerIds[i]);
        if (isNull(layer)) continue;
        
        switch (operation) {
            case "freeze":
                layer.setFrozen(true);
                mixedOp.addObject(layer, false);
                break;
                
            case "thaw":
                layer.setFrozen(false);
                mixedOp.addObject(layer, false);
                break;
                
            case "lock":
                layer.setLocked(true);
                mixedOp.addObject(layer, false);
                break;
                
            case "unlock":
                layer.setLocked(false);
                mixedOp.addObject(layer, false);
                break;
                
            case "delete":
                // 只删除空图层
                var entityIds = doc.queryLayerEntities(layerIds[i]);
                if (entityIds.length === 0 && layer.getName() !== "0") {
                    mixedOp.deleteObject(layer);
                }
                break;
                
            case "merge":
                // 将实体移动到当前图层
                var currentLayerId = doc.getCurrentLayerId();
                var entityIds = doc.queryLayerEntities(layerIds[i]);
                
                for (var j = 0; j < entityIds.length; j++) {
                    var entity = doc.queryEntityDirect(entityIds[j]);
                    if (!isNull(entity)) {
                        entity.setLayerId(currentLayerId);
                        mixedOp.addObject(entity, false);
                    }
                }
                break;
        }
    }
    
    di.applyOperation(mixedOp);
};

14.6 工具发布与分享

14.6.1 工具打包

MyToolPackage/
├── scripts/
│   └── MyTools/
│       ├── MyTools.js          # 入口文件
│       ├── DrawStar/
│       │   ├── DrawStar.js
│       │   └── DrawStar.svg
│       ├── DistributeEvenly/
│       │   ├── DistributeEvenly.js
│       │   └── DistributeEvenly.svg
│       └── GenerateBOM/
│           ├── GenerateBOM.js
│           └── GenerateBOM.svg
├── ts/                         # 翻译文件
│   ├── MyTools_zh_CN.ts
│   └── MyTools_en.ts
├── README.md
└── LICENSE

14.6.2 入口文件

// MyTools.js
function initMyTools(basePath) {
    // 加载所有工具
    var tools = [
        "DrawStar/DrawStar.js",
        "DistributeEvenly/DistributeEvenly.js",
        "GenerateBOM/GenerateBOM.js"
    ];
    
    for (var i = 0; i < tools.length; i++) {
        include(basePath + "/" + tools[i]);
    }
    
    // 初始化工具
    DrawStar.init(basePath + "/DrawStar");
    DistributeEvenly.init(basePath + "/DistributeEvenly");
    GenerateBOM.init(basePath + "/GenerateBOM");
}

14.6.3 安装说明

# MyTools 安装说明

1. 将 `scripts/MyTools` 文件夹复制到 QCAD 安装目录的 `scripts` 目录下

2. 在 `scripts/autostart.js` 中添加:
   ```javascript
   include("scripts/MyTools/MyTools.js");
   initMyTools("scripts/MyTools");
  1. 重启 QCAD

  2. 工具将出现在相应的菜单和工具栏中


## 14.7 本章小结

本章介绍了QCAD自定义工具开发实战:

1. **工具开发概述**:
   - 工具类型分类
   - 开发流程
   - 工具基类

2. **绘图工具**:
   - 星形绘制工具完整实现
   - 状态机设计
   - 预览功能

3. **编辑工具**:
   - 均匀分布工具
   - 对象操作

4. **查询工具**:
   - 材料清单生成
   - 数据统计
   - 报告导出

5. **批处理工具**:
   - 图层批量处理
   - 批量操作实现

6. **工具发布**:
   - 打包结构
   - 安装部署

---

**下一章预告**:第十五章将提供实战案例与最佳实践总结。
posted @ 2026-01-11 01:40  我才是银古  阅读(13)  评论(0)    收藏  举报