Embedding a view to another (QT) application?

Have some feature requests, feedback, cool stuff to share, or want to know where FreeCAD is going? This is the place.
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
wmayer
Founder
Posts: 20321
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Embedding a view to another (QT) application?

Post by wmayer »

Hi,

here are some code snippets that may be of interest for you.

To get the main window via PyQt:

Code: Select all

from PyQt4 import QtGui
from PyQt4 import QtCore
	
def getMainWindow():
	toplevel = QtGui.qApp.topLevelWidgets()
	for i in toplevel:
		if i.metaObject().className() == "Gui::MainWindow":
			return i
	raise Exception("No main window found")

mw=getMainWindow()
To get the View3DInventor view follows the same way.

Code: Select all

def get3dview(mw):
		childs=mw.findChildren(QtGui.QMainWindow)
		for i in childs:
			if i.metaObject().className()=="Gui::View3DInventor":
				return i
		return None

v=get3dview(mw)
This is the generated Python code from a Ui-file created with QtDesigner

Code: Select all

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sun Dec 27 11:18:56 2009
#      by: PyQt4 UI code generator 4.6
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(508, 436)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.mdiArea = QtGui.QMdiArea(self.centralwidget)
        self.mdiArea.setViewMode(QtGui.QMdiArea.TabbedView)
        self.mdiArea.setTabPosition(QtGui.QTabWidget.South)
        self.mdiArea.setObjectName("mdiArea")
        self.gridLayout.addWidget(self.mdiArea, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 508, 27))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
Now, create the main window (that should represent your main window) with an MDI area and "move" the 3d view to it

Code: Select all

ui=Ui_MainWindow()
my_mw=QtGui.QMainWindow()
ui.setupUi(my_mw)
ui.mdiArea.addSubWindow(v)
my_mw.show()
But note, there are still a lot of problems with this approach. So, the Qt event handling doesn't seem to work (no idea why) and if you use the 3d view's context-menu the application crashes.
So, I think Jürgen is right that it would be better to create your own 3d view SoQtExaminerViewer or SoQtViewer and "push" the content of FreeCAD's 3d view to your view.

An easy way to get the 3d representation of an object without reimplementing from scratch:

Code: Select all

App.activeDocument().addObject("Part::Box","myBox")
s=Gui.activeDocument().getObject("myBox").toString() # store as string
from pivy import coin
inp.setBuffer(s)
 node=coin.SoDB.readAll(inp) # restore from string
# add node to the scenegraph of your 3d view
Werner
jukkaaho
Posts: 14
Joined: Thu Dec 24, 2009 5:42 am

Re: Embedding a view to another (QT) application?

Post by jukkaaho »

Hi,

Yes, it's starts to look that using FreeCADGui isn't at this point counted as possibility. That last approach is worth of trying. Is it possible to gain that scenegraph without importing GUI part of application at all? addObject launches GUI and starts the mainApp loop, i think that's the actual problem so it should be done without it.
wmayer
Founder
Posts: 20321
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Embedding a view to another (QT) application?

Post by wmayer »

When using pivy's quarter module you can use this script

Code: Select all

#!/usr/bin/env python

###
# Copyright (c) 2002-2008 Kongsberg SIM
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

import os
import sys

from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication

from pivy.coin import SoInput, SoDB
from pivy.quarter import QuarterWidget

import FreeCAD, FreeCADGui

def getMainWindow():
	toplevel = QtGui.qApp.topLevelWidgets()
	for i in toplevel:
		if i.metaObject().className() == "Gui::MainWindow":
			return i
	raise Exception("No main window found")

class MdiQuarterWidget(QuarterWidget):
    def __init__(self, parent, sharewidget):
        QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)

    def loadFile(self, filename):
        in_ = SoInput()
        if (in_.openFile(str(filename.toLatin1()))):
            root = SoDB.readAll(in_)
        if (root):
            self.setSceneGraph(root)
            self.currentfile = filename
            self.setWindowTitle(filename)
            return True
        return False

    def currentFile(self):
        return self.currentfile

    def minimumSizeHint(self):
        return QtCore.QSize(640, 480)


class MdiMainWindow(QMainWindow):
    def __init__(self, qApp):
        QMainWindow.__init__(self)
        self._firstwidget = None
        self._workspace = QWorkspace()
        self.setCentralWidget(self._workspace)
        self.setAcceptDrops(True)
        self.setWindowTitle("Pivy Quarter MDI example")

        filemenu = self.menuBar().addMenu("&File")
        windowmenu = self.menuBar().addMenu("&Windows")

        fileopenaction = QAction("&Create Box", self)
        fileexitaction = QAction("E&xit", self)
        tileaction = QAction("Tile", self)
        cascadeaction = QAction("Cascade", self)

        filemenu.addAction(fileopenaction)
        filemenu.addAction(fileexitaction)
        windowmenu.addAction(tileaction)
        windowmenu.addAction(cascadeaction)

        self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
        self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
        self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
        self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)

        windowmapper = QtCore.QSignalMapper(self)
        self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)

        self.dirname = os.curdir        

    def dragEnterEvent(self, event):
        # just accept anything...
        event.acceptProposedAction()

    def dropEvent(self, event):
        mimedata = event.mimeData()
        if mimedata.hasUrls():
            path = mimedata.urls().takeFirst().path()
            self.open_path(path)

    def closeEvent(self, event):
        self._workspace.closeAllWindows()

    def open(self):
        self.open_path(QFileDialog.getOpenFileName(self, "", self.dirname))

    def open_path(self, filename):
        self.dirname = os.path.dirname(str(filename.toLatin1()))
        if not filename.isEmpty():
            existing = self.findMdiChild(filename)
            if existing:
                self._workspace.setActiveWindow(existing)
                return
        child = self.createMdiChild()
        if (child.loadFile(filename)):
            self.statusBar().showMessage("File loaded", 2000)
            child.show()
        else:
            child.close()

    def findMdiChild(self, filename):
        canonicalpath = QtCore.QFileInfo(filename).canonicalFilePath()
        for window in self._workspace.windowList():
            mdiwidget = window
            if mdiwidget.currentFile() == canonicalpath:
                return mdiwidget
        return 0;

    def createMdiChild(self):
        widget = MdiQuarterWidget(None, self._firstwidget)
        self._workspace.addWindow(widget)
        if not self._firstwidget:
            self._firstwidget = widget
        return widget

    def createBoxInFreeCAD(self):
        widget = MdiQuarterWidget(None, self._firstwidget)
        self._workspace.addWindow(widget)
        if not self._firstwidget:
            self._firstwidget = widget
        widget.show()
        doc = FreeCAD.newDocument()
        doc.addObject("Part::Box","myBox")
        iv_=FreeCADGui.getDocument(doc.Name).getObject("myBox").toString()
        in_ = SoInput()
        in_.setBuffer(iv_)
        root = SoDB.readAll(in_)
        if (root):
            widget.setSceneGraph(root)


def main():
    app = QApplication(sys.argv)

    mdi = MdiMainWindow(app)    
    mdi.show()
    FreeCADGui.showMainWindow() # setup the GUI stuff of FreeCAD
    mw=getMainWindow()
    mw.hide() # hide all
    if len(sys.argv)==2:
        mdi.open_path(QtCore.QString(sys.argv[1]))
    sys.exit(app.exec_())


def show():
    mdi = MdiMainWindow(QtGui.qApp)    
    mdi.show()
    mw=getMainWindow()
    #mw.hide() # hide all

if __name__ == '__main__':
    main()
The original script is taken from http://source.coin3d.org/viewvc.py/Pivy ... y?view=log
wmayer
Founder
Posts: 20321
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Embedding a view to another (QT) application?

Post by wmayer »

Yes, it's starts to look that using FreeCADGui isn't at this point counted as possibility. That last approach is worth of trying. Is it possible to gain that scenegraph without importing GUI part of application at all? addObject launches GUI and starts the mainApp loop, i think that's the actual problem so it should be done without it.
No, it's not possible to get the scenegraph without the GUI. Maybe we can add a convenience function to the FreeCADGui module to return the Inventor stuff as string of a given object. This approach should work then without showing up the FreeCAD main window or starting its eventloop.
User avatar
yorik
Founder
Posts: 13665
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Embedding a view to another (QT) application?

Post by yorik »

Totally interesting demonstration Werner... I created a new page on the wiki with it:
http://sourceforge.net/apps/mediawiki/f ... FreeCADGui
User avatar
jriegel
Founder
Posts: 3369
Joined: Sun Feb 15, 2009 5:29 pm
Location: Ulm, Germany
Contact:

Re: Embedding a view to another (QT) application?

Post by jriegel »

Thats a good idea Werner,
a method in the GuiModule which instantiate the ViewProvider without register him to the
View. Then you can get the scene graph of the object without entering the event loop.

But the gui design was never intended that way? Can have serious side effects....

Nice articel Yorik!
Stop whining - start coding!
jukkaaho
Posts: 14
Joined: Thu Dec 24, 2009 5:42 am

Re: Embedding a view to another (QT) application?

Post by jukkaaho »

Basicly it's one kind of syntax to define object shapes, so is that more FreeCAD than FreeCADGui feature?

I get AssertionError when trying that code pasted:

Code: Select all

Assertion failed: (parentType != Base::Type::badType()), function initSubclass, file BaseClass.cpp, line 97.
As said, i think that starting FreeCADGui and it's event loop is the thing that is making embedding objects to other applications so complicated. Maybe because when FreeCADGui initialized it creates QApplication and for that reason making second crashes interpreter? I think there shouldn't be need to import FreeCADGui if the FreeCAD isn't needed to view object but just to get a proper data to present them in some other system.

Jukka
User avatar
jriegel
Founder
Posts: 3369
Joined: Sun Feb 15, 2009 5:29 pm
Location: Ulm, Germany
Contact:

Re: Embedding a view to another (QT) application?

Post by jriegel »

Mhh,
all we discussed so far is basically hacking. I can not really think of a way to get such a construct
stable to run without running in a lot of serious issues.

And so whats the use case? You whant to run 90% of FreeCAD without running FreeCAD?

The design of FreeCAD offers two ways of getting things done. The script way (which mean
the interpreter tells the application what to do) and the GUI way (the user tells the application
with mouse clicks and menues what to do). The GUI is C++ and Qt and the scripting is Python.
At some Point you have to decide what you want to do.

If you want to do a Gui application we offer a lot of ways to do it with and inside of FreeCAD.
If that all is not enough for you I have to say, we can not completely turn around the design
of FreeCAd for you....
Stop whining - start coding!
User avatar
yorik
Founder
Posts: 13665
Joined: Tue Feb 17, 2009 9:16 pm
Location: Brussels
Contact:

Re: Embedding a view to another (QT) application?

Post by yorik »

But I think an easy way to get the coin representation of an object would be pretty useful, even inside FreeCAD itself (I'm thinking of an exemple of a hexahedron we had on this forum a month ago or so)

A python feature that has a shape in it, if you had a way to get a coin object from it, you could use it in your own python feature view provider...
And if that was available without firing up the interface would effectively make freecad's graphical output totally embeddable in any other application... Why would then someone need to import FreeCAD's own 3D view?
wmayer
Founder
Posts: 20321
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Embedding a view to another (QT) application?

Post by wmayer »

Hi to all,

the examples I have posted here do also only partially work for me. I do also get this assertion failure. I have to check what the problem is here because actually this _should_ work.
Basicly it's one kind of syntax to define object shapes, so is that more FreeCAD than FreeCADGui feature?
That's not possible because it would totally break the design of FreeCAD. Coin & SoQt IS GUI and thus belongs to the FreeCADGui module, not to FreeCAD.

And actually the eventloop, I think, isn't the big problem because the Qt internals are implemented in such a flexible manner that it interacts seamlessly with the eventloop of other applications. As said before only the requirement of glib or Win32 API must be fulfilled. I have tested this in the past with some other toolkits and it worked fine.

I think a simple way to get the Inventor structure of a given object is by creating a temporary view provider object, getting this structure as string and deleting the view provider immediately. There is no need to add it to our 3d view and thus the eventloop issue isn't a problem anymore (just in case it is one which I don't think ATM). This approach should also work for other applications whose eventloop isn't compatible to that of Qt.

BTW, when loading the FreeCADGui python module no QApplication object is created. All what is done at this point is initializing the Coin&SoQt subsystem, creating the FreeCAD application objects and initializing the internal FreeCAD types.
In the method showMainWindow() we check for an existing QApplication instance and create one if none was found.

So, to circumvent the eventloop stuff we can have a second method e.g. setupWithoutGUI (stupid name :)) which closes the signal/slot connections from App to GUI so that when creating documents and objects the GUI simply does nothing and no QWidget gets ever created.

I think Yorik is right that we should achieve to get the view representation of an object without using the 3d view.

Werner
Post Reply