QCAD Plugin 開(kāi)發(fā)
Posted on 2018-06-21 23:28 eryar 閱讀(2688) 評(píng)論(0) 編輯 收藏 引用 所屬分類(lèi): 6.OthersQCAD Plugin 開(kāi)發(fā)
Abstract. QCAD是基于GPL協(xié)議的開(kāi)源CAD軟件,核心功能基于Qt使用C++開(kāi)發(fā),界面及其交互使用Javascript腳本進(jìn)行開(kāi)發(fā)。QCAD官方推薦開(kāi)發(fā)其Plugin的方式為使用Javascript腳本的方式,因?yàn)镼CAD的菜單及其對(duì)應(yīng)的功能全部由Javascript實(shí)現(xiàn)。程序有時(shí)也需要和C++直接通信,如在QCAD中使用OpenCASCADE。本文主要介紹如何來(lái)開(kāi)發(fā)QCAD的插件Plugin,從而能夠?qū)CAD進(jìn)行擴(kuò)展,做一些定制化的功能開(kāi)發(fā)。
Key Words. QCAD Plugin, Javascript, C++, CAD, 3D
1.Introduction
QCAD是GPL協(xié)議的開(kāi)源CAD軟件,主要使用Javascript腳本進(jìn)行開(kāi)發(fā),也可使用C++開(kāi)發(fā)。與AutoCAD的多種開(kāi)發(fā)方式一樣,支持AutoLisp腳本,也支持ObjectArx使用C++進(jìn)行開(kāi)發(fā)。不過(guò)開(kāi)源的程序可以進(jìn)行源碼Debug,遇到問(wèn)題可以自己動(dòng)手解決。而AutoCAD是閉源的,如果是正版用戶(hù)可以咨詢(xún)開(kāi)發(fā)廠家,不能追根溯源。對(duì)于想學(xué)習(xí)CAD的人來(lái)說(shuō),建議可以多看這種開(kāi)源軟件,學(xué)習(xí)CAD的開(kāi)發(fā)原理。
本文主要介紹開(kāi)源軟件QCAD的插件Plugin的開(kāi)發(fā)方法。
2.Javascript
由于QCAD的菜單、交互都提供了Javascript的封裝,所以QCAD的大部分功能都是用Javascript腳本實(shí)現(xiàn)。使Javascript腳本對(duì)QCAD進(jìn)行開(kāi)發(fā)也是QCAD作者推薦的方式。
https://www.qcad.org/doc/qcad/latest/developer/_script_scope.html
QCAD程序框架提供了一很完整的強(qiáng)大的ECMAScript接口,QCAD幾乎所有的功能都可以通過(guò)腳本JavaScript來(lái)訪問(wèn)。ECMAScript(JavaScript)是很流行且易于學(xué)習(xí)的一種腳本語(yǔ)言。通過(guò)使用JavaScript腳本來(lái)擴(kuò)展QCAD是一種簡(jiǎn)單高效的方式,擴(kuò)展的功能包括交互創(chuàng)建、修改工具等等。
用戶(hù)甚至可以基于QCAD的應(yīng)用框架開(kāi)發(fā)出一個(gè)全新的程序。全新的程序可能是一個(gè)控制臺(tái)工具或包含用戶(hù)交互的CAD程序:
如下圖所示為QCAD中主要模塊的功能。Qt主要涉及通用的功能,與CAD沒(méi)有直接關(guān)系。QCAD程序框架QCAD Application Framework提供CAD專(zhuān)用功能,如CAD Core, DXF導(dǎo)入導(dǎo)出、強(qiáng)大的圖形視圖powerful graphics view等等。腳本ECMAScript可以用來(lái)快速的擴(kuò)展CAD專(zhuān)用功能。QCAD用戶(hù)接口及所有的交互功能、幾乎所有的窗口都是通過(guò)腳本實(shí)現(xiàn)的。
QCAD包中的qcad.exe就是一個(gè)ECMAScript解釋器,并且封裝了Qt和QCAD的接口。當(dāng)沒(méi)有任何ECMAScript腳本的時(shí)候,運(yùn)行qcad.exe將會(huì)什么也不做。Qcad.exe默認(rèn)會(huì)查找“scripts/autostart.js”并執(zhí)行。在QCAD中,autostart.js腳本初始化了所有的ECMAScript工具和用戶(hù)交互的功能,并啟動(dòng)主程序。
QCAD中幾乎所有的窗口、菜單、工具欄都是通過(guò)ECMAScript腳本實(shí)現(xiàn)。這些腳本位于scripts文件夾中。
用JavaScript腳本開(kāi)發(fā)QCAD插件最好辦法就是先在QCAD中創(chuàng)建菜單和工具欄。下面就給出在QCAD中創(chuàng)建菜單和工具欄的步驟。首先要?jiǎng)?chuàng)建文件結(jié)構(gòu):
l 對(duì)于新的頂層菜單,在QCAD目錄中的scripts文件夾中創(chuàng)建一個(gè)新的文件夾。例如:創(chuàng)建一個(gè)“MyScripts”的文件夾;
l 在MyScripts文件夾中創(chuàng)建一個(gè)文本文件“MyScripts.js”;
l 在MyScripts文件夾中創(chuàng)建另外一個(gè)文件夾來(lái)提供一個(gè)命令A(yù)ction,如命名為“MyAction”;
l 在MyAction文件夾中創(chuàng)建一個(gè)文本文件MyAction.js,文件名必須和文件夾的名字一致;
文件組織結(jié)構(gòu)如下所示:
將如下JavaScript腳本復(fù)制到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(); };
將如下腳本代碼復(fù)制到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"]); };
啟動(dòng)QCAD可以發(fā)現(xiàn)在菜單上有了MyScripts,如下圖所示:
點(diǎn)擊MyAction菜單,會(huì)在命令窗口中輸出測(cè)試文字。
3.C++
既然QCAD的是基于Qt開(kāi)發(fā)的,理所當(dāng)然地應(yīng)該支持C++開(kāi)發(fā),只是C++開(kāi)發(fā)方式需要與JavaScript相結(jié)合。因?yàn)镼CAD中一些交互功能封裝到JavaScript腳本中了,所以只能通過(guò)在JavaScript中調(diào)用C++。這種方式QCAD也提供了一個(gè)例子,位于源碼的
support\examples\exampleplugin中。主要是將C++的類(lèi)暴露給JavaScript,使在JavaScript中可以調(diào)用C++的類(lèi)及其方法。只將頭文件源碼列出如下: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(); };
從上述源碼可以看出,通過(guò)Qt的Plguin機(jī)制將C++的類(lèi)暴露給JavaScript,從而在JavaScript中使用C++的功能。如下圖所示為在QCAD中通過(guò)JavaScript調(diào)用C++來(lái)顯示一個(gè)三維視圖的窗口。
4.Conclusion
綜上所述,QCAD二次開(kāi)發(fā)的方式主要是以JavaScript為主。如果要在QCAD中使用C++,或者是使用C++的第三方庫(kù),只能是將相關(guān)的C++類(lèi)暴露給JavaScirpt,這樣開(kāi)發(fā)才是最簡(jiǎn)單的。如果純用C++開(kāi)發(fā),一些交互功能是封裝在JavaScript中,反而效率不高。
通過(guò)在QCAD中使用OpenCASCADE之類(lèi)的三維幾何內(nèi)核,可以實(shí)現(xiàn)一些建模、出圖的功能。
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
3. https://www.qcad.org/rsforum/viewforum.php?f=30&sid=8621b8249232845e54252ef7fa6b34ae
4. JavaScript 高級(jí)程序設(shè)計(jì)
5. C++ GUI Programming with Qt
為了方便大家在移動(dòng)端也能看到我的博文和討論交流,現(xiàn)已注冊(cè)微信公眾號(hào),歡迎大家掃描下方二維碼關(guān)注。
