Difference between revisions of "Embedding FreeCADGui/cs"

From FreeCAD Documentation
Jump to navigation Jump to search
(Updating to match new version of source page)
(Updating to match new version of source page)
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
You already know that you can [[Embedding FreeCAD|import the FreeCAD module]] into a python application, and use all its tools from the host application. But the FreeCAD User Interface (GUI) can also be imported as a python module. Normally you can only import the complete interface as a whole, not pieces of it. That is because the FreeCAD interface system is not only made of independent widgets and toolbars, but is a complex construction where several invisible components (such as the selection system, etc) are needed for the main 3D view to be able to function.
+
<languages/>
 +
 
 +
== Introduction ==
 +
 
 +
It is possible to [[Embedding_FreeCAD|import the FreeCAD module]] into a [[Python|Python]] application, and use all its tools from the host application, but the graphical user interface (GUI) can be imported as a Python module as well. Normally you can only import the complete interface as a whole, not pieces of it; this is because the FreeCAD interface system is not only made of independent widgets and toolbars, but is a complex construction where several invisible components (such as the selection system, etc) are needed for the main [[3D_view|3D view]] to be able to function.
  
 
But, with a bit of hacking, it is possible to import the whole FreeCAD interface, then move the 3D view from it to your own Qt application. We show here 3 different methods.
 
But, with a bit of hacking, it is possible to import the whole FreeCAD interface, then move the 3D view from it to your own Qt application. We show here 3 different methods.
Line 7: Line 11:
 
Be aware that there are a lot of problems with this approach. 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. A better way could be to create your own 3d view SoQtExaminerViewer or SoQtViewer and "push" the content of FreeCAD's 3d view to your view, as shown in the other sections below.
 
Be aware that there are a lot of problems with this approach. 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. A better way could be to create your own 3d view SoQtExaminerViewer or SoQtViewer and "push" the content of FreeCAD's 3d view to your view, as shown in the other sections below.
  
First, get the main window via PyQt:
+
First, get the main window via [[PySide|PySide]]:
<syntaxhighlight>
+
{{Code|code=
 
from PySide import QtGui
 
from PySide import QtGui
 
from PySide import QtCore
 
from PySide import QtCore
 
+
 
 
def getMainWindow():
 
def getMainWindow():
 
   toplevel = QtGui.qApp.topLevelWidgets()
 
   toplevel = QtGui.qApp.topLevelWidgets()
Line 19: Line 23:
 
   raise Exception("No main window found")
 
   raise Exception("No main window found")
  
mw=getMainWindow()
+
mw = getMainWindow()
</syntaxhighlight>
+
}}
 +
 
 
Then get the View3DInventor view the same way:
 
Then get the View3DInventor view the same way:
<syntaxhighlight>
+
{{Code|code=
 
def get3dview(mw):
 
def get3dview(mw):
 
       childs=mw.findChildren(QtGui.QMainWindow)
 
       childs=mw.findChildren(QtGui.QMainWindow)
 
       for i in childs:
 
       for i in childs:
         if i.metaObject().className()=="Gui::View3DInventor":
+
         if i.metaObject().className() == "Gui::View3DInventor":
 
             return i
 
             return i
 
       return None
 
       return None
  
v=get3dview(mw)
+
v = get3dview(mw)
</syntaxhighlight>
+
}}
 +
 
 
The following code is generated automatically, by [[Dialog_creation|creating a Ui-file with QtDesigner]], and converting it to python code with the pyuic tool:
 
The following code is generated automatically, by [[Dialog_creation|creating a Ui-file with QtDesigner]], and converting it to python code with the pyuic tool:
<syntaxhighlight>
+
{{Code|code=
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8 -*-
  
Line 76: Line 82:
 
     def retranslateUi(self, MainWindow):
 
     def retranslateUi(self, MainWindow):
 
         MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
 
         MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
</syntaxhighlight>
+
}}
Then, create a main window that should be your application's main window, apply the UI setup above to it in order to add an MDI area and "move" our 3d view to it
+
 
<syntaxhighlight>
+
Then, create a main window that should be your application's main window, apply the UI setup above to it in order to add an MDI area and "move" our [[3D_view|3D view]] to it.
ui=Ui_MainWindow()
+
{{Code|code=
my_mw=QtGui.QMainWindow()
+
ui = Ui_MainWindow()
 +
my_mw = QtGui.QMainWindow()
 
ui.setupUi(my_mw)
 
ui.setupUi(my_mw)
 
ui.mdiArea.addSubWindow(v)
 
ui.mdiArea.addSubWindow(v)
 
my_mw.show()
 
my_mw.show()
</syntaxhighlight>
+
}}
 +
 
 
==Creating a soGui Examiner Viewer==
 
==Creating a soGui Examiner Viewer==
  
Alternatively, you can also use the FreeCADGui module to extract a coin/openInventor representation of the objects of your scene, then use that coin data in an external viewer (your application). Here is an easy way to get the 3d representation of an object:
+
Alternatively, you can also use the FreeCADGui module to extract a OpenInventor (Coin) representation of the objects of your scene, then use that data in an external viewer (your application). Here is an easy way to get the 3D representation of an object.
<syntaxhighlight>
+
{{Code|code=
    FreeCAD.activeDocument().addObject("Part::Box","myBox")
+
from pivy import coin
    s=FreeCADGui.activeDocument().getObject("myBox").toString() # store as string
+
import FreeCAD as App
    from pivy import coin
+
 
    inp.setBuffer(s)
+
box = App.activeDocument().addObject("Part::Box", "myBox")
    myNode=coin.SoDB.readAll(inp) # restore from string
+
s = box.ViewObject.toString() # store as string
</syntaxhighlight>
+
inp = coin.SoInput()
Then, create a standalone viewer with pivy:
+
inp.setBuffer(s)
<syntaxhighlight>
+
myNode = coin.SoDB.readAll(inp) # restore from string
 +
}}
 +
 
 +
Then, create a standalone viewer with [[Pivy|Pivy]]:
 +
{{Code|code=
 
from pivy.sogui import *
 
from pivy.sogui import *
 
from pivy.coin import *
 
from pivy.coin import *
Line 105: Line 117:
 
     # If unsuccessful, exit.
 
     # If unsuccessful, exit.
 
     myWindow = SoGui.init(sys.argv[0])
 
     myWindow = SoGui.init(sys.argv[0])
     if myWindow == None: sys.exit(1)
+
     if myWindow == None:
 +
        sys.exit(1)
  
 
     # Make an empty scene and add our node to it
 
     # Make an empty scene and add our node to it
Line 121: Line 134:
 
     SoGui.show(myWindow) # Display main window
 
     SoGui.show(myWindow) # Display main window
 
     SoGui.mainLoop()    # Main Coin event loop
 
     SoGui.mainLoop()    # Main Coin event loop
</syntaxhighlight>
+
}}
 +
 
 
Then you just need to run your viewer:
 
Then you just need to run your viewer:
<syntaxhighlight>
+
{{Code|code=
 
myViewer()
 
myViewer()
</syntaxhighlight>
+
}}
 +
 
 
==Using the quarter module==
 
==Using the quarter module==
  
Instead of using the sogui viewer, you can also use the more modern quarter module. This is probably the best solution of the 3.
+
Instead of using the sogui viewer, you can also use the more modern quarter module. This is generally the best of the three options.
<syntaxhighlight>
+
{{Code|code=
    #!/usr/bin/env python
+
#!/usr/bin/env python
 
   
 
   
    ###
+
###
    # Copyright (c) 2002-2008 Kongsberg SIM
+
# Copyright (c) 2002-2008 Kongsberg SIM
    #
+
#
    # Permission to use, copy, modify, and distribute this software for any
+
# Permission to use, copy, modify, and distribute this software for any
    # purpose with or without fee is hereby granted, provided that the above
+
# purpose with or without fee is hereby granted, provided that the above
    # copyright notice and this permission notice appear in all copies.
+
# copyright notice and this permission notice appear in all copies.
    #
+
#
    # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    #
+
#
 
   
 
   
    import os
+
import os
    import sys
+
import sys
 
   
 
   
    from PyQt4 import QtCore, QtGui
+
from PySide import QtCore, QtGui
    from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
+
from PySide.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
 
   
 
   
    from pivy.coin import SoInput, SoDB
+
from pivy.coin import SoInput, SoDB
    from pivy.quarter import QuarterWidget
+
from pivy.quarter import QuarterWidget
 
   
 
   
    import FreeCAD, FreeCADGui
+
import FreeCAD, FreeCADGui
 
   
 
   
    def getMainWindow():
+
def getMainWindow():
      toplevel = QtGui.qApp.topLevelWidgets()
+
  toplevel = QtGui.qApp.topLevelWidgets()
      for i in toplevel:
+
  for i in toplevel:
          if i.metaObject().className() == "Gui::MainWindow":
+
      if i.metaObject().className() == "Gui::MainWindow":
            return i
+
        return i
      raise Exception("No main window found")
+
  raise Exception("No main window found")
 
   
 
   
    class MdiQuarterWidget(QuarterWidget):
+
class MdiQuarterWidget(QuarterWidget):
        def __init__(self, parent, sharewidget):
+
    def __init__(self, parent, sharewidget):
            QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
+
        QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
 
   
 
   
        def loadFile(self, filename):
+
    def loadFile(self, filename):
            in_ = SoInput()
+
        in_ = SoInput()
            if (in_.openFile(str(filename.toLatin1()))):
+
        if (in_.openFile(str(filename.toLatin1()))):
                root = SoDB.readAll(in_)
+
            root = SoDB.readAll(in_)
            if (root):
+
        if (root):
                self.setSceneGraph(root)
+
            self.setSceneGraph(root)
                self.currentfile = filename
+
            self.currentfile = filename
                self.setWindowTitle(filename)
+
            self.setWindowTitle(filename)
                return True
+
            return True
            return False
+
        return False
 
   
 
   
        def currentFile(self):
+
    def currentFile(self):
            return self.currentfile
+
        return self.currentfile
 
   
 
   
        def minimumSizeHint(self):
+
    def minimumSizeHint(self):
            return QtCore.QSize(640, 480)
+
        return QtCore.QSize(640, 480)
 
   
 
   
    class MdiMainWindow(QMainWindow):
+
class MdiMainWindow(QMainWindow):
        def __init__(self, qApp):
+
    def __init__(self, qApp):
            QMainWindow.__init__(self)
+
        QMainWindow.__init__(self)
            self._firstwidget = None
+
        self._firstwidget = None
            self._workspace = QWorkspace()
+
        self._workspace = QWorkspace()
            self.setCentralWidget(self._workspace)
+
        self.setCentralWidget(self._workspace)
            self.setAcceptDrops(True)
+
        self.setAcceptDrops(True)
            self.setWindowTitle("Pivy Quarter MDI example")
+
        self.setWindowTitle("Pivy Quarter MDI example")
 
   
 
   
            filemenu = self.menuBar().addMenu("&File")
+
        filemenu = self.menuBar().addMenu("&File")
            windowmenu = self.menuBar().addMenu("&Windows")
+
        windowmenu = self.menuBar().addMenu("&Windows")
 
   
 
   
            fileopenaction = QAction("&Create Box", self)
+
        fileopenaction = QAction("&Create Box", self)
            fileexitaction = QAction("E&xit", self)
+
        fileexitaction = QAction("E&xit", self)
            tileaction = QAction("Tile", self)
+
        tileaction = QAction("Tile", self)
            cascadeaction = QAction("Cascade", self)
+
        cascadeaction = QAction("Cascade", self)
 
   
 
   
            filemenu.addAction(fileopenaction)
+
        filemenu.addAction(fileopenaction)
            filemenu.addAction(fileexitaction)
+
        filemenu.addAction(fileexitaction)
            windowmenu.addAction(tileaction)
+
        windowmenu.addAction(tileaction)
            windowmenu.addAction(cascadeaction)
+
        windowmenu.addAction(cascadeaction)
 
   
 
   
            self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
+
        self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
            self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
+
        self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
            self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
+
        self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
            self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
+
        self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
 
   
 
   
            windowmapper = QtCore.QSignalMapper(self)
+
        windowmapper = QtCore.QSignalMapper(self)
            self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
+
        self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
 
   
 
   
            self.dirname = os.curdir       
+
        self.dirname = os.curdir       
 
   
 
   
        def dragEnterEvent(self, event):
+
    def dragEnterEvent(self, event):
            # just accept anything...
+
        # just accept anything...
            event.acceptProposedAction()
+
        event.acceptProposedAction()
 
   
 
   
        def dropEvent(self, event):
+
    def dropEvent(self, event):
            mimedata = event.mimeData()
+
        mimedata = event.mimeData()
            if mimedata.hasUrls():
+
        if mimedata.hasUrls():
                path = mimedata.urls().takeFirst().path()
+
            path = mimedata.urls().takeFirst().path()
                self.open_path(path)
+
            self.open_path(path)
 
   
 
   
        def closeEvent(self, event):
+
    def closeEvent(self, event):
            self._workspace.closeAllWindows()
+
        self._workspace.closeAllWindows()
 
   
 
   
        def open(self):
+
    def open(self):
            self.open_path(QFileDialog.getOpenFileName(self, "", self.dirname))
+
        self.open_path(QFileDialog.getOpenFileName(self, "", self.dirname))
 
   
 
   
        def open_path(self, filename):
+
    def open_path(self, filename):
            self.dirname = os.path.dirname(str(filename.toLatin1()))
+
        self.dirname = os.path.dirname(str(filename.toLatin1()))
            if not filename.isEmpty():
+
        if not filename.isEmpty():
                existing = self.findMdiChild(filename)
+
            existing = self.findMdiChild(filename)
                if existing:
+
            if existing:
                    self._workspace.setActiveWindow(existing)
+
                self._workspace.setActiveWindow(existing)
                    return
+
                return
            child = self.createMdiChild()
+
        child = self.createMdiChild()
            if (child.loadFile(filename)):
+
        if (child.loadFile(filename)):
                self.statusBar().showMessage("File loaded", 2000)
+
            self.statusBar().showMessage("File loaded", 2000)
                child.show()
+
            child.show()
            else:
+
        else:
                child.close()
+
            child.close()
 
   
 
   
        def findMdiChild(self, filename):
+
    def findMdiChild(self, filename):
            canonicalpath = QtCore.QFileInfo(filename).canonicalFilePath()
+
        canonicalpath = QtCore.QFileInfo(filename).canonicalFilePath()
            for window in self._workspace.windowList():
+
        for window in self._workspace.windowList():
                mdiwidget = window
+
            mdiwidget = window
                if mdiwidget.currentFile() == canonicalpath:
+
            if mdiwidget.currentFile() == canonicalpath:
                    return mdiwidget
+
                return mdiwidget
            return 0;
+
        return 0;
 
   
 
   
        def createMdiChild(self):
+
    def createMdiChild(self):
            widget = MdiQuarterWidget(None, self._firstwidget)
+
        widget = MdiQuarterWidget(None, self._firstwidget)
            self._workspace.addWindow(widget)
+
        self._workspace.addWindow(widget)
            if not self._firstwidget:
+
        if not self._firstwidget:
                self._firstwidget = widget
+
            self._firstwidget = widget
            return widget
+
        return widget
 
   
 
   
        def createBoxInFreeCAD(self):
+
    def createBoxInFreeCAD(self):
            widget = MdiQuarterWidget(None, self._firstwidget)
+
        widget = MdiQuarterWidget(None, self._firstwidget)
            self._workspace.addWindow(widget)
+
        self._workspace.addWindow(widget)
            if not self._firstwidget:
+
        if not self._firstwidget:
                self._firstwidget = widget
+
            self._firstwidget = widget
            widget.show()
+
        widget.show()
            doc = FreeCAD.newDocument()
+
        doc = FreeCAD.newDocument()
            doc.addObject("Part::Box","myBox")
+
        doc.addObject("Part::Box","myBox")
            iv_=FreeCADGui.getDocument(doc.Name).getObject("myBox").toString()
+
        iv_=FreeCADGui.getDocument(doc.Name).getObject("myBox").toString()
            in_ = SoInput()
+
        in_ = SoInput()
            in_.setBuffer(iv_)
+
        in_.setBuffer(iv_)
            root = SoDB.readAll(in_)
+
        root = SoDB.readAll(in_)
            if (root):
+
        if (root):
                widget.setSceneGraph(root)
+
            widget.setSceneGraph(root)
 
   
 
   
    def main():
+
def main():
        app = QApplication(sys.argv)  
+
    app = QApplication(sys.argv)  
        mdi = MdiMainWindow(app)   
+
    mdi = MdiMainWindow(app)   
        mdi.show()
+
    mdi.show()
        FreeCADGui.showMainWindow() # setup the GUI stuff of FreeCAD
+
    FreeCADGui.showMainWindow() # setup the GUI stuff of FreeCAD
        mw=getMainWindow()
+
    mw=getMainWindow()
        mw.hide() # hide all
+
    mw.hide() # hide all
        if len(sys.argv)==2:
+
    if len(sys.argv)==2:
            mdi.open_path(QtCore.QString(sys.argv[1]))
+
        mdi.open_path(QtCore.QString(sys.argv[1]))
        sys.exit(app.exec_())
+
    sys.exit(app.exec_())
 
   
 
   
    def show():
+
def show():
        mdi = MdiMainWindow(QtGui.qApp)   
+
    mdi = MdiMainWindow(QtGui.qApp)   
        mdi.show()
+
    mdi.show()
        mw=getMainWindow()
+
    mw=getMainWindow()
        #mw.hide() # hide all
+
    #mw.hide() # hide all
 
   
 
   
    if __name__ == '__main__':
+
if __name__ == '__main__':
        main()
+
    main()
</syntaxhighlight>
+
}}
 +
 
 
== Without even firing up the FreeCAD Gui ==
 
== Without even firing up the FreeCAD Gui ==
  
Starting from FreeCAD rev2760, it is now possible to obtain the coin representation of any FreeCAD object without opening the main window. This makes it extremely easy to implement your own viewer and transparently have FreeCAD updating it. After importing the FreeCADGui module, you need to fire it up with the setupWithoutGUI() method, after which you can use all of FreeCAD's view providers to obtain coin/openInventor nodes.
+
Starting from FreeCAD rev2760 (2010, [https://forum.freecadweb.org/viewtopic.php?f=8&t=203&start=20#p1226 1]), it is now possible to obtain the coin representation of any FreeCAD object without opening the main window. This makes it extremely easy to implement your own viewer and transparently have FreeCAD updating it. After importing the {{incode|FreeCADGui}} module, you need to fire it up with the {{incode|setupWithoutGUI()}} method, after which you can use all of FreeCAD's view providers to obtain OpenInventor (Coin) nodes.
<syntaxhighlight>
+
{{Code|code=
    import os, sys, FreeCAD, FreeCADGui
+
import os, sys, FreeCAD, FreeCADGui
    from PyQt4 import QtCore, QtGui
+
from PyQt4 import QtCore, QtGui
    from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
+
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
    from pivy.coin import SoInput, SoDB, sogui
+
from pivy.coin import SoInput, SoDB, sogui
 
   
 
   
    class MdiMainWindow(QMainWindow):
+
class MdiMainWindow(QMainWindow):
        def __init__(self, qApp):
+
    def __init__(self, qApp):
            QMainWindow.__init__(self)
+
        QMainWindow.__init__(self)
            self._firstwidget = None
+
        self._firstwidget = None
            self._workspace = QWorkspace()
+
        self._workspace = QWorkspace()
            self.setCentralWidget(self._workspace)
+
        self.setCentralWidget(self._workspace)
            self.setAcceptDrops(True)
+
        self.setAcceptDrops(True)
            self.setWindowTitle("Pivy Quarter MDI example")
+
        self.setWindowTitle("Pivy Quarter MDI example")
            self.viewers=[]
+
        self.viewers=[]
 
   
 
   
            filemenu = self.menuBar().addMenu("&File")
+
        filemenu = self.menuBar().addMenu("&File")
            windowmenu = self.menuBar().addMenu("&Windows")
+
        windowmenu = self.menuBar().addMenu("&Windows")
 
   
 
   
            fileopenaction = QAction("&Create Box", self)
+
        fileopenaction = QAction("&Create Box", self)
            fileexitaction = QAction("E&xit", self)
+
        fileexitaction = QAction("E&xit", self)
            tileaction = QAction("Tile", self)
+
        tileaction = QAction("Tile", self)
            cascadeaction = QAction("Cascade", self)
+
        cascadeaction = QAction("Cascade", self)
 
   
 
   
            filemenu.addAction(fileopenaction)
+
        filemenu.addAction(fileopenaction)
            filemenu.addAction(fileexitaction)
+
        filemenu.addAction(fileexitaction)
            windowmenu.addAction(tileaction)
+
        windowmenu.addAction(tileaction)
            windowmenu.addAction(cascadeaction)
+
        windowmenu.addAction(cascadeaction)
 
   
 
   
            self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
+
        self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
            self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
+
        self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
            self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
+
        self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
            self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
+
        self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
 
   
 
   
            windowmapper = QtCore.QSignalMapper(self)
+
        windowmapper = QtCore.QSignalMapper(self)
            self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
+
        self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
 
   
 
   
        def closeEvent(self, event):
+
    def closeEvent(self, event):
            self._workspace.closeAllWindows()
+
        self._workspace.closeAllWindows()
 
   
 
   
        def createBoxInFreeCAD(self):
+
    def createBoxInFreeCAD(self):
            widget = QtGui.QWidget(self._firstwidget)
+
        widget = QtGui.QWidget(self._firstwidget)
            viewer = sogui.SoGuiExaminerViewer(widget)
+
        viewer = sogui.SoGuiExaminerViewer(widget)
            self._workspace.addWindow(widget)
+
        self._workspace.addWindow(widget)
            if not self._firstwidget:
+
        if not self._firstwidget:
                self._firstwidget = widget
+
            self._firstwidget = widget
            widget.show()
+
        widget.show()
            self.viewers.append(viewer)
+
        self.viewers.append(viewer)
            doc = FreeCAD.newDocument()
+
        doc = FreeCAD.newDocument()
            obj=doc.addObject("Part::Box","myBox")
+
        obj=doc.addObject("Part::Box","myBox")
            doc.recompute()
+
        doc.recompute()
            root=FreeCADGui.subgraphFromObject(obj)
+
        root=FreeCADGui.subgraphFromObject(obj)
            viewer.setSceneGraph(root)
+
        viewer.setSceneGraph(root)
 
   
 
   
    def main():
+
def main():
        app = QApplication(sys.argv)
+
    app = QApplication(sys.argv)
        mdi = MdiMainWindow(app)   
+
    mdi = MdiMainWindow(app)   
        mdi.show()
+
    mdi.show()
        FreeCADGui.setupWithoutGUI()
+
    FreeCADGui.setupWithoutGUI()
        sys.exit(app.exec_())
+
    sys.exit(app.exec_())
 
   
 
   
    if __name__ == '__main__':
+
if __name__ == '__main__':
        main()
+
    main()
</syntaxhighlight>
+
}}
 +
 
 
Or, if using pivy's sogui module doesn't work for you (the sogui module is becoming obsoleted and the coin developers are now favoring the new quarter library, which has much better interaction with qt), this is the same script but with using quarter:
 
Or, if using pivy's sogui module doesn't work for you (the sogui module is becoming obsoleted and the coin developers are now favoring the new quarter library, which has much better interaction with qt), this is the same script but with using quarter:
<syntaxhighlight>
+
{{Code|code=
    #!/usr/bin/env python
+
#!/usr/bin/env python
 
   
 
   
    import os
+
import os
    import sys
+
import sys
 
   
 
   
    from PyQt4 import QtCore, QtGui
+
from PyQt4 import QtCore, QtGui
    from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QApplication
+
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QApplication
 
   
 
   
    from pivy.coin import SoInput, SoDB
+
from pivy.coin import SoInput, SoDB
    from pivy.quarter import QuarterWidget
+
from pivy.quarter import QuarterWidget
    import FreeCADGui
+
import FreeCADGui
 
   
 
   
 
   
 
   
    class MdiQuarterWidget(QuarterWidget):
+
class MdiQuarterWidget(QuarterWidget):
        def __init__(self, parent, sharewidget):
+
    def __init__(self, parent, sharewidget):
            QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
+
        QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
 
   
 
   
        def minimumSizeHint(self):
+
    def minimumSizeHint(self):
            return QtCore.QSize(640, 480)
+
        return QtCore.QSize(640, 480)
 
   
 
   
 
   
 
   
    class MdiMainWindow(QMainWindow):
+
class MdiMainWindow(QMainWindow):
        def __init__(self, qApp):
+
    def __init__(self, qApp):
            QMainWindow.__init__(self)
+
        QMainWindow.__init__(self)
            self._firstwidget = None
+
        self._firstwidget = None
            self._workspace = QWorkspace()
+
        self._workspace = QWorkspace()
            self.setCentralWidget(self._workspace)
+
        self.setCentralWidget(self._workspace)
            self.setAcceptDrops(True)
+
        self.setAcceptDrops(True)
            self.setWindowTitle("Pivy Quarter MDI example")
+
        self.setWindowTitle("Pivy Quarter MDI example")
 
   
 
   
            filemenu = self.menuBar().addMenu("&File")
+
        filemenu = self.menuBar().addMenu("&File")
            windowmenu = self.menuBar().addMenu("&Windows")
+
        windowmenu = self.menuBar().addMenu("&Windows")
 
   
 
   
            fileopenaction = QAction("&Create Box", self)
+
        fileopenaction = QAction("&Create Box", self)
            fileexitaction = QAction("E&xit", self)
+
        fileexitaction = QAction("E&xit", self)
            tileaction = QAction("Tile", self)
+
        tileaction = QAction("Tile", self)
            cascadeaction = QAction("Cascade", self)
+
        cascadeaction = QAction("Cascade", self)
 
   
 
   
            filemenu.addAction(fileopenaction)
+
        filemenu.addAction(fileopenaction)
            filemenu.addAction(fileexitaction)
+
        filemenu.addAction(fileexitaction)
            windowmenu.addAction(tileaction)
+
        windowmenu.addAction(tileaction)
            windowmenu.addAction(cascadeaction)
+
        windowmenu.addAction(cascadeaction)
 
   
 
   
            self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
+
        self.connect(fileopenaction, QtCore.SIGNAL("triggered()"), self.createBoxInFreeCAD)
            self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
+
        self.connect(fileexitaction, QtCore.SIGNAL("triggered()"), QtGui.qApp.closeAllWindows)
            self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
+
        self.connect(tileaction, QtCore.SIGNAL("triggered()"), self._workspace.tile)
            self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
+
        self.connect(cascadeaction, QtCore.SIGNAL("triggered()"), self._workspace.cascade)
 
   
 
   
            windowmapper = QtCore.QSignalMapper(self)
+
        windowmapper = QtCore.QSignalMapper(self)
            self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
+
        self.connect(windowmapper, QtCore.SIGNAL("mapped(QWidget *)"), self._workspace.setActiveWindow)
 
   
 
   
            self.dirname = os.curdir       
+
        self.dirname = os.curdir       
 
   
 
   
        def closeEvent(self, event):
+
    def closeEvent(self, event):
            self._workspace.closeAllWindows()
+
        self._workspace.closeAllWindows()
 
   
 
   
        def createBoxInFreeCAD(self):
+
    def createBoxInFreeCAD(self):
            d=FreeCAD.newDocument()
+
        d=FreeCAD.newDocument()
            o=d.addObject("Part::Box")
+
        o=d.addObject("Part::Box")
            d.recompute()
+
        d.recompute()
            s=FreeCADGui.subgraphFromObject(o)
+
        s=FreeCADGui.subgraphFromObject(o)
            child = self.createMdiChild()
+
        child = self.createMdiChild()
            child.show()
+
        child.show()
            child.setSceneGraph(s)
+
        child.setSceneGraph(s)
 
   
 
   
        def createMdiChild(self):
+
    def createMdiChild(self):
            widget = MdiQuarterWidget(None, self._firstwidget)
+
        widget = MdiQuarterWidget(None, self._firstwidget)
            self._workspace.addWindow(widget)
+
        self._workspace.addWindow(widget)
            if not self._firstwidget:
+
        if not self._firstwidget:
                self._firstwidget = widget
+
            self._firstwidget = widget
            return widget
+
        return widget
 
   
 
   
 
   
 
   
    def main():
+
def main():
        app = QApplication(sys.argv)
+
    app = QApplication(sys.argv)
        FreeCADGui.setupWithoutGUI()         
+
    FreeCADGui.setupWithoutGUI()         
        mdi = MdiMainWindow(app)   
+
    mdi = MdiMainWindow(app)   
        mdi.show()
+
    mdi.show()
        sys.exit(app.exec_())
+
    sys.exit(app.exec_())
 
   
 
   
 
   
 
   
    if __name__ == '__main__':
+
if __name__ == '__main__':
        main()
+
    main()
 +
}}
 +
 
 +
== Additional information ==
  
</syntaxhighlight>
+
* [https://forum.freecadweb.org/viewtopic.php?f=8&t=203 Embedding a view to another (QT) application?]
 +
* [https://forum.freecadweb.org/viewtopic.php?t=12575 Using Gui functions without Gui.showMainWindow() in python script]
 +
* [https://forum.freecadweb.org/viewtopic.php?t=14912 Acess Scenegraph through python API in without Gui mode]
 +
* [https://forum.freecadweb.org/viewtopic.php?p=190353 FreeCAD Hangs When Called from Python]
 +
* [https://forum.freecadweb.org/viewtopic.php?t=41697 Problem with FreeCADGui]
  
[[Category:Poweruser Documentation/cs]]
+
In the source code there are examples of embedding FreeCAD with various graphical toolkits:
[[Category:Python Code/cs]]
+
* [https://github.com/FreeCAD/FreeCAD/tree/master/src/Tools/embedded src/Tools/embedded]
  
 +
{{Powerdocnavi{{#translation:}}}}
 +
[[Category:Developer Documentation{{#translation:}}]]
 +
[[Category:Python Code{{#translation:}}]]
 
{{clear}}
 
{{clear}}
<languages/>
 

Latest revision as of 21:00, 23 August 2020

Other languages:
Deutsch • ‎English • ‎Türkçe • ‎español • ‎français • ‎italiano • ‎polski • ‎română • ‎svenska • ‎čeština • ‎русский

Introduction

It is possible to import the FreeCAD module into a Python application, and use all its tools from the host application, but the graphical user interface (GUI) can be imported as a Python module as well. Normally you can only import the complete interface as a whole, not pieces of it; this is because the FreeCAD interface system is not only made of independent widgets and toolbars, but is a complex construction where several invisible components (such as the selection system, etc) are needed for the main 3D view to be able to function.

But, with a bit of hacking, it is possible to import the whole FreeCAD interface, then move the 3D view from it to your own Qt application. We show here 3 different methods.

Using the FreeCAD 3D view widget directly

Be aware that there are a lot of problems with this approach. 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. A better way could be to create your own 3d view SoQtExaminerViewer or SoQtViewer and "push" the content of FreeCAD's 3d view to your view, as shown in the other sections below.

First, get the main window via PySide:

from PySide import QtGui
from PySide 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()

Then get the View3DInventor view the same way:

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)

The following code is generated automatically, by creating a Ui-file with QtDesigner, and converting it to python code with the pyuic tool:

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

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sun Dec 27 11:18:56 2009
#      by: PySide UI code generator 4.6
#
# Modify for PySide 11/02/2015
#      Python version: 2.7.8
#      Qt version: 4.8.6
#
# WARNING! All changes made in this file will be lost!

from PySide 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))

Then, create a main window that should be your application's main window, apply the UI setup above to it in order to add an MDI area and "move" our 3D view to it.

ui = Ui_MainWindow()
my_mw = QtGui.QMainWindow()
ui.setupUi(my_mw)
ui.mdiArea.addSubWindow(v)
my_mw.show()

Creating a soGui Examiner Viewer

Alternatively, you can also use the FreeCADGui module to extract a OpenInventor (Coin) representation of the objects of your scene, then use that data in an external viewer (your application). Here is an easy way to get the 3D representation of an object.

from pivy import coin
import FreeCAD as App

box = App.activeDocument().addObject("Part::Box", "myBox")
s = box.ViewObject.toString()  # store as string
inp = coin.SoInput()
inp.setBuffer(s)
myNode = coin.SoDB.readAll(inp)  # restore from string

Then, create a standalone viewer with Pivy:

from pivy.sogui import *
from pivy.coin import *
import sys

def myViewer():
    # Initialize Coin. This returns a main window to use.
    # If unsuccessful, exit.
    myWindow = SoGui.init(sys.argv[0])
    if myWindow == None:
        sys.exit(1)

    # Make an empty scene and add our node to it
    scene = SoSeparator()
    scene.addChild(myNode)

    # Create a viewer in which to see our scene graph.
    viewer = SoGuiExaminerViewer(myWindow)

    # Put our scene into viewer, change the title
    viewer.setSceneGraph(scene)
    viewer.setTitle("FreeCAD Object Viewer")
    viewer.show()

    SoGui.show(myWindow) # Display main window
    SoGui.mainLoop()     # Main Coin event loop

Then you just need to run your viewer:

myViewer()

Using the quarter module

Instead of using the sogui viewer, you can also use the more modern quarter module. This is generally the best of the three options.

#!/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 PySide import QtCore, QtGui
from PySide.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()

Without even firing up the FreeCAD Gui

Starting from FreeCAD rev2760 (2010, 1), it is now possible to obtain the coin representation of any FreeCAD object without opening the main window. This makes it extremely easy to implement your own viewer and transparently have FreeCAD updating it. After importing the FreeCADGui module, you need to fire it up with the setupWithoutGUI() method, after which you can use all of FreeCAD's view providers to obtain OpenInventor (Coin) nodes.

import os, sys, FreeCAD, FreeCADGui
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QFileDialog, QApplication
from pivy.coin import SoInput, SoDB, sogui
 
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")
        self.viewers=[]
 
        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)
 
    def closeEvent(self, event):
        self._workspace.closeAllWindows()
 
    def createBoxInFreeCAD(self):
        widget = QtGui.QWidget(self._firstwidget)
        viewer = sogui.SoGuiExaminerViewer(widget)
        self._workspace.addWindow(widget)
        if not self._firstwidget:
            self._firstwidget = widget
        widget.show()
        self.viewers.append(viewer)
        doc = FreeCAD.newDocument()
        obj=doc.addObject("Part::Box","myBox")
        doc.recompute()
        root=FreeCADGui.subgraphFromObject(obj)
        viewer.setSceneGraph(root)
 
def main():
    app = QApplication(sys.argv)
    mdi = MdiMainWindow(app)   
    mdi.show()
    FreeCADGui.setupWithoutGUI()
    sys.exit(app.exec_())
 
if __name__ == '__main__':
    main()

Or, if using pivy's sogui module doesn't work for you (the sogui module is becoming obsoleted and the coin developers are now favoring the new quarter library, which has much better interaction with qt), this is the same script but with using quarter:

#!/usr/bin/env python
 
import os
import sys
 
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QMainWindow, QWorkspace, QAction, QApplication
 
from pivy.coin import SoInput, SoDB
from pivy.quarter import QuarterWidget
import FreeCADGui
 
 
class MdiQuarterWidget(QuarterWidget):
    def __init__(self, parent, sharewidget):
        QuarterWidget.__init__(self, parent=parent, sharewidget=sharewidget)
 
    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 closeEvent(self, event):
        self._workspace.closeAllWindows()
 
    def createBoxInFreeCAD(self):
        d=FreeCAD.newDocument()
        o=d.addObject("Part::Box")
        d.recompute()
        s=FreeCADGui.subgraphFromObject(o)
        child = self.createMdiChild()
        child.show()
        child.setSceneGraph(s)
 
    def createMdiChild(self):
        widget = MdiQuarterWidget(None, self._firstwidget)
        self._workspace.addWindow(widget)
        if not self._firstwidget:
            self._firstwidget = widget
        return widget
 
 
def main():
    app = QApplication(sys.argv)
    FreeCADGui.setupWithoutGUI()        
    mdi = MdiMainWindow(app)   
    mdi.show()
    sys.exit(app.exec_())
 
 
if __name__ == '__main__':
    main()

Additional information

In the source code there are examples of embedding FreeCAD with various graphical toolkits: