• QCAD Plugin 开发


    QCAD Plugin 开发

    eryar@163.com

    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是一种简单高效的方式,扩展的功能包括交互创建、修改工具等等。

    wps_clip_image-21599

    用户甚至可以基于QCAD的应用框架开发出一个全新的程序。全新的程序可能是一个控制台工具或包含用户交互的CAD程序:

    wps_clip_image-12615

    如下图所示为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文件夹中。

    wps_clip_image-10035

    用JavaScript脚本开发QCAD插件最好办法就是先在QCAD中创建菜单和工具栏。下面就给出在QCAD中创建菜单和工具栏的步骤。首先要创建文件结构:

    l 对于新的顶层菜单,在QCAD目录中的scripts文件夹中创建一个新的文件夹。例如:创建一个“MyScripts”的文件夹;

    l 在MyScripts文件夹中创建一个文本文件“MyScripts.js”;

    l 在MyScripts文件夹中创建另外一个文件夹来提供一个命令Action,如命名为“MyAction”;

    l 在MyAction文件夹中创建一个文本文件MyAction.js,文件名必须和文件夹的名字一致;

    文件组织结构如下所示:

    wps_clip_image-11506

    将如下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,如下图所示:

    wps_clip_image-31967

    点击MyAction菜单,会在命令窗口中输出测试文字。

    3.C++

    既然QCAD的是基于Qt开发的,理所当然地应该支持C++开发,只是C++开发方式需要与JavaScript相结合。因为QCAD中一些交互功能封装到JavaScript脚本中了,所以只能通过在JavaScript中调用C++。这种方式QCAD也提供了一个例子,位于源码的

    supportexamplesexampleplugin中。主要是将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++来显示一个三维视图的窗口。

    wps_clip_image-25228

    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

    3. https://www.qcad.org/rsforum/viewforum.php?f=30&sid=8621b8249232845e54252ef7fa6b34ae

    4. JavaScript 高级程序设计

    5. C++ GUI Programming with Qt

  • 相关阅读:
    MVC4.0系统开发新手历程1
    hdu1205(类似 分布垃圾数列)
    python manage.py startapp app 时候报错No module named _sqlite3
    delphi 文件夹权限设置(执行一个小脚本的笨办法)
    FreeBSD 10安装KDE桌面环境简介(亲测bsdconfig命令有效)
    Qt的目录依赖问题----怎样生成一个绿色的Qt软件包
    Qt5位置相关函数异同详解(附源码)
    Qt 设置背景图片3种方法(三种方法:QPalette调色板,paintEvent,QSS)
    简单实现android和wp聊天
    进程间通讯之mmap文件共享
  • 原文地址:https://www.cnblogs.com/opencascade/p/QCAD_Plugin.html
Copyright © 2020-2023  润新知