Skip to content

交互式脚本动作

脚本动作是这样一类脚本:它向菜单和/或工具栏添加一个条目,并且能够处理用户交互。脚本动作会一直保持活动状态,直到被用户终止或自行终止为止。

脚本动作一旦启动,就会处理各种事件,直到被终止为止。事件是指某件事情发生时所产生的东西。例如,当脚本动作启动时,会调用 beginEvent。当用户点击某个实体时,会触发 pickEntity 事件;当用户点击某个坐标时,会发生 pickCoordinate 事件,等等。

脚本动作的最简结构如下:

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

这个示例脚本会在菜单 其他 > 示例 的底部添加一个菜单项。菜单文本为 “Minimal Example”。

请注意,为了让脚本能够被找到,文件名需要与类名匹配,即此处为 “ExMyMinimal.js”。它还需要位于一个同名目录 “ExMyMinimal” 内,因此该脚本例如可以放在 scripts/Misc/ExMyMinimal/ExMyMinimal.js。

您也可以将脚本放入用户主目录中的本地 scripts 文件夹。要确定具体的文件夹,请打开关于对话框(帮助 > 关于 QCAD…)并切换到 系统 选项卡。您可以在其中的 Data directory 处看到数据位置。这就是您需要在其下创建名为 scripts 的子文件夹的目录,并在其中为每个脚本工具创建一个子文件夹,例如 /数据目录路径/scripts/MyScripts/MyScript1/MyScript1.js

具体位置取决于您的系统及其配置。

上面的脚本功能完整,可以被触发。然而,它在被触发时实际上什么也不做。此外,一旦被触发,脚本会一直保持活动状态,直到用户点击鼠标右键将其终止。为了改变这一点,让我们实现 beginEvent,向 QCAD 的命令行历史打印一些内容,然后终止该动作:

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
EAction.handleUserMessage("Hello World!");
this.terminate();
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

如果现在启动工具 其他 > 示例 > Minimal Example,它会向命令行历史打印 “Hello World!”(第 12 行),然后终止(第 14 行)。

如果某个脚本不需要任何用户交互,则可以用这样的脚本来添加一个执行某项操作后即终止的菜单项。这类动作的例子有 视图 > 自动缩放、选择 > 全选、编辑 > 删除 等等。

一旦脚本需要任何形式的用户交互,我们就需要实现更多的事件处理程序,并告诉脚本用户接下来需要做什么(例如选取一个实体或定义一个坐标)。在下一步中,我们进入一种状态,在该状态下动作期望用户提供一个坐标。然后我们在用户点击或输入的每个位置绘制一个圆。

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
this.pos = undefined;
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
var di = this.getDocumentInterface();
di.setClickMode(RAction.PickCoordinate);
};
ExMyMinimal.prototype.pickCoordinate = function(event, preview) {
this.pos = event.getModelPosition();
if (preview) {
this.updatePreview();
}
else {
this.applyOperation();
}
};
ExMyMinimal.prototype.getOperation = function(preview) {
var doc = this.getDocument();
var op = new RAddObjectOperation();
var circle = new RCircle(this.pos, 1);
op.addObject(shapeToEntity(doc, circle));
return op;
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

在 beginEvent 中,我们不再立即终止该动作,而是让它一直运行,直到用户将其终止(右键点击或按 Escape 键)。然后我们实现 pickCoordinate,用于存储鼠标光标的位置或所输入的坐标,并更新预览或应用操作(即添加圆)。每当用户移动鼠标以显示计划操作的预览时,都会调用 pickCoordinate。当用户点击或输入坐标时,它会以参数 preview 设为 false 的方式被调用,以表示已选取或输入了一个确定的坐标。

第 22 行的 updatePreview 预览 getOperation 返回的操作,而第 25 行的 applyOperation 则实际将操作应用到我们的文档中。

必须实现 getOperation,使其返回要预览或应用到文档的操作。这比我们上面在简单 API 中看到的稍微复杂一些。这是因为单个操作可用于添加多个对象、修改对象或删除对象。

我们示例中绘制的圆始终具有 1 个图形单位的半径(见第 33 行)。下一步,我们希望允许用户为圆输入一个半径。QCAD 通常使用顶部的选项工具栏来显示和更改此类工具参数。为此,我们需要定义想在选项工具栏中显示哪些控件,以及它们控制哪些参数。这可以通过一个 UI 文件来完成,即一个定义控件及其内容的 XML 文件。UI 文件可以使用一款名为 Qt Designer 的软件(作为 Qt 工具包的一部分提供)方便地进行设计。对于本示例,我们使用一个简单的 UI 文件,它也可以在文本编辑器中创建(文件 ExMyMinimal.ui):

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExMyMinimal</class>
<widget class="QWidget" name="ExMyMinimal">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="RadiusLabel">
<property name="text">
<string>&amp;Radius:</string>
</property>
<property name="buddy">
<cstring>Radius</cstring>
</property>
</widget>
</item>
<item>
<widget class="RMathLineEdit" name="Radius">
<property name="text">
<string notr="true">1</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RMathLineEdit</class>
<extends>QLineEdit</extends>
<header>RMathLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

该 UI 文件定义了两个控件:一个标签(QLabel)和一个行编辑框(RMathLineEdit)。重要的是行编辑框的名称(“Radius”)。控件通过此名称自动与我们的脚本关联。我们在脚本中要做的全部工作就是定义想使用哪个 UI 文件(第 9 行),并实现一个名为 slotRadiusChanged 的新事件处理程序,即 “slot” + [我们行编辑框的名称] + “Changed”(第 45 行):

include("scripts/EAction.js");
function ExMyMinimal(guiAction) {
EAction.call(this, guiAction);
this.pos = undefined;
this.radius = undefined;
this.setUiOptions("ExMyMinimal.ui");
}
ExMyMinimal.prototype = new EAction();
ExMyMinimal.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
var di = this.getDocumentInterface();
di.setClickMode(RAction.PickCoordinate);
};
ExMyMinimal.prototype.pickCoordinate = function(event, preview) {
this.pos = event.getModelPosition();
if (preview) {
this.updatePreview();
}
else {
this.applyOperation();
}
};
ExMyMinimal.prototype.getOperation = function(preview) {
if (isNull(this.pos) || isNull(this.radius)) {
return undefined;
}
var doc = this.getDocument();
var op = new RAddObjectOperation();
var circle = new RCircle(this.pos, this.radius);
op.addObject(shapeToEntity(doc, circle));
return op;
};
ExMyMinimal.prototype.slotRadiusChanged = function(v) {
this.radius = v;
this.updatePreview();
};
ExMyMinimal.init = function(basePath) {
var action = new RGuiAction(qsTr("&Minimal Example"), RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
action.setScriptFile(basePath + "/ExMyMinimal.js");
action.setGroupSortOrder(100000);
action.setSortOrder(0);
action.setWidgetNames(["ExamplesMenu"]);
};

每当用户输入新的半径时,都会调用这个新函数 slotRadiusChanged。它设置成员变量 this.radius,该变量随后会在 getOperation 中创建圆时使用。

QCAD 中的所有脚本都基于本教程中概述的这些概念之一。

由于 QCAD 中的每个工具在最顶层都是作为脚本实现的,因此有大量可用的示例脚本。你可以在我们的 git 仓库中找到它们。