Abstract. QCAD是基于GPL協定的開源CAD軟體,核心功能基于Qt使用C++開發,界面及其互動使用Javascript腳本進行開發。QCAD官方推薦開發其Plugin的方式為使用Javascript腳本的方式,因為QCAD的菜單及其對應的功能全部由Javascript實作。程式有時也需要和C++直接通信,如在QCAD中使用OpenCASCADE。本文主要介紹如何來開發QCAD的插件Plugin,進而能夠對QCAD進行擴充,做一些定制化的功能開發。 Key Words. QCAD Plugin, Javascript, C++, CAD, 3D
QCAD Plugin 開發
Abstract. QCAD是基于GPL協定的開源CAD軟體,核心功能基于Qt使用C++開發,界面及其互動使用Javascript腳本進行開發。QCAD官方推薦開發其Plugin的方式為使用Javascript腳本的方式,因為QCAD的菜單及其對應的功能全部由Javascript實作。程式有時也需要和C++直接通信,如在QCAD中使用OpenCASCADE。本文主要介紹如何來開發QCAD的插件Plugin,進而能夠對QCAD進行擴充,做一些定制化的功能開發。
Key Words. QCAD Plugin, Javascript, C++, CAD, 3D
1.Introduction
QCAD是GPL協定的開源CAD軟體,主要使用Javascript腳本進行開發,也可使用C++開發。與AutoCAD的多種開發方式一樣,支援AutoLisp腳本,也支援ObjectArx使用C++進行開發。不過開源的程式可以進行源碼Debug,遇到問題可以自己動手解決。而AutoCAD是閉源的,如果是正版使用者可以咨詢開發廠家,不能追根溯源。對于想學習CAD的人來說,建議可以多看這種開源軟體,學習CAD的開發原理。
本文主要介紹開源軟體QCAD的插件Plugin的開發方法。
2.Javascript
由于QCAD的菜單、互動都提供了Javascript的封裝,是以QCAD的大部分功能都是用Javascript腳本實作。使Javascript腳本對QCAD進行開發也是QCAD作者推薦的方式。
https://www.qcad.org/doc/qcad/latest/developer/_script_scope.html
QCAD程式架構提供了一很完整的強大的ECMAScript接口,QCAD幾乎所有的功能都可以通過腳本JavaScript來通路。ECMAScript(JavaScript)是很流行且易于學習的一種腳本語言。通過使用JavaScript腳本來擴充QCAD是一種簡單高效的方式,擴充的功能包括互動建立、修改工具等等。

使用者甚至可以基于QCAD的應用架構開發出一個全新的程式。全新的程式可能是一個控制台工具或包含使用者互動的CAD程式:
如下圖所示為QCAD中主要子產品的功能。Qt主要涉及通用的功能,與CAD沒有直接關系。QCAD程式架構QCAD Application Framework提供CAD專用功能,如CAD Core, DXF導入導出、強大的圖形視圖powerful graphics view等等。腳本ECMAScript可以用來快速的擴充CAD專用功能。QCAD使用者接口及所有的互動功能、幾乎所有的視窗都是通過腳本實作的。
QCAD包中的qcad.exe就是一個ECMAScript解釋器,并且封裝了Qt和QCAD的接口。當沒有任何ECMAScript腳本的時候,運作qcad.exe将會什麼也不做。Qcad.exe預設會查找“scripts/autostart.js”并執行。在QCAD中,autostart.js腳本初始化了所有的ECMAScript工具和使用者互動的功能,并啟動主程式。
QCAD中幾乎所有的視窗、菜單、工具欄都是通過ECMAScript腳本實作。這些腳本位于scripts檔案夾中。
用JavaScript腳本開發QCAD插件最好辦法就是先在QCAD中建立菜單和工具欄。下面就給出在QCAD中建立菜單和工具欄的步驟。首先要建立檔案結構:
l 對于新的頂層菜單,在QCAD目錄中的scripts檔案夾中建立一個新的檔案夾。例如:建立一個“MyScripts”的檔案夾;
l 在MyScripts檔案夾中建立一個文本檔案“MyScripts.js”;
l 在MyScripts檔案夾中建立另外一個檔案夾來提供一個指令Action,如命名為“MyAction”;
l 在MyAction檔案夾中建立一個文本檔案MyAction.js,檔案名必須和檔案夾的名字一緻;
檔案組織結構如下所示:
将如下JavaScript腳本複制到MyScripts.js檔案中:
/**
* Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
*
* This file is part of the QCAD project.
*
* QCAD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QCAD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QCAD.
*/
// MyScripts.js
// All actions are derived from class EAction, so we need to
// include this class definition here:
include("../EAction.js");
// Constructor calls base class constructor:
function MyScripts(guiAction) {
EAction.call(this, guiAction);
}
// Derive class MyScripts from class EAction:
MyScripts.prototype = new EAction();
// This static function returns a new or existing QMenu object.
MyScripts.getMenu = function() {
// EAction.getMenu is a helper function that returns an existing
// or new QMenu object with the given title and object name.
// The object name (here "MyScriptMenu") must be unique.
return EAction.getMenu(MyScripts.getTitle(), "MyScriptsMenu");
};
// This static function returns a new or existing QToolBar object.
MyScripts.getToolBar = function() {
// EAction.getToolBar is a helper function that returns an existing
// or new QToolBar object with the given title and object name.
// The object name (here "MyScriptToolBar") must be unique.
return EAction.getToolBar(MyScripts.getTitle(), "MyScriptToolBar");
};
// This static function defines and returns the title of the menu
// and toolbar.
// The qsTr function marks the title as a translatable string.
MyScripts.getTitle = function() {
return qsTr("My Scripts");
};
// Init creates the menu and toolbar on start.
MyScripts.init = function() {
MyScripts.getMenu();
MyScripts.getToolBar();
};
将如下腳本代碼複制到MyAction.js檔案中:
/**
* Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
*
* This file is part of the QCAD project.
*
* QCAD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QCAD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QCAD.
*/
// MyAction.js
// Include base class definition:
include("../MyScripts.js");
// Constructor calls base class constructor:
function MyAction(guiAction) {
MyScripts.call(this, guiAction);
}
// Derive class MyAction from class MyScripts:
MyAction.prototype = new MyScripts();
// This function is called immediately after the constructor when the user
// starts this action. For actions that don't require any user input (for
// example auto zoom), beginEvent does everything and then terminates the
// action.
MyAction.prototype.beginEvent = function() {
// call base class implementation of beginEvent:
MyScripts.prototype.beginEvent.call(this);
// get main application window:
var appWin = EAction.getMainWindow();
// print a message in the console of QCAD:
appWin.handleUserMessage("MyAction() is running...");
// terminate this action immediately:
this.terminate();
};
// MyAction.init() is called by QCAD to initialize the action and create
// the menu / toolbar for it.
MyAction.init = function(basePath) {
// Create a new RGuiAction (extended QAction):
var action = new RGuiAction("&My Action", RMainWindowQt.getMainWindow());
// This action requires a document to be open. If no document is
// open, the menu and tool button are grayed out:
action.setRequiresDocument(true);
// Define the script file that is executed when this action is
// launched:
action.setScriptFile(basePath + "/MyAction.js");
// Set the icon that is shown in the toolbar and on some platforms
// also in the menu:
action.setIcon(basePath + "/MyAction.svg");
// Set the command(s) that can be used on the command line to
// launch this action:
action.setDefaultCommands(["myaction"]);
// Define the sort order of this action. Menus and tool buttons are
// ordered by these values:
action.setGroupSortOrder(80100);
action.setSortOrder(200);
// Set list of widgets this action is added to
// (menus, tool bars, CAD tool bar panels):
action.setWidgetNames(["MyScriptsMenu"]);
};
啟動QCAD可以發現在菜單上有了MyScripts,如下圖所示:
點選MyAction菜單,會在指令視窗中輸出測試文字。
3.C++
既然QCAD的是基于Qt開發的,理所當然地應該支援C++開發,隻是C++開發方式需要與JavaScript相結合。因為QCAD中一些互動功能封裝到JavaScript腳本中了,是以隻能通過在JavaScript中調用C++。這種方式QCAD也提供了一個例子,位于源碼的
support\examples\exampleplugin中。主要是将C++的類暴露給JavaScript,使在JavaScript中可以調用C++的類及其方法。隻将頭檔案源碼列出如下:RExamplePlugin.h
#include <QDebug>
#include <QObject>
#include <QScriptEngine>
#include <QStringList>
#include "RActionAdapter.h"
#include "RDocumentInterface.h"
#include "RGuiAction.h"
#include "RMainWindow.h"
#include "RPluginInterface.h"
class MyAction : public RActionAdapter {
public:
MyAction(RGuiAction* guiAction) : RActionAdapter(guiAction) {}
static void factory(RGuiAction* guiAction) {
qDebug() << "MyAction::factory";
if (guiAction==NULL) {
qDebug("guiAction is NULL");
return;
}
RDocumentInterface* di = RMainWindow::getDocumentInterfaceStatic();
if (di==NULL) {
qDebug("di is NULL");
return;
}
di->setCurrentAction(new MyAction(guiAction));
}
virtual void beginEvent() {
qDebug() << "MyAction::beginEvent";
}
};
class MyClass : public QObject {
Q_OBJECT
public:
MyClass() : QObject(), i(0), d(0.0) {}
virtual int getInt() const {
return i;
}
virtual double getDouble() const {
return d;
}
virtual QString getString() const {
return s;
}
virtual void setInt(int v) {
i = v;
}
virtual void setDouble(int v) {
d = v;
}
virtual void setString(const QString& v) {
s = v;
}
void emitSignal() {
emit mySignal(i);
}
signals:
void mySignal(int code);
private:
int i;
double d;
QString s;
};
Q_DECLARE_METATYPE(MyClass*)
/**
* Script binding for MyClass.
*/
class EcmaMyClass {
public:
static void initEcma(QScriptEngine& engine);
static QScriptValue createMyClass(QScriptContext* context, QScriptEngine* engine);
static QScriptValue myClassToString(QScriptContext *context, QScriptEngine *engine);
static MyClass* getSelfMyClass(const QString& fName, QScriptContext* context);
static QScriptValue getInt(QScriptContext* context, QScriptEngine* engine);
static QScriptValue getDouble(QScriptContext* context, QScriptEngine* engine);
static QScriptValue getString(QScriptContext* context, QScriptEngine* engine);
static QScriptValue setInt(QScriptContext* context, QScriptEngine* engine);
static QScriptValue setDouble(QScriptContext* context, QScriptEngine* engine);
static QScriptValue setString(QScriptContext* context, QScriptEngine* engine);
static QScriptValue emitSignal(QScriptContext* context, QScriptEngine* engine);
};
class RExamplePlugin : public QObject, public RPluginInterface
{
Q_OBJECT
Q_INTERFACES(RPluginInterface)
#if QT_VERSION >= 0x050000
Q_PLUGIN_METADATA(IID "org.qcad.exampleplugin")
#endif
public:
virtual bool init();
virtual void uninit(bool) {}
virtual void postInit(InitStatus status);
virtual void initScriptExtensions(QScriptEngine& engine);
virtual RPluginInfo getPluginInfo();
};
從上述源碼可以看出,通過Qt的Plguin機制将C++的類暴露給JavaScript,進而在JavaScript中使用C++的功能。如下圖所示為在QCAD中通過JavaScript調用C++來顯示一個三維視圖的視窗。
4.Conclusion
綜上所述,QCAD二次開發的方式主要是以JavaScript為主。如果要在QCAD中使用C++,或者是使用C++的第三方庫,隻能是将相關的C++類暴露給JavaScirpt,這樣開發才是最簡單的。如果純用C++開發,一些互動功能是封裝在JavaScript中,反而效率不高。
通過在QCAD中使用OpenCASCADE之類的三維幾何核心,可以實作一些模組化、出圖的功能。
5.References
1. https://www.qcad.org/doc/qcad/latest/developer/_menus_and_tool_bars.html
2. https://www.qcad.org/doc/qcad/latest/developer/index.html#what_is