第14章-自定义工具开发
第十四章:自定义工具开发
14.1 工具开发概述
14.1.1 工具类型
QCAD中的工具按功能分类:
| 类型 | 说明 | 示例 |
|---|---|---|
| 绘图工具 | 创建新实体 | 直线、圆、矩形 |
| 编辑工具 | 修改现有实体 | 移动、旋转、缩放 |
| 选择工具 | 选择实体 | 框选、交叉选择 |
| 查询工具 | 获取信息 | 测量、统计 |
| 视图工具 | 控制视图 | 缩放、平移 |
14.1.2 工具开发流程
- 确定功能需求
- 设计用户交互流程
- 创建工具类
- 实现状态机
- 添加预览功能
- 创建UI(选项面板)
- 注册工具
- 测试和调试
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");
-
重启 QCAD
-
工具将出现在相应的菜单和工具栏中
## 14.7 本章小结
本章介绍了QCAD自定义工具开发实战:
1. **工具开发概述**:
- 工具类型分类
- 开发流程
- 工具基类
2. **绘图工具**:
- 星形绘制工具完整实现
- 状态机设计
- 预览功能
3. **编辑工具**:
- 均匀分布工具
- 对象操作
4. **查询工具**:
- 材料清单生成
- 数据统计
- 报告导出
5. **批处理工具**:
- 图层批量处理
- 批量操作实现
6. **工具发布**:
- 打包结构
- 安装部署
---
**下一章预告**:第十五章将提供实战案例与最佳实践总结。

浙公网安备 33010602011771号