Embedding FreeCADGui: Difference between revisions

From FreeCAD Documentation
mNo edit summary
m (Category "Poweruser Documentation" "Python Code")
Line 448: Line 448:
if __name__ == '__main__':
if __name__ == '__main__':
main()
main()



[[Category:Poweruser Documentation]]
[[Category:Python Code]]


{{languages | {{es|Embedding_FreeCADGui/es}} }}
{{languages | {{es|Embedding_FreeCADGui/es}} }}

Revision as of 13:31, 29 August 2012

You already know that you can 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.

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 PyQt:

   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()

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: 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))

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 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:

   FreeCAD.activeDocument().addObject("Part::Box","myBox")
   s=FreeCADGui.activeDocument().getObject("myBox").toString() # store as string
   from pivy import coin
   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 probably the best solution of the 3.

   #!/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()

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.

   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():
       FreeCADGui.setupWithoutGUI()
       app = QApplication(sys.argv)
       mdi = MdiMainWindow(app)   
       mdi.show()
       sys.exit(app.exec_())


   if __name__ == '__main__':
       main()
Available translations of this page: