交互式脚本动作
脚本动作是这样一类脚本:它向菜单和/或工具栏添加一个条目,并且能够处理用户交互。脚本动作会一直保持活动状态,直到被用户终止或自行终止为止。
脚本动作一旦启动,就会处理各种事件,直到被终止为止。事件是指某件事情发生时所产生的东西。例如,当脚本动作启动时,会调用 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
Section titled “添加 beginEvent”上面的脚本功能完整,可以被触发。然而,它在被触发时实际上什么也不做。此外,一旦被触发,脚本会一直保持活动状态,直到用户点击鼠标右键将其终止。为了改变这一点,让我们实现 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 中看到的稍微复杂一些。这是因为单个操作可用于添加多个对象、修改对象或删除对象。
向选项工具栏添加控件
Section titled “向选项工具栏添加控件”我们示例中绘制的圆始终具有 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>&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 仓库中找到它们。