yade-dev team mailing list archive
-
yade-dev team
-
Mailing list archive
-
Message #12124
[Branch ~yade-pkg/yade/git-trunk] Rev 3691: Prepare Qt5-build.
------------------------------------------------------------
revno: 3691
committer: Anton Gladky <gladky.anton@xxxxxxxxx>
timestamp: Fri 2015-06-26 22:19:22 +0200
message:
Prepare Qt5-build.
Not functional yet.
removed:
gui/qt4/build
added:
gui/qt5/
gui/qt5/GLViewer.cpp
gui/qt5/GLViewer.hpp
gui/qt5/GLViewerDisplay.cpp
gui/qt5/GLViewerMouse.cpp
gui/qt5/Inspector.py
gui/qt5/OpenGLManager.cpp
gui/qt5/OpenGLManager.hpp
gui/qt5/SerializableEditor.py
gui/qt5/XYZ.png
gui/qt5/XYZ.xpm
gui/qt5/YZX.png
gui/qt5/YZX.xpm
gui/qt5/ZXY.png
gui/qt5/ZXY.xpm
gui/qt5/_GLViewer.cpp
gui/qt5/__init__.py
gui/qt5/controller.ui
gui/qt5/img.qrc
gui/qt5/yade-favicon.png
gui/qt5/yade-favicon.xpm
modified:
CMakeLists.txt
gui/CMakeLists.txt
--
lp:yade
https://code.launchpad.net/~yade-pkg/yade/git-trunk
Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-pkg/yade/git-trunk/+edit-subscription
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2015-06-12 13:05:43 +0000
+++ CMakeLists.txt 2015-06-26 20:19:22 +0000
@@ -22,9 +22,10 @@
# runtimePREFIX: used for packaging, when install directory is not the same is runtime directory (/usr/local by default)
# CHUNKSIZE: set >1, if you want several sources to be compiled at once. Increases compilation speed and RAM-consumption during it (1 by default)
# VECTORIZE: enables vectorization and alignment in Eigen3 library, experimental (OFF by default)
+# USE_QT5: use QT5 for GUI, experimental (OFF by default)
project(Yade C CXX)
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8.11)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cMake")
@@ -126,6 +127,7 @@
OPTION(ENABLE_LIQMIGRATION "Enable liquid control (very experimental), see [Mani2013] for details" OFF)
OPTION(ENABLE_MASK_ARBITRARY "Enable arbitrary precision of bitmask variables (only Body::groupMask yet implemented) (experimental). Use -DMASK_ARBITRARY_SIZE=int to set number of used bits (256 by default)" OFF)
OPTION(ENABLE_PROFILING "Enable profiling, e.g. shows some more metrics, which can define bottlenecks of the code (OFF by default)" OFF)
+OPTION(USE_QT5 "USE Qt5 for GUI" OFF)
#===========================================================
# Use Eigen3 by default
@@ -214,25 +216,42 @@
ENDIF(ENABLE_GTS)
#===========================================================
IF(ENABLE_GUI)
- FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtOpenGL)
FIND_PACKAGE(OpenGL)
FIND_PACKAGE(GLUT)
FIND_PACKAGE(glib2)
FIND_PACKAGE(QGLVIEWER)
- IF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
- SET(GUI_LIBS ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES})
- SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
- SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
- INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
- INCLUDE_DIRECTORIES(${QT_INCLUDES})
- MESSAGE(STATUS "Found GUI-LIBS")
- SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI")
- ELSE(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
- MESSAGE(STATUS "GUI-LIBS NOT found")
- SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
- SET(ENABLE_GUI OFF)
- ENDIF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ IF(USE_QT5)
+ FIND_PACKAGE(Qt5 CONFIG REQUIRED Widgets Xml OpenGL)
+ IF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ SET(GUI_LIBS ${GLUT_LIBRARY} ${QGLVIEWER_LIBRARIES})
+ SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
+
+ MESSAGE(STATUS "Found GUI-Qt5-LIBS")
+ SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI-Qt5")
+ ELSE(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ MESSAGE(STATUS "GUI-Qt5-LIBS NOT found")
+ SET(DISABLED_FEATS "${DISABLED_FEATS} GUI-Qt5")
+ SET(ENABLE_GUI OFF)
+ ENDIF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ ELSE(USE_QT5) # Use Qt4
+ FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtOpenGL)
+ IF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ SET(GUI_LIBS ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES})
+ SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
+ INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
+ INCLUDE_DIRECTORIES(${QT_INCLUDES})
+
+ MESSAGE(STATUS "Found GUI-LIBS")
+ SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI")
+ ELSE(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ MESSAGE(STATUS "GUI-LIBS NOT found")
+ SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
+ SET(ENABLE_GUI OFF)
+ ENDIF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+ ENDIF(USE_QT5)
ELSE(ENABLE_GUI)
SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
ENDIF(ENABLE_GUI)
=== modified file 'gui/CMakeLists.txt'
--- gui/CMakeLists.txt 2013-10-14 20:51:11 +0000
+++ gui/CMakeLists.txt 2015-06-26 20:19:22 +0000
@@ -1,34 +1,64 @@
IF(${ENABLE_GUI})
- INCLUDE(${QT_USE_FILE})
INCLUDE_DIRECTORIES(${QGLVIEWER_INCLUDE_DIR})
-
- SET(_GLViewer_MOC_HEADERS qt4/GLViewer.hpp;qt4/OpenGLManager.hpp)
- SET(_GLViewer_SOURCE_FILES qt4/GLViewer.cpp;qt4/_GLViewer.cpp;qt4/OpenGLManager.cpp;qt4/GLViewerDisplay.cpp;qt4/GLViewerMouse.cpp)
-
- QT4_WRAP_CPP(_GLViewer_MOC_OUTFILES ${_GLViewer_MOC_HEADERS})
-
- ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES} ${_GLViewer_MOC_OUTFILES})
- SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "")
- TARGET_LINK_LIBRARIES(_GLViewer ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QT_LIBRARIES} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
- IF(GL2PS_FOUND AND ENABLE_GL2PS)
- TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
- ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
- INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
-
- FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt4/*.py")
- INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
-
- EXECUTE_PROCESS(
- COMMAND "pyrcc4" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
- RESULT_VARIABLE rv
- )
-
- EXECUTE_PROCESS(
- COMMAND "pyuic4" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
- RESULT_VARIABLE rv
- )
- INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt)
-
+ IF(USE_QT5)
+ SET(CMAKE_AUTOMOC ON)
+ SET(_GLViewer_SOURCE_FILES qt5/GLViewer.cpp;qt5/_GLViewer.cpp;qt5/OpenGLManager.cpp;qt5/GLViewerDisplay.cpp;qt5/GLViewerMouse.cpp)
+ ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES})
+
+ TARGET_LINK_LIBRARIES(_GLViewer Qt5::Widgets Qt5::Xml Qt5::OpenGL
+ ${GLUT_LIBRARY} ${OPENGL_LIBRARY}
+ ${QGLVIEWER_LIBRARIES}
+ ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
+ IF(GL2PS_FOUND AND ENABLE_GL2PS)
+ TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
+ ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
+ INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
+
+ FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt5/*.py")
+ INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
+
+ EXECUTE_PROCESS(
+ COMMAND "pyrcc5" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+ RESULT_VARIABLE rv
+ )
+
+ EXECUTE_PROCESS(
+ COMMAND "pyuic5" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5
+ RESULT_VARIABLE rv
+ )
+ INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt)
+ ELSE(USE_QT5)
+ INCLUDE(${QT_USE_FILE})
+
+ SET(_GLViewer_MOC_HEADERS qt4/GLViewer.hpp;qt4/OpenGLManager.hpp)
+ SET(_GLViewer_SOURCE_FILES qt4/GLViewer.cpp;qt4/_GLViewer.cpp;qt4/OpenGLManager.cpp;qt4/GLViewerDisplay.cpp;qt4/GLViewerMouse.cpp)
+
+ QT4_WRAP_CPP(_GLViewer_MOC_OUTFILES ${_GLViewer_MOC_HEADERS})
+
+ ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES} ${_GLViewer_MOC_OUTFILES})
+ SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "")
+ TARGET_LINK_LIBRARIES(_GLViewer ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QT_LIBRARIES} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
+ IF(GL2PS_FOUND AND ENABLE_GL2PS)
+ TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
+ ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
+ INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
+
+ FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt4/*.py")
+ INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
+
+ EXECUTE_PROCESS(
+ COMMAND "pyrcc4" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+ RESULT_VARIABLE rv
+ )
+
+ EXECUTE_PROCESS(
+ COMMAND "pyuic4" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+ RESULT_VARIABLE rv
+ )
+ INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py DESTINATION ${YADE_PY_PATH}/yade/qt)
+ ENDIF(USE_QT5)
ENDIF(${ENABLE_GUI})
=== removed file 'gui/qt4/build'
--- gui/qt4/build 2010-07-24 18:10:24 +0000
+++ gui/qt4/build 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
-#!/bin/sh
-pyrcc4 -o img_rc.py img.qrc
-pyuic4 controller.ui > ui_controller.py
-pyuic4 SeqSerializable.ui > ui_SeqSerializable.py
=== added directory 'gui/qt5'
=== added file 'gui/qt5/GLViewer.cpp'
--- gui/qt5/GLViewer.cpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewer.cpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,488 @@
+/*************************************************************************
+* Copyright (C) 2004 by Olivier Galizzi *
+* olivier.galizzi@xxxxxxx *
+* Copyright (C) 2005 by Janek Kozicki *
+* cosurgi@xxxxxxxxxx *
+* *
+* This program is free software; it is licensed under the terms of the *
+* GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+ #include<gl2ps.h>
+#endif
+
+static unsigned initBlocked(State::DOF_NONE);
+
+CREATE_LOGGER(GLViewer);
+
+GLLock::GLLock(GLViewer* _glv): boost::try_mutex::scoped_lock(Omega::instance().renderMutex), glv(_glv){
+ glv->makeCurrent();
+}
+GLLock::~GLLock(){ glv->doneCurrent(); }
+
+
+#define _W3 setw(3)<<setfill('0')
+#define _W2 setw(2)<<setfill('0')
+GLViewer::~GLViewer(){ /* get the GL mutex when closing */ GLLock lock(this); /* cerr<<"Destructing view #"<<viewId<<endl;*/ }
+
+void GLViewer::closeEvent(QCloseEvent *e){
+ LOG_DEBUG("Will emit closeView for view #"<<viewId);
+ OpenGLManager::self->emitCloseView(viewId);
+ e->accept();
+}
+
+GLViewer::GLViewer(int _viewId, const shared_ptr<OpenGLRenderer>& _renderer, QGLWidget* shareWidget): QGLViewer(/*parent*/(QWidget*)NULL,shareWidget), renderer(_renderer), viewId(_viewId) {
+ isMoving=false;
+ drawGrid=0;
+ drawScale=true;
+ timeDispMask=TIME_REAL|TIME_VIRT|TIME_ITER;
+ cut_plane = 0;
+ cut_plane_delta = -2;
+ gridSubdivide = false;
+ resize(550,550);
+ last=-1;
+ if(viewId==0) setWindowTitle("Primary view");
+ else setWindowTitle(("Secondary view #"+boost::lexical_cast<string>(viewId)).c_str());
+
+ show();
+
+ mouseMovesCamera();
+ manipulatedClipPlane=-1;
+
+ if(manipulatedFrame()==0) setManipulatedFrame(new qglviewer::ManipulatedFrame());
+
+ xyPlaneConstraint=shared_ptr<qglviewer::LocalConstraint>(new qglviewer::LocalConstraint());
+ manipulatedFrame()->setConstraint(NULL);
+
+ setKeyDescription(Qt::Key_Return,"Run simulation.");
+ setKeyDescription(Qt::Key_A,"Toggle visibility of global axes.");
+ setKeyDescription(Qt::Key_C,"Set scene center so that all bodies are visible; if a body is selected, center around this body.");
+ setKeyDescription(Qt::Key_C & Qt::AltModifier,"Set scene center to median body position (same as space)");
+ setKeyDescription(Qt::Key_D,"Toggle time display mask");
+ setKeyDescription(Qt::Key_G,"Toggle grid visibility; g turns on and cycles");
+ setKeyDescription(Qt::Key_G & Qt::ShiftModifier ,"Hide grid.");
+ setKeyDescription(Qt::Key_M, "Move selected object.");
+ setKeyDescription(Qt::Key_X,"Show the xz [shift: xy] (up-right) plane (clip plane: align normal with +x)");
+ setKeyDescription(Qt::Key_Y,"Show the yx [shift: yz] (up-right) plane (clip plane: align normal with +y)");
+ setKeyDescription(Qt::Key_Z,"Show the zy [shift: zx] (up-right) plane (clip plane: align normal with +z)");
+ setKeyDescription(Qt::Key_Period,"Toggle grid subdivision by 10");
+ setKeyDescription(Qt::Key_S,"Save QGLViewer state to /tmp/qglviewerState.xml");
+ setKeyDescription(Qt::Key_T,"Switch orthographic / perspective camera");
+ setKeyDescription(Qt::Key_O,"Set narrower field of view");
+ setKeyDescription(Qt::Key_P,"Set wider field of view");
+ setKeyDescription(Qt::Key_R,"Revolve around scene center");
+ setKeyDescription(Qt::Key_V,"Save PDF of the current view to /tmp/yade-snapshot-0001.pdf (whichever number is available first). (Must be compiled with the gl2ps feature.)");
+ setPathKey(-Qt::Key_F1);
+ setPathKey(-Qt::Key_F2);
+ setKeyDescription(Qt::Key_Escape,"Manipulate scene (default)");
+ setKeyDescription(Qt::Key_F1,"Manipulate clipping plane #1");
+ setKeyDescription(Qt::Key_F2,"Manipulate clipping plane #2");
+ setKeyDescription(Qt::Key_F3,"Manipulate clipping plane #3");
+ setKeyDescription(Qt::Key_1,"Make the manipulated clipping plane parallel with plane #1");
+ setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #2");
+ setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #3");
+ setKeyDescription(Qt::Key_1 & Qt::AltModifier,"Add/remove plane #1 to/from the bound group");
+ setKeyDescription(Qt::Key_2 & Qt::AltModifier,"Add/remove plane #2 to/from the bound group");
+ setKeyDescription(Qt::Key_3 & Qt::AltModifier,"Add/remove plane #3 to/from the bound group");
+ setKeyDescription(Qt::Key_0,"Clear the bound group");
+ setKeyDescription(Qt::Key_7,"Load [Alt: save] view configuration #0");
+ setKeyDescription(Qt::Key_8,"Load [Alt: save] view configuration #1");
+ setKeyDescription(Qt::Key_9,"Load [Alt: save] view configuration #2");
+ setKeyDescription(Qt::Key_Space,"Center scene (same as Alt-C); clip plane: activate/deactivate");
+
+ centerScene();
+}
+
+bool GLViewer::isManipulating(){
+ return isMoving || manipulatedClipPlane>=0;
+}
+
+void GLViewer::resetManipulation(){
+ mouseMovesCamera();
+ setSelectedName(-1);
+ isMoving=false;
+ manipulatedClipPlane=-1;
+}
+
+void GLViewer::startClipPlaneManipulation(int planeNo){
+ assert(planeNo<renderer->numClipPlanes);
+ resetManipulation();
+ mouseMovesManipulatedFrame(xyPlaneConstraint.get());
+ manipulatedClipPlane=planeNo;
+ const Se3r se3(renderer->clipPlaneSe3[planeNo]);
+ manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(se3.position[0],se3.position[1],se3.position[2]),qglviewer::Quaternion(se3.orientation.x(),se3.orientation.y(),se3.orientation.z(),se3.orientation.w()));
+ string grp=strBoundGroup();
+ displayMessage("Manipulating clip plane #"+boost::lexical_cast<string>(planeNo+1)+(grp.empty()?grp:" (bound planes:"+grp+")"));
+}
+
+string GLViewer::getState(){
+ QString origStateFileName=stateFileName();
+ string tmpFile=Omega::instance().tmpFilename();
+ setStateFileName(QString(tmpFile.c_str())); saveStateToFile(); setStateFileName(origStateFileName);
+ LOG_WARN("State saved to temp file "<<tmpFile);
+ // read tmp file contents and return it as string
+ // this will replace all whitespace by space (nowlines will disappear, which is what we want)
+ ifstream in(tmpFile.c_str()); string ret; while(!in.eof()){string ss; in>>ss; ret+=" "+ss;}; in.close();
+ boost::filesystem::remove(boost::filesystem::path(tmpFile));
+ return ret;
+}
+
+void GLViewer::setState(string state){
+ string tmpFile=Omega::instance().tmpFilename();
+ std::ofstream out(tmpFile.c_str());
+ if(!out.good()){ LOG_ERROR("Error opening temp file `"<<tmpFile<<"', loading aborted."); return; }
+ out<<state; out.close();
+ LOG_WARN("Will load state from temp file "<<tmpFile);
+ QString origStateFileName=stateFileName(); setStateFileName(QString(tmpFile.c_str())); restoreStateFromFile(); setStateFileName(origStateFileName);
+ boost::filesystem::remove(boost::filesystem::path(tmpFile));
+}
+
+void GLViewer::keyPressEvent(QKeyEvent *e)
+{
+ last_user_event = boost::posix_time::second_clock::local_time();
+
+ if(false){}
+ /* special keys: Escape and Space */
+ else if(e->key()==Qt::Key_A){ toggleAxisIsDrawn(); return; }
+ else if(e->key()==Qt::Key_Escape){
+ if(!isManipulating()){ setSelectedName(-1); return; }
+ else { resetManipulation(); displayMessage("Manipulating scene."); }
+ }
+ else if(e->key()==Qt::Key_Space){
+ if(manipulatedClipPlane>=0) {displayMessage("Clip plane #"+boost::lexical_cast<string>(manipulatedClipPlane+1)+(renderer->clipPlaneActive[manipulatedClipPlane]?" de":" ")+"activated"); renderer->clipPlaneActive[manipulatedClipPlane]=!renderer->clipPlaneActive[manipulatedClipPlane]; }
+ else{ centerMedianQuartile(); }
+ }
+ /* function keys */
+ else if(e->key()==Qt::Key_F1 || e->key()==Qt::Key_F2 || e->key()==Qt::Key_F3 /* || ... */ ){
+ int n=0; if(e->key()==Qt::Key_F1) n=1; else if(e->key()==Qt::Key_F2) n=2; else if(e->key()==Qt::Key_F3) n=3; assert(n>0); int planeId=n-1;
+ if(planeId>=renderer->numClipPlanes) return;
+ if(planeId!=manipulatedClipPlane) startClipPlaneManipulation(planeId);
+ }
+ /* numbers */
+ else if(e->key()==Qt::Key_0 && (e->modifiers() & Qt::AltModifier)) { boundClipPlanes.clear(); displayMessage("Cleared bound planes group.");}
+ else if(e->key()==Qt::Key_1 || e->key()==Qt::Key_2 || e->key()==Qt::Key_3 /* || ... */ ){
+ int n=0; if(e->key()==Qt::Key_1) n=1; else if(e->key()==Qt::Key_2) n=2; else if(e->key()==Qt::Key_3) n=3; assert(n>0); int planeId=n-1;
+ if(planeId>=renderer->numClipPlanes) return; // no such clipping plane
+ if(e->modifiers() & Qt::AltModifier){
+ if(boundClipPlanes.count(planeId)==0) {boundClipPlanes.insert(planeId); displayMessage("Added plane #"+boost::lexical_cast<string>(planeId+1)+" to the bound group: "+strBoundGroup());}
+ else {boundClipPlanes.erase(planeId); displayMessage("Removed plane #"+boost::lexical_cast<string>(planeId+1)+" from the bound group: "+strBoundGroup());}
+ }
+ else if(manipulatedClipPlane>=0 && manipulatedClipPlane!=planeId) {
+ const Quaternionr& o=renderer->clipPlaneSe3[planeId].orientation;
+ manipulatedFrame()->setOrientation(qglviewer::Quaternion(o.x(),o.y(),o.z(),o.w()));
+ displayMessage("Copied orientation from plane #1");
+ }
+ }
+ else if(e->key()==Qt::Key_7 || e->key()==Qt::Key_8 || e->key()==Qt::Key_9){
+ int nn=-1; if(e->key()==Qt::Key_7)nn=0; else if(e->key()==Qt::Key_8)nn=1; else if(e->key()==Qt::Key_9)nn=2; assert(nn>=0); size_t n=(size_t)nn;
+ if(e->modifiers() & Qt::AltModifier) saveDisplayParameters(n);
+ else useDisplayParameters(n);
+ }
+ /* letters alphabetically */
+ else if(e->key()==Qt::Key_C && (e->modifiers() & Qt::AltModifier)){ displayMessage("Median centering"); centerMedianQuartile(); }
+ else if(e->key()==Qt::Key_C){
+ // center around selected body
+ if(selectedName() >= 0 && (*(Omega::instance().getScene()->bodies)).exists(selectedName())) setSceneCenter(manipulatedFrame()->position());
+ // make all bodies visible
+ else centerScene();
+ }
+ else if(e->key()==Qt::Key_D &&(e->modifiers() & Qt::AltModifier)){ Body::id_t id; if((id=Omega::instance().getScene()->selectedBody)>=0){ const shared_ptr<Body>& b=Body::byId(id); b->setDynamic(!b->isDynamic()); LOG_INFO("Body #"<<id<<" now "<<(b->isDynamic()?"":"NOT")<<" dynamic"); } }
+ else if(e->key()==Qt::Key_D) {timeDispMask+=1; if(timeDispMask>(TIME_REAL|TIME_VIRT|TIME_ITER))timeDispMask=0; }
+ else if(e->key()==Qt::Key_G) { if(e->modifiers() & Qt::ShiftModifier){ drawGrid=0; return; } else drawGrid++; if(drawGrid>=8) drawGrid=0; }
+ else if (e->key()==Qt::Key_M && selectedName() >= 0){
+ if(!(isMoving=!isMoving)){
+ displayMessage("Moving done.");
+ if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} mouseMovesCamera();}
+ else{ displayMessage("Moving selected object");
+
+ long selection = Omega::instance().getScene()->selectedBody;
+ initBlocked=Body::byId(Body::id_t(selection))->state->blockedDOFs; last=selection;
+ Body::byId(Body::id_t(selection))->state->blockedDOFs=State::DOF_ALL;
+ Quaternionr& q = Body::byId(selection)->state->ori;
+ Vector3r& v = Body::byId(selection)->state->pos;
+ manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(v[0],v[1],v[2]),qglviewer::Quaternion(q.x(),q.y(),q.z(),q.w()));
+ mouseMovesManipulatedFrame();}
+ }
+ else if (e->key() == Qt::Key_T) camera()->setType(camera()->type()==qglviewer::Camera::ORTHOGRAPHIC ? qglviewer::Camera::PERSPECTIVE : qglviewer::Camera::ORTHOGRAPHIC);
+ else if(e->key()==Qt::Key_O) camera()->setFieldOfView(camera()->fieldOfView()*0.9);
+ else if(e->key()==Qt::Key_P) camera()->setFieldOfView(camera()->fieldOfView()*1.1);
+ else if(e->key()==Qt::Key_R){ // reverse the clipping plane; revolve around scene center if no clipping plane selected
+ if(manipulatedClipPlane>=0 && manipulatedClipPlane<renderer->numClipPlanes){
+ /* here, we must update both manipulatedFrame orientation and renderer->clipPlaneSe3 orientation in the same way */
+ Quaternionr& ori=renderer->clipPlaneSe3[manipulatedClipPlane].orientation;
+ ori=Quaternionr(AngleAxisr(Mathr::PI,Vector3r(0,1,0)))*ori;
+ manipulatedFrame()->setOrientation(qglviewer::Quaternion(qglviewer::Vec(0,1,0),Mathr::PI)*manipulatedFrame()->orientation());
+ displayMessage("Plane #"+boost::lexical_cast<string>(manipulatedClipPlane+1)+" reversed.");
+ }
+ else {
+ camera()->setRevolveAroundPoint(sceneCenter());
+ }
+ }
+ else if(e->key()==Qt::Key_S){
+ LOG_INFO("Saving QGLViewer state to /tmp/qglviewerState.xml");
+ setStateFileName("/tmp/qglviewerState.xml"); saveStateToFile(); setStateFileName(QString::null);
+ }
+ else if(e->key()==Qt::Key_L){
+ LOG_INFO("Loading QGLViewer state from /tmp/qglviewerState.xml");
+ setStateFileName("/tmp/qglviewerState.xml"); restoreStateFromFile(); setStateFileName(QString::null);
+ }
+ else if(e->key()==Qt::Key_X || e->key()==Qt::Key_Y || e->key()==Qt::Key_Z){
+ int axisIdx=(e->key()==Qt::Key_X?0:(e->key()==Qt::Key_Y?1:2));
+ if(manipulatedClipPlane<0){
+ qglviewer::Vec up(0,0,0), vDir(0,0,0);
+ bool alt=(e->modifiers() && Qt::ShiftModifier);
+ up[axisIdx]=1; vDir[(axisIdx+(alt?2:1))%3]=alt?1:-1;
+ camera()->setViewDirection(vDir);
+ camera()->setUpVector(up);
+ centerMedianQuartile();
+ }
+ else{ // align clipping normal plane with world axis
+ // x: (0,1,0),pi/2; y: (0,0,1),pi/2; z: (1,0,0),0
+ qglviewer::Vec axis(0,0,0); axis[(axisIdx+1)%3]=1; Real angle=axisIdx==2?0:Mathr::PI/2;
+ manipulatedFrame()->setOrientation(qglviewer::Quaternion(axis,angle));
+ }
+ }
+ else if(e->key()==Qt::Key_Period) gridSubdivide = !gridSubdivide;
+ else if(e->key()==Qt::Key_Return){
+ if (Omega::instance().isRunning()) Omega::instance().pause();
+ else Omega::instance().run();
+ LOG_INFO("Running...");
+ }
+#ifdef YADE_GL2PS
+ else if(e->key()==Qt::Key_V){
+ for(int i=0; ;i++){
+ std::ostringstream fss; fss<<"/tmp/yade-snapshot-"<<setw(4)<<setfill('0')<<i<<".pdf";
+ if(!boost::filesystem::exists(fss.str())){ nextFrameSnapshotFilename=fss.str(); break; }
+ }
+ LOG_INFO("Will save snapshot to "<<nextFrameSnapshotFilename);
+ }
+#endif
+#if 0
+ else if( e->key()==Qt::Key_Plus ){
+ cut_plane = std::min(1.0, cut_plane + std::pow(10.0,(double)cut_plane_delta));
+ static_cast<YadeCamera*>(camera())->setCuttingDistance(cut_plane);
+ displayMessage("Cut plane: "+boost::lexical_cast<std::string>(cut_plane));
+ }else if( e->key()==Qt::Key_Minus ){
+ cut_plane = std::max(0.0, cut_plane - std::pow(10.0,(double)cut_plane_delta));
+ static_cast<YadeCamera*>(camera())->setCuttingDistance(cut_plane);
+ displayMessage("Cut plane: "+boost::lexical_cast<std::string>(cut_plane));
+ }else if( e->key()==Qt::Key_Slash ){
+ cut_plane_delta -= 1;
+ displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast<std::string>(cut_plane_delta));
+ }else if( e->key()==Qt::Key_Asterisk ){
+ cut_plane_delta = std::min(1+cut_plane_delta,-1);
+ displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast<std::string>(cut_plane_delta));
+ }
+#endif
+
+ else if(e->key()!=Qt::Key_Escape && e->key()!=Qt::Key_Space) QGLViewer::keyPressEvent(e);
+ updateGL();
+}
+/* Center the scene such that periodic cell is contained in the view */
+void GLViewer::centerPeriodic(){
+ Scene* scene=Omega::instance().getScene().get();
+ assert(scene->isPeriodic);
+ Vector3r center=.5*scene->cell->getSize();
+ Vector3r halfSize=.5*scene->cell->getSize();
+ float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2]));
+ LOG_DEBUG("Periodic scene center="<<center<<", halfSize="<<halfSize<<", radius="<<radius);
+ setSceneCenter(qglviewer::Vec(center[0],center[1],center[2]));
+ setSceneRadius(radius*1.5);
+ showEntireScene();
+}
+
+/* Calculate medians for x, y and z coordinates of all bodies;
+ *then set scene center to median position and scene radius to 2*inter-quartile distance.
+ *
+ * This function eliminates the effect of lonely bodies that went nuts and enlarge
+ * the scene's Aabb in such a way that fitting the scene to see the Aabb makes the
+ * "central" (where most bodies is) part very small or even invisible.
+ */
+void GLViewer::centerMedianQuartile(){
+ Scene* scene=Omega::instance().getScene().get();
+ if(scene->isPeriodic){ centerPeriodic(); return; }
+ long nBodies=scene->bodies->size();
+ if(nBodies<4) {
+ LOG_DEBUG("Less than 4 bodies, median makes no sense; calling centerScene() instead.");
+ return centerScene();
+ }
+ std::vector<Real> coords[3];
+ for(int i=0;i<3;i++)coords[i].reserve(nBodies);
+ FOREACH(shared_ptr<Body> b, *scene->bodies){
+ if(!b) continue;
+ for(int i=0; i<3; i++) coords[i].push_back(b->state->pos[i]);
+ }
+ Vector3r median,interQuart;
+ for(int i=0;i<3;i++){
+ sort(coords[i].begin(),coords[i].end());
+ median[i]=*(coords[i].begin()+nBodies/2);
+ interQuart[i]=*(coords[i].begin()+3*nBodies/4)-*(coords[i].begin()+nBodies/4);
+ }
+ LOG_DEBUG("Median position is"<<median<<", inter-quartile distance is "<<interQuart);
+ setSceneCenter(qglviewer::Vec(median[0],median[1],median[2]));
+ setSceneRadius(2*(interQuart[0]+interQuart[1]+interQuart[2])/3.);
+ showEntireScene();
+}
+
+void GLViewer::centerScene(){
+ Scene* rb=Omega::instance().getScene().get();
+ if (!rb) return;
+ if(rb->isPeriodic){ centerPeriodic(); return; }
+ LOG_INFO("Select with shift, press 'm' to move.");
+ Vector3r min,max;
+ if(not(rb->bound)){ rb->updateBound();}
+
+ min=rb->bound->min; max=rb->bound->max;
+ bool hasNan=(isnan(min[0])||isnan(min[1])||isnan(min[2])||isnan(max[0])||isnan(max[1])||isnan(max[2]));
+ Real minDim=std::min(max[0]-min[0],std::min(max[1]-min[1],max[2]-min[2]));
+ if(minDim<=0 || hasNan){
+ // Aabb is not yet calculated...
+ LOG_DEBUG("scene's bound not yet calculated or has zero or nan dimension(s), attempt get that from bodies' positions.");
+ Real inf=std::numeric_limits<Real>::infinity();
+ min=Vector3r(inf,inf,inf); max=Vector3r(-inf,-inf,-inf);
+ FOREACH(const shared_ptr<Body>& b, *rb->bodies){
+ if(!b) continue;
+ max=max.cwiseMax(b->state->pos);
+ min=min.cwiseMin(b->state->pos);
+ }
+ if(isinf(min[0])||isinf(min[1])||isinf(min[2])||isinf(max[0])||isinf(max[1])||isinf(max[2])){ LOG_DEBUG("No min/max computed from bodies either, setting cube (-1,-1,-1)×(1,1,1)"); min=-Vector3r::Ones(); max=Vector3r::Ones(); }
+ } else {LOG_DEBUG("Using scene's Aabb");}
+
+ LOG_DEBUG("Got scene box min="<<min<<" and max="<<max);
+ Vector3r center = (max+min)*0.5;
+ Vector3r halfSize = (max-min)*0.5;
+ float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2])); if(radius<=0) radius=1;
+ LOG_DEBUG("Scene center="<<center<<", halfSize="<<halfSize<<", radius="<<radius);
+ setSceneCenter(qglviewer::Vec(center[0],center[1],center[2]));
+ setSceneRadius(radius*1.5);
+ showEntireScene();
+}
+
+// new object selected.
+// set frame coordinates, and isDynamic=false;
+void GLViewer::postSelection(const QPoint& point)
+{
+ LOG_DEBUG("Selection is "<<selectedName());
+ int selection = selectedName();
+ if(selection<0){
+ if (last>=0) {
+ Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1; Omega::instance().getScene()->selectedBody = -1;}
+ if(isMoving){
+ displayMessage("Moving finished"); mouseMovesCamera(); isMoving=false;
+ Omega::instance().getScene()->selectedBody = -1;
+ }
+ return;
+ }
+ if(selection>=0 && (*(Omega::instance().getScene()->bodies)).exists(selection)){
+ resetManipulation();
+ if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;}
+ if(Body::byId(Body::id_t(selection))->isClumpMember()){ // select clump (invisible) instead of its member
+ LOG_DEBUG("Clump member #"<<selection<<" selected, selecting clump instead.");
+ selection=Body::byId(Body::id_t(selection))->clumpId;
+ }
+
+ setSelectedName(selection);
+ LOG_DEBUG("New selection "<<selection);
+ displayMessage("Selected body #"+boost::lexical_cast<string>(selection)+(Body::byId(selection)->isClump()?" (clump)":""));
+ Omega::instance().getScene()->selectedBody = selection;
+ PyGILState_STATE gstate;
+ gstate = PyGILState_Ensure();
+ boost::python::object main=boost::python::import("__main__");
+ boost::python::object global=main.attr("__dict__");
+ // the try/catch block must be properly nested inside PyGILState_Ensure and PyGILState_Release
+ try{
+ boost::python::eval(string("onBodySelect("+boost::lexical_cast<string>(selection)+")").c_str(),global,global);
+ } catch (boost::python::error_already_set const &) {
+ LOG_DEBUG("unable to call onBodySelect. Not defined?");
+ }
+ PyGILState_Release(gstate);
+ // see https://svn.boost.org/trac/boost/ticket/2781 for exception handling
+ }
+}
+
+// maybe new object will be selected.
+// if so, then set isDynamic of previous selection, to old value
+void GLViewer::endSelection(const QPoint &point){
+ manipulatedClipPlane=-1;
+ QGLViewer::endSelection(point);
+}
+
+string GLViewer::getRealTimeString(){
+ ostringstream oss;
+ boost::posix_time::time_duration t=Omega::instance().getRealTime_duration();
+ unsigned d=t.hours()/24,h=t.hours()%24,m=t.minutes(),s=t.seconds();
+ oss<<"clock ";
+ if(d>0) oss<<d<<"days "<<_W2<<h<<":"<<_W2<<m<<":"<<_W2<<s;
+ else if(h>0) oss<<_W2<<h<<":"<<_W2<<m<<":"<<_W2<<s;
+ else oss<<_W2<<m<<":"<<_W2<<s;
+ return oss.str();
+}
+#undef _W2
+#undef _W3
+
+// cut&paste from QGLViewer::domElement documentation
+QDomElement GLViewer::domElement(const QString& name, QDomDocument& document) const{
+ QDomElement de=document.createElement("grid");
+ string val; if(drawGrid & 1) val+="x"; if(drawGrid & 2)val+="y"; if(drawGrid & 4)val+="z";
+ de.setAttribute("normals",val.c_str());
+ QDomElement de2=document.createElement("timeDisplay"); de2.setAttribute("mask",timeDispMask);
+ QDomElement res=QGLViewer::domElement(name,document);
+ res.appendChild(de);
+ res.appendChild(de2);
+ return res;
+}
+
+// cut&paste from QGLViewer::initFromDomElement documentation
+void GLViewer::initFromDOMElement(const QDomElement& element){
+ QGLViewer::initFromDOMElement(element);
+ QDomElement child=element.firstChild().toElement();
+ while (!child.isNull()){
+ if (child.tagName()=="gridXYZ" && child.hasAttribute("normals")){
+ string val=child.attribute("normals").toLower().toStdString();
+ drawGrid=0;
+ if(val.find("x")!=string::npos) drawGrid+=1; if(val.find("y")!=string::npos)drawGrid+=2; if(val.find("z")!=string::npos)drawGrid+=4;
+ }
+ if(child.tagName()=="timeDisplay" && child.hasAttribute("mask")) timeDispMask=atoi(child.attribute("mask").toLatin1());
+ child = child.nextSibling().toElement();
+ }
+}
+
+boost::posix_time::ptime GLViewer::getLastUserEvent(){return last_user_event;};
+
+#if QGLVIEWER_VERSION>=0x020603
+qreal YadeCamera::zNear() const
+#else
+float YadeCamera::zNear() const
+#endif
+{
+ float z = distanceToSceneCenter() - zClippingCoefficient()*sceneRadius()*(1.f-2*cuttingDistance);
+
+ // Prevents negative or null zNear values.
+ const float zMin = zNearCoefficient() * zClippingCoefficient() * sceneRadius();
+ if (z < zMin)
+/* switch (type())
+ {
+ case Camera::PERSPECTIVE :*/ z = zMin; /*break;
+ case Camera::ORTHOGRAPHIC : z = 0.0; break;
+ }*/
+ return z;
+}
+
+
=== added file 'gui/qt5/GLViewer.hpp'
--- gui/qt5/GLViewer.hpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewer.hpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,163 @@
+// Copyright (C) 2004 by Olivier Galizzi, Janek Kozicki *
+// © 2008 Václav Šmilauer
+#pragma once
+
+#ifndef Q_MOC_RUN
+#include<core/Omega.hpp>
+#include<pkg/common/OpenGLRenderer.hpp>
+#include<pkg/common/PeriodicEngines.hpp>
+#include<boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+#include <QGLViewer/qglviewer.h>
+#include <QGLViewer/manipulatedFrame.h>
+#include <QGLViewer/constraint.h>
+
+using std::setw;
+using std::setfill;
+using std::setprecision;
+
+/*! Class handling user interaction with the openGL rendering of simulation.
+ *
+ * Clipping planes:
+ * ================
+ *
+ * Clipping plane is manipulated after hitting F1, F2, .... To end the manipulation, press Escape.
+ *
+ * Keystrokes during clipping plane manipulation:
+ * * space activates/deactives the clipping plane
+ * * x,y,z aligns the plane with yz, xz, xy planes
+ * * left-double-click aligns the plane with world coordinates system (canonical planes + 45˚ interpositions)
+ * * 1,2,... will align the current plane with #1, #2, ... (same orientation)
+ * * r reverses the plane (normal*=-1)a
+ *
+ * Keystrokes that work regardless of whether a clipping plane is being manipulated:
+ * * Alt-1,Alt-2,... adds/removes the respective plane to bound group:
+ * mutual positions+orientations of planes in the group are maintained when one of those planes is manipulated
+ *
+ * Clip plane number is 3; change YADE_RENDERER_NUM_CLIP_PLANE, complete switches "|| ..." in keyPressEvent
+ * and recompile to have more.
+ */
+class GLViewer : public QGLViewer
+{
+ Q_OBJECT
+
+ friend class QGLThread;
+ protected:
+ shared_ptr<OpenGLRenderer> renderer;
+
+ private :
+
+ bool isMoving;
+ bool wasDynamic;
+ float cut_plane;
+ int cut_plane_delta;
+ bool gridSubdivide;
+ long last;
+ int manipulatedClipPlane;
+ set<int> boundClipPlanes;
+ shared_ptr<qglviewer::LocalConstraint> xyPlaneConstraint;
+ string strBoundGroup(){string ret;FOREACH(int i, boundClipPlanes) ret+=" "+boost::lexical_cast<string>(i+1);return ret;}
+ boost::posix_time::ptime last_user_event;
+
+ public:
+ //virtual void updateGL(void);
+
+ const int viewId;
+
+ void centerMedianQuartile();
+ int drawGrid;
+ bool drawScale;
+ int timeDispMask;
+ enum{TIME_REAL=1,TIME_VIRT=2,TIME_ITER=4};
+
+ GLViewer(int viewId, const shared_ptr<OpenGLRenderer>& renderer, QGLWidget* shareWidget=0);
+ virtual ~GLViewer();
+ #if 0
+ virtual void paintGL();
+ #endif
+ virtual void draw();
+ virtual void drawWithNames();
+ void displayMessage(const std::string& s){ QGLViewer::displayMessage(QString(s.c_str()));}
+ void centerScene();
+ void centerPeriodic();
+ void mouseMovesCamera();
+ void mouseMovesManipulatedFrame(qglviewer::Constraint* c=NULL);
+ void resetManipulation();
+ bool isManipulating();
+ void startClipPlaneManipulation(int planeNo);
+ //! get QGLViewer state as string (XML); QGLViewer normally only supports saving state to file.
+ string getState();
+ //! set QGLViewer state from string (XML); QGLVIewer normally only supports loading state from file.
+ void setState(string);
+ //! Load display parameters (QGLViewer and OpenGLRenderer) from Scene::dispParams[n] and use them
+ void useDisplayParameters(size_t n);
+ //! Save display parameters (QGOViewer and OpenGLRenderer) to Scene::dispParams[n]
+ void saveDisplayParameters(size_t n);
+ //! Get radius of the part of scene that fits the current view
+ float displayedSceneRadius();
+ //! Get center of the part of scene that fits the current view
+ qglviewer::Vec displayedSceneCenter();
+
+ //! Adds our attributes to the QGLViewer state that can be saved
+ QDomElement domElement(const QString& name, QDomDocument& document) const;
+ //! Adds our attributes to the QGLViewer state that can be restored
+ void initFromDOMElement(const QDomElement& element);
+
+ // if defined, snapshot will be saved to this file right after being drawn and the string will be reset.
+ // this way the caller will be notified of the frame being saved successfully.
+ string nextFrameSnapshotFilename;
+ #ifdef YADE_GL2PS
+ // output stream for gl2ps; initialized as needed
+ FILE* gl2psStream;
+ #endif
+
+ boost::posix_time::ptime getLastUserEvent();
+
+
+ DECLARE_LOGGER;
+ protected :
+ virtual void keyPressEvent(QKeyEvent *e);
+ virtual void postDraw();
+ // overridden in the player that doesn't get time from system clock but from the db
+ virtual string getRealTimeString();
+ virtual void closeEvent(QCloseEvent *e);
+ virtual void postSelection(const QPoint& point);
+ virtual void endSelection(const QPoint &point);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+ virtual void wheelEvent(QWheelEvent* e);
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mousePressEvent(QMouseEvent *e);
+};
+
+/*! Get unconditional lock on a GL view.
+
+Use if you need to manipulate GL context in some way.
+The ctor doesn't return until the lock has been acquired
+and the lock is released when the GLLock object is desctructed;
+*/
+class GLLock: public boost::try_mutex::scoped_lock{
+ GLViewer* glv;
+ public:
+ GLLock(GLViewer* _glv);
+ ~GLLock();
+};
+
+
+class YadeCamera : public qglviewer::Camera
+{
+ Q_OBJECT
+ private:
+ float cuttingDistance;
+ public :
+ YadeCamera():cuttingDistance(0){};
+#if QGLVIEWER_VERSION>=0x020603
+ virtual qreal zNear() const;
+#else
+ virtual float zNear() const;
+#endif
+ virtual void setCuttingDistance(float s){cuttingDistance=s;};
+};
+
+
+
=== added file 'gui/qt5/GLViewerDisplay.cpp'
--- gui/qt5/GLViewerDisplay.cpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewerDisplay.cpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,287 @@
+/*************************************************************************
+* Copyright (C) 2004 by Olivier Galizzi *
+* olivier.galizzi@xxxxxxx *
+* Copyright (C) 2005 by Janek Kozicki *
+* cosurgi@xxxxxxxxxx *
+* *
+* This program is free software; it is licensed under the terms of the *
+* GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+
+
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+ #include<gl2ps.h>
+#endif
+
+void GLViewer::useDisplayParameters(size_t n){
+ LOG_DEBUG("Loading display parameters from #"<<n);
+ vector<shared_ptr<DisplayParameters> >& dispParams=Omega::instance().getScene()->dispParams;
+ if(dispParams.size()<=(size_t)n){ throw std::invalid_argument(("Display parameters #"+boost::lexical_cast<string>(n)+" don't exist (number of entries "+boost::lexical_cast<string>(dispParams.size())+")").c_str());; return;}
+ const shared_ptr<DisplayParameters>& dp=dispParams[n];
+ string val;
+ if(dp->getValue("OpenGLRenderer",val)){ istringstream oglre(val);
+ yade::ObjectIO::load<decltype(renderer),boost::archive::xml_iarchive>(oglre,"renderer",renderer);
+ }
+ else { LOG_WARN("OpenGLRenderer configuration not found in display parameters, skipped.");}
+ if(dp->getValue("GLViewer",val)){ GLViewer::setState(val); displayMessage("Loaded view configuration #"+boost::lexical_cast<string>(n)); }
+ else { LOG_WARN("GLViewer configuration not found in display parameters, skipped."); }
+}
+
+void GLViewer::saveDisplayParameters(size_t n){
+ LOG_DEBUG("Saving display parameters to #"<<n);
+ vector<shared_ptr<DisplayParameters> >& dispParams=Omega::instance().getScene()->dispParams;
+ if(dispParams.size()<=n){while(dispParams.size()<=n) dispParams.push_back(shared_ptr<DisplayParameters>(new DisplayParameters));} assert(n<dispParams.size());
+ shared_ptr<DisplayParameters>& dp=dispParams[n];
+ ostringstream oglre;
+ yade::ObjectIO::save<decltype(renderer),boost::archive::xml_oarchive>(oglre,"renderer",renderer);
+ dp->setValue("OpenGLRenderer",oglre.str());
+ dp->setValue("GLViewer",GLViewer::getState());
+ displayMessage("Saved view configuration ot #"+boost::lexical_cast<string>(n));
+}
+
+void GLViewer::draw()
+{
+#ifdef YADE_GL2PS
+ if(!nextFrameSnapshotFilename.empty() && boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){
+ gl2psStream=fopen(nextFrameSnapshotFilename.c_str(),"wb");
+ if(!gl2psStream){ int err=errno; throw runtime_error(string("Error opening file ")+nextFrameSnapshotFilename+": "+strerror(err)); }
+ LOG_DEBUG("Start saving snapshot to "<<nextFrameSnapshotFilename);
+ size_t nBodies=Omega::instance().getScene()->bodies->size();
+ int sortAlgo=(nBodies<100 ? GL2PS_BSP_SORT : GL2PS_SIMPLE_SORT);
+ gl2psBeginPage(/*const char *title*/"Some title", /*const char *producer*/ "Yade",
+ /*GLint viewport[4]*/ NULL,
+ /*GLint format*/ GL2PS_PDF, /*GLint sort*/ sortAlgo, /*GLint options*/GL2PS_SIMPLE_LINE_OFFSET|GL2PS_USE_CURRENT_VIEWPORT|GL2PS_TIGHT_BOUNDING_BOX|GL2PS_COMPRESS|GL2PS_OCCLUSION_CULL|GL2PS_NO_BLENDING,
+ /*GLint colormode*/ GL_RGBA, /*GLint colorsize*/0,
+ /*GL2PSrgba *colortable*/NULL,
+ /*GLint nr*/0, /*GLint ng*/0, /*GLint nb*/0,
+ /*GLint buffersize*/4096*4096 /* 16MB */, /*FILE *stream*/ gl2psStream,
+ /*const char *filename*/NULL);
+ }
+#endif
+
+ qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]);
+ if(Omega::instance().getScene()){
+ const shared_ptr<Scene>& scene=Omega::instance().getScene();
+ int selection = selectedName();
+ if(selection!=-1 && (*(Omega::instance().getScene()->bodies)).exists(selection) && isMoving){
+ static Real lastTimeMoved(0);
+#if QGLVIEWER_VERSION>=0x020603
+ qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#else
+ float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#endif
+ if(last == selection) // delay by one redraw, so the body will not jump into 0,0,0 coords
+ {
+ Quaternionr& q = (*(Omega::instance().getScene()->bodies))[selection]->state->ori;
+ Vector3r& v = (*(Omega::instance().getScene()->bodies))[selection]->state->pos;
+ Vector3r& vel = (*(Omega::instance().getScene()->bodies))[selection]->state->vel;
+ Vector3r& angVel = (*(Omega::instance().getScene()->bodies))[selection]->state->angVel;
+ angVel=Vector3r::Zero();
+ Real dt=(scene->time-lastTimeMoved); lastTimeMoved=scene->time;
+ if (dt!=0) { vel[0]=-(v[0]-v0)/dt; vel[1]=-(v[1]-v1)/dt; vel[2]=-(v[2]-v2)/dt;}
+ else vel[0]=vel[1]=vel[2]=0;
+ //FIXME: should update spin like velocity above, when the body is rotated:
+ double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3); q.x()=q0;q.y()=q1;q.z()=q2;q.w()=q3;
+ }
+ (*(Omega::instance().getScene()->bodies))[selection]->userForcedDisplacementRedrawHook();
+ }
+ if(manipulatedClipPlane>=0){
+ assert(manipulatedClipPlane<renderer->numClipPlanes);
+#if QGLVIEWER_VERSION>=0x020603
+ qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#else
+ float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#endif
+ double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3);
+ Se3r newSe3(Vector3r(v0,v1,v2),Quaternionr(q0,q1,q2,q3)); newSe3.orientation.normalize();
+ const Se3r& oldSe3=renderer->clipPlaneSe3[manipulatedClipPlane];
+ FOREACH(int planeId, boundClipPlanes){
+ if(planeId>=renderer->numClipPlanes || !renderer->clipPlaneActive[planeId] || planeId==manipulatedClipPlane) continue;
+ Se3r& boundSe3=renderer->clipPlaneSe3[planeId];
+ Quaternionr relOrient=oldSe3.orientation.conjugate()*boundSe3.orientation; relOrient.normalize();
+ Vector3r relPos=oldSe3.orientation.conjugate()*(boundSe3.position-oldSe3.position);
+ boundSe3.position=newSe3.position+newSe3.orientation*relPos;
+ boundSe3.orientation=newSe3.orientation*relOrient;
+ boundSe3.orientation.normalize();
+ }
+ renderer->clipPlaneSe3[manipulatedClipPlane]=newSe3;
+ }
+ scene->renderer=renderer;
+ renderer->render(scene, selectedName());
+ }
+}
+
+void GLViewer::drawWithNames(){
+ qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]);
+ const shared_ptr<Scene> scene(Omega::instance().getScene());
+ scene->renderer=renderer;
+ renderer->scene=scene;
+ renderer->renderShape();
+}
+
+
+qglviewer::Vec GLViewer::displayedSceneCenter(){
+ return camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2 /* pixels */ ,height()/2 /* pixels */, /*middle between near plane and far plane*/ .5));
+}
+
+float GLViewer::displayedSceneRadius(){
+ return (camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2,height()/2,.5))-camera()->unprojectedCoordinatesOf(qglviewer::Vec(0,0,.5))).norm();
+}
+
+void GLViewer::postDraw(){
+ Real wholeDiameter=QGLViewer::camera()->sceneRadius()*2;
+
+ renderer->viewInfo.sceneRadius=QGLViewer::camera()->sceneRadius();
+ qglviewer::Vec c=QGLViewer::camera()->sceneCenter();
+ renderer->viewInfo.sceneCenter=Vector3r(c[0],c[1],c[2]);
+
+ Real dispDiameter=min(wholeDiameter,max((Real)displayedSceneRadius()*2,wholeDiameter/1e3)); // limit to avoid drawing 1e5 lines with big zoom level
+ //qglviewer::Vec center=QGLViewer::camera()->sceneCenter();
+ Real gridStep=pow(10,(floor(log10(dispDiameter)-.7)));
+ Real scaleStep=pow(10,(floor(log10(displayedSceneRadius()*2)-.7))); // unconstrained
+ int nSegments=((int)(wholeDiameter/gridStep))+1;
+ Real realSize=nSegments*gridStep;
+ //LOG_TRACE("nSegments="<<nSegments<<",gridStep="<<gridStep<<",realSize="<<realSize);
+ glPushMatrix();
+
+ nSegments *= 2; // there's an error in QGLViewer::drawGrid(), so we need to mitigate it by '* 2'
+ // XYZ grids
+ glLineWidth(.5);
+ if(drawGrid & 1) {glColor3f(0.6,0.3,0.3); glPushMatrix(); glRotated(90.,0.,1.,0.); QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+ if(drawGrid & 2) {glColor3f(0.3,0.6,0.3); glPushMatrix(); glRotated(90.,1.,0.,0.); QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+ if(drawGrid & 4) {glColor3f(0.3,0.3,0.6); glPushMatrix(); /*glRotated(90.,0.,1.,0.);*/ QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+ if(gridSubdivide){
+ if(drawGrid & 1) {glColor3f(0.4,0.1,0.1); glPushMatrix(); glRotated(90.,0.,1.,0.); QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+ if(drawGrid & 2) {glColor3f(0.1,0.4,0.1); glPushMatrix(); glRotated(90.,1.,0.,0.); QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+ if(drawGrid & 4) {glColor3f(0.1,0.1,0.4); glPushMatrix(); /*glRotated(90.,0.,1.,0.);*/ QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+ }
+
+ // scale
+ if(drawScale){
+ Real segmentSize=scaleStep;
+ qglviewer::Vec screenDxDy[3]; // dx,dy for x,y,z scale segments
+ int extremalDxDy[2]={0,0};
+ for(int axis=0; axis<3; axis++){
+ qglviewer::Vec delta(0,0,0); delta[axis]=segmentSize;
+ qglviewer::Vec center=displayedSceneCenter();
+ screenDxDy[axis]=camera()->projectedCoordinatesOf(center+delta)-camera()->projectedCoordinatesOf(center);
+ for(int xy=0;xy<2;xy++)extremalDxDy[xy]=(axis>0 ? min(extremalDxDy[xy],(int)screenDxDy[axis][xy]) : screenDxDy[axis][xy]);
+ }
+ //LOG_DEBUG("Screen offsets for axes: "<<" x("<<screenDxDy[0][0]<<","<<screenDxDy[0][1]<<") y("<<screenDxDy[1][0]<<","<<screenDxDy[1][1]<<") z("<<screenDxDy[2][0]<<","<<screenDxDy[2][1]<<")");
+ int margin=10; // screen pixels
+ int scaleCenter[2]; scaleCenter[0]=std::abs(extremalDxDy[0])+margin; scaleCenter[1]=std::abs(extremalDxDy[1])+margin;
+ //LOG_DEBUG("Center of scale "<<scaleCenter[0]<<","<<scaleCenter[1]);
+ //displayMessage(QString().sprintf("displayed scene radius %g",displayedSceneRadius()));
+ startScreenCoordinatesSystem();
+ glDisable(GL_LIGHTING);
+ glDisable(GL_DEPTH_TEST);
+ glLineWidth(3.0);
+ for(int axis=0; axis<3; axis++){
+ Vector3r color(.4,.4,.4); color[axis]=.9;
+ glColor3v(color);
+ glBegin(GL_LINES);
+ glVertex2f(scaleCenter[0],scaleCenter[1]);
+ glVertex2f(scaleCenter[0]+screenDxDy[axis][0],scaleCenter[1]+screenDxDy[axis][1]);
+ glEnd();
+ }
+ glLineWidth(1.);
+ glEnable(GL_DEPTH_TEST);
+ QGLViewer::drawText(scaleCenter[0],scaleCenter[1],QString().sprintf("%.3g",(double)scaleStep));
+ stopScreenCoordinatesSystem();
+ }
+
+ // cutting planes (should be moved to OpenGLRenderer perhaps?)
+ // only painted if one of those is being manipulated
+ if(manipulatedClipPlane>=0){
+ for(int planeId=0; planeId<renderer->numClipPlanes; planeId++){
+ if(!renderer->clipPlaneActive[planeId] && planeId!=manipulatedClipPlane) continue;
+ glPushMatrix();
+ const Se3r& se3=renderer->clipPlaneSe3[planeId];
+ AngleAxisr aa(se3.orientation);
+ glTranslatef(se3.position[0],se3.position[1],se3.position[2]);
+ glRotated(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]);
+ Real cff=1;
+ if(!renderer->clipPlaneActive[planeId]) cff=.4;
+ glColor3f(max((Real)0.,cff*cos(planeId)),max((Real)0.,cff*sin(planeId)),planeId==manipulatedClipPlane); // variable colors
+ QGLViewer::drawGrid(realSize,2*nSegments);
+ drawArrow(wholeDiameter/6);
+ glPopMatrix();
+ }
+ }
+
+ Scene* rb=Omega::instance().getScene().get();
+ #define _W3 setw(3)<<setfill('0')
+ #define _W2 setw(2)<<setfill('0')
+ if(timeDispMask!=0){
+ const int lineHt=13;
+ unsigned x=10,y=height()-3-lineHt*2;
+ glColor3v(Vector3r(1,1,1));
+ if(timeDispMask & GLViewer::TIME_VIRT){
+ ostringstream oss;
+ const Real& t=Omega::instance().getScene()->time;
+ unsigned min=((unsigned)t/60),sec=(((unsigned)t)%60),msec=((unsigned)(1e3*t))%1000,usec=((unsigned long)(1e6*t))%1000,nsec=((unsigned long)(1e9*t))%1000;
+ if(min>0) oss<<_W2<<min<<":"<<_W2<<sec<<"."<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+ else if (sec>0) oss<<_W2<<sec<<"."<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+ else if (msec>0) oss<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+ else if (usec>0) oss<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+ else oss<<_W3<<nsec<<"ns";
+ QGLViewer::drawText(x,y,oss.str().c_str());
+ y-=lineHt;
+ }
+ glColor3v(Vector3r(0,.5,.5));
+ if(timeDispMask & GLViewer::TIME_REAL){
+ QGLViewer::drawText(x,y,getRealTimeString().c_str() /* virtual, since player gets that from db */);
+ y-=lineHt;
+ }
+ if(timeDispMask & GLViewer::TIME_ITER){
+ ostringstream oss;
+ oss<<"#"<<rb->iter;
+ if(rb->stopAtIter>rb->iter) oss<<" ("<<setiosflags(ios::fixed)<<setw(3)<<setprecision(1)<<setfill('0')<<(100.*rb->iter)/rb->stopAtIter<<"%)";
+ QGLViewer::drawText(x,y,oss.str().c_str());
+ y-=lineHt;
+ }
+ if(drawGrid){
+ glColor3v(Vector3r(1,1,0));
+ ostringstream oss;
+ oss<<"grid: "<<setprecision(4)<<gridStep;
+ if(gridSubdivide) oss<<" (minor "<<setprecision(3)<<gridStep*.1<<")";
+ QGLViewer::drawText(x,y,oss.str().c_str());
+ y-=lineHt;
+ }
+ }
+ QGLViewer::postDraw();
+ if(!nextFrameSnapshotFilename.empty()){
+ #ifdef YADE_GL2PS
+ if(boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){
+ gl2psEndPage();
+ LOG_DEBUG("Finished saving snapshot to "<<nextFrameSnapshotFilename);
+ fclose(gl2psStream);
+ } else
+ #endif
+ {
+ // save the snapshot
+ saveSnapshot(QString(nextFrameSnapshotFilename.c_str()),/*overwrite*/ true);
+ }
+ // notify the caller that it is done already (probably not an atomic op :-|, though)
+ nextFrameSnapshotFilename.clear();
+ }
+}
+
+
=== added file 'gui/qt5/GLViewerMouse.cpp'
--- gui/qt5/GLViewerMouse.cpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewerMouse.cpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,113 @@
+/*************************************************************************
+* Copyright (C) 2004 by Olivier Galizzi *
+* olivier.galizzi@xxxxxxx *
+* Copyright (C) 2005 by Janek Kozicki *
+* cosurgi@xxxxxxxxxx *
+* *
+* This program is free software; it is licensed under the terms of the *
+* GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+#include<QGLViewer/manipulatedCameraFrame.h>
+
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+ #include<gl2ps.h>
+#endif
+
+void GLViewer::mouseMovesCamera(){
+ setWheelBinding(Qt::ShiftModifier , FRAME, ZOOM);
+ setWheelBinding(Qt::NoModifier, CAMERA, ZOOM);
+
+#if QGLVIEWER_VERSION>=0x020500
+ setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ZOOM);
+ setMouseBinding(Qt::ShiftModifier, Qt::MidButton, FRAME, TRANSLATE);
+ setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ROTATE);
+
+ setMouseBinding(Qt::NoModifier, Qt::MidButton, CAMERA, ZOOM);
+ setMouseBinding(Qt::NoModifier, Qt::LeftButton, CAMERA, ROTATE);
+ setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, TRANSLATE);
+#else
+ setMouseBinding(Qt::SHIFT + Qt::LeftButton, SELECT);
+ setMouseBinding(Qt::SHIFT + Qt::LeftButton + Qt::RightButton, FRAME, ZOOM);
+ setMouseBinding(Qt::SHIFT + Qt::MidButton, FRAME, TRANSLATE);
+ setMouseBinding(Qt::SHIFT + Qt::RightButton, FRAME, ROTATE);
+
+ setMouseBinding(Qt::LeftButton + Qt::RightButton, CAMERA, ZOOM);
+ setMouseBinding(Qt::MidButton, CAMERA, ZOOM);
+ setMouseBinding(Qt::LeftButton, CAMERA, ROTATE);
+ setMouseBinding(Qt::RightButton, CAMERA, TRANSLATE);
+ camera()->frame()->setWheelSensitivity(-1.0f);
+#endif
+};
+
+void GLViewer::mouseMovesManipulatedFrame(qglviewer::Constraint* c){
+ setMouseBinding(Qt::LeftButton + Qt::RightButton, FRAME, ZOOM);
+ setMouseBinding(Qt::MidButton, FRAME, ZOOM);
+ setMouseBinding(Qt::LeftButton, FRAME, ROTATE);
+ setMouseBinding(Qt::RightButton, FRAME, TRANSLATE);
+ setWheelBinding(Qt::NoModifier , FRAME, ZOOM);
+ manipulatedFrame()->setConstraint(c);
+}
+
+
+void GLViewer::mouseMoveEvent(QMouseEvent *e){
+ last_user_event = boost::posix_time::second_clock::local_time();
+ QGLViewer::mouseMoveEvent(e);
+}
+
+void GLViewer::mousePressEvent(QMouseEvent *e){
+ last_user_event = boost::posix_time::second_clock::local_time();
+ QGLViewer::mousePressEvent(e);
+}
+
+/* Handle double-click event; if clipping plane is manipulated, align it with the global coordinate system.
+ * Otherwise pass the event to QGLViewer to handle it normally.
+ *
+ * mostly copied over from ManipulatedFrame::mouseDoubleClickEvent
+ */
+void GLViewer::mouseDoubleClickEvent(QMouseEvent *event){
+ last_user_event = boost::posix_time::second_clock::local_time();
+
+ if(manipulatedClipPlane<0) { /* LOG_DEBUG("Double click not on clipping plane"); */ QGLViewer::mouseDoubleClickEvent(event); return; }
+#if QT_VERSION >= 0x040000
+ if (event->modifiers() == Qt::NoModifier)
+#else
+ if (event->state() == Qt::NoButton)
+#endif
+ switch (event->button()){
+ case Qt::LeftButton: manipulatedFrame()->alignWithFrame(NULL,true); LOG_DEBUG("Aligning cutting plane"); break;
+ default: break; // avoid warning
+ }
+}
+
+void GLViewer::wheelEvent(QWheelEvent* event){
+ last_user_event = boost::posix_time::second_clock::local_time();
+
+ if(manipulatedClipPlane<0){ QGLViewer::wheelEvent(event); return; }
+ assert(manipulatedClipPlane<renderer->numClipPlanes);
+ float distStep=1e-3*sceneRadius();
+ float dist=event->delta()*manipulatedFrame()->wheelSensitivity()*distStep;
+ Vector3r normal=renderer->clipPlaneSe3[manipulatedClipPlane].orientation*Vector3r(0,0,1);
+ qglviewer::Vec newPos=manipulatedFrame()->position()+qglviewer::Vec(normal[0],normal[1],normal[2])*dist;
+ manipulatedFrame()->setPosition(newPos);
+ renderer->clipPlaneSe3[manipulatedClipPlane].position=Vector3r(newPos[0],newPos[1],newPos[2]);
+ updateGL();
+ /* in draw, bound cutting planes will be moved as well */
+}
+
=== added file 'gui/qt5/Inspector.py'
--- gui/qt5/Inspector.py 1970-01-01 00:00:00 +0000
+++ gui/qt5/Inspector.py 2015-06-26 20:19:22 +0000
@@ -0,0 +1,285 @@
+# encoding: utf-8
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from yade import *
+from yade.qt.SerializableEditor import *
+import yade.qt
+
+
+class EngineInspector(QWidget):
+ def __init__(self,parent=None):
+ QWidget.__init__(self,parent)
+ grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+ self.serEd=SeqSerializable(parent=None,getter=lambda:O.engines,setter=lambda x:setattr(O,'engines',x),serType=Engine,path='O.engines')
+ grid.addWidget(self.serEd)
+ self.setLayout(grid)
+#class MaterialsInspector(QWidget):
+# def __init__(self,parent=None):
+# QWidget.__init__(self,parent)
+# grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+# self.serEd=SeqSerializable(parent=None,getter=lambda:O.materials,setter=lambda x:setattr(O,'materials',x),serType=Engine)
+# grid.addWidget(self.serEd)
+# self.setLayout(grid)
+
+class CellInspector(QWidget):
+ def __init__(self,parent=None):
+ QWidget.__init__(self,parent)
+ self.layout=QVBoxLayout(self) #; self.layout.setSpacing(0); self.layout.setMargin(0)
+ self.periCheckBox=QCheckBox('periodic boundary',self)
+ self.periCheckBox.clicked.connect(self.update)
+ self.layout.addWidget(self.periCheckBox)
+ self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True)
+ self.layout.addWidget(self.scroll)
+ self.setLayout(self.layout)
+ self.refresh()
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refresh)
+ self.refreshTimer.start(1000)
+ def refresh(self):
+ self.periCheckBox.setChecked(O.periodic)
+ editor=self.scroll.widget()
+ if not O.periodic and editor: self.scroll.takeWidget()
+ if (O.periodic and not editor) or (editor and editor.ser!=O.cell):
+ self.scroll.setWidget(SerializableEditor(O.cell,parent=self,showType=True,path='O.cell'))
+ def update(self):
+ self.scroll.takeWidget() # do this before changing periodicity, otherwise the SerializableEditor will raise exception about None object
+ O.periodic=self.periCheckBox.isChecked()
+ self.refresh()
+
+
+
+def makeBodyLabel(b):
+ ret=unicode(b.id)+u' '
+ if not b.shape: ret+=u'⬚'
+ else:
+ typeMap={'Sphere':u'⚫','Facet':u'△','Wall':u'┃','Box':u'⎕','Cylinder':u'⌭','ChainedCylinder':u'☡','Clump':u'☍'}
+ ret+=typeMap.get(b.shape.__class__.__name__,u'﹖')
+ if not b.dynamic: ret+=u'⚓'
+ elif b.state.blockedDOFs: ret+=u'⎈'
+ return ret
+
+def getBodyIdFromLabel(label):
+ try:
+ return int(unicode(label).split()[0])
+ except ValueError:
+ print 'Error with label:',unicode(label)
+ return -1
+
+class BodyInspector(QWidget):
+ def __init__(self,bodyId=-1,parent=None,bodyLinkCallback=None,intrLinkCallback=None):
+ QWidget.__init__(self,parent)
+ v=yade.qt.views()
+ self.bodyId=bodyId
+ if bodyId<0 and len(v)>0 and v[0].selection>0: self.bodyId=v[0].selection
+ self.idGlSync=self.bodyId
+ self.bodyLinkCallback,self.intrLinkCallback=bodyLinkCallback,intrLinkCallback
+ self.bodyIdBox=QSpinBox(self)
+ self.bodyIdBox.setMinimum(0)
+ self.bodyIdBox.setMaximum(100000000)
+ self.bodyIdBox.setValue(self.bodyId)
+ self.intrWithCombo=QComboBox(self);
+ self.gotoBodyButton=QPushButton(u'→ #',self)
+ self.gotoIntrButton=QPushButton(u'→ #+#',self)
+ # id selector
+ topBoxWidget=QWidget(self); topBox=QHBoxLayout(topBoxWidget); topBox.setMargin(0); #topBox.setSpacing(0);
+ hashLabel=QLabel('#',self); hashLabel.setFixedWidth(8)
+ topBox.addWidget(hashLabel)
+ topBox.addWidget(self.bodyIdBox)
+ self.plusLabel=QLabel('+',self); topBox.addWidget(self.plusLabel)
+ hashLabel2=QLabel('#',self); hashLabel2.setFixedWidth(8); topBox.addWidget(hashLabel2)
+ topBox.addWidget(self.intrWithCombo)
+ topBox.addStretch()
+ topBox.addWidget(self.gotoBodyButton)
+ topBox.addWidget(self.gotoIntrButton)
+ topBoxWidget.setLayout(topBox)
+ # forces display
+ forcesWidget=QFrame(self); forcesWidget.setFrameShape(QFrame.Box); self.forceGrid=QGridLayout(forcesWidget);
+ self.forceGrid.setVerticalSpacing(0); self.forceGrid.setHorizontalSpacing(9); self.forceGrid.setMargin(4);
+ for i,j in itertools.product((0,1,2,3),(-1,0,1,2)):
+ lab=QLabel('<small>'+('force','torque','move','rot')[i]+'</small>' if j==-1 else ''); self.forceGrid.addWidget(lab,i,j+1);
+ if j>=0: lab.setAlignment(Qt.AlignRight)
+ if i>1: lab.hide() # do not show forced moves and rotations by default (they will appear if non-zero)
+ self.showMovRot=False
+ #
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ self.grid.addWidget(topBoxWidget)
+ self.grid.addWidget(forcesWidget)
+ self.scroll=QScrollArea(self)
+ self.scroll.setWidgetResizable(True)
+ self.grid.addWidget(self.scroll)
+ self.tryShowBody()
+ self.bodyIdBox.valueChanged.connect(self.bodyIdSlot)
+ self.gotoBodyButton.clicked.connect(self.gotoBodySlot)
+ self.gotoIntrButton.clicked.connect(self.gotoIntrSlot)
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(1000)
+ self.intrWithCombo.addItems(['0']); self.intrWithCombo.setCurrentIndex(0);
+ self.intrWithCombo.setMinimumWidth(80)
+ self.setWindowTitle('Body #%d'%self.bodyId)
+ self.gotoBodySlot()
+ def displayForces(self):
+ if self.bodyId<0: return
+ try:
+ val=[O.forces.f(self.bodyId),O.forces.t(self.bodyId),O.forces.move(self.bodyId),O.forces.rot(self.bodyId)]
+ hasMovRot=(val[2]!=Vector3.Zero or val[3]!=Vector3.Zero)
+ if hasMovRot!=self.showMovRot:
+ for i,j in itertools.product((2,3),(-1,0,1,2)):
+ if hasMovRot: self.forceGrid.itemAtPosition(i,j+1).widget().show()
+ else: self.forceGrid.itemAtPosition(i,j+1).widget().hide()
+ self.showMovRot=hasMovRot
+ rows=((0,1,2,3) if hasMovRot else (0,1))
+ for i,j in itertools.product(rows,(0,1,2)):
+ self.forceGrid.itemAtPosition(i,j+1).widget().setText('<small>'+str(val[i][j])+'</small>')
+ except IndexError:pass
+ def tryShowBody(self):
+ try:
+ b=O.bodies[self.bodyId]
+ self.serEd=SerializableEditor(b,showType=True,parent=self,path='O.bodies[%d]'%self.bodyId)
+ except IndexError:
+ self.serEd=QFrame(self)
+ self.bodyId=-1
+ self.scroll.setWidget(self.serEd)
+ def changeIdSlot(self,newId):
+ self.bodyIdBox.setValue(newId);
+ self.bodyIdSlot()
+ def bodyIdSlot(self):
+ self.bodyId=self.bodyIdBox.value()
+ self.tryShowBody()
+ self.setWindowTitle('Body #%d'%self.bodyId)
+ self.refreshEvent()
+ def gotoBodySlot(self):
+ try:
+ id=int(getBodyIdFromLabel(self.intrWithCombo.currentText()))
+ except ValueError: return # empty id
+ if not self.bodyLinkCallback:
+ self.bodyIdBox.setValue(id); self.bodyId=id
+ else: self.bodyLinkCallback(id)
+ def gotoIntrSlot(self):
+ ids=self.bodyIdBox.value(),getBodyIdFromLabel(self.intrWithCombo.currentText())
+ if not self.intrLinkCallback:
+ self.ii=InteractionInspector(ids)
+ self.ii.show()
+ else: self.intrLinkCallback(ids)
+ def refreshEvent(self):
+ try: O.bodies[self.bodyId]
+ except: self.bodyId=-1 # invalidate deleted body
+ # no body shown yet, try to get the first one...
+ if self.bodyId<0 and len(O.bodies)>0:
+ try:
+ print 'SET ZERO'
+ b=O.bodies[0]; self.bodyIdBox.setValue(0)
+ except IndexError: pass
+ v=yade.qt.views()
+ if len(v)>0 and v[0].selection!=self.bodyId:
+ if self.idGlSync==self.bodyId: # changed in the viewer, reset ourselves
+ self.bodyId=self.idGlSync=v[0].selection; self.changeIdSlot(self.bodyId)
+ return
+ else: v[0].selection=self.idGlSync=self.bodyId # changed here, set in the viewer
+ meId=self.bodyIdBox.value(); pos=self.intrWithCombo.currentIndex()
+ try:
+ meLabel=makeBodyLabel(O.bodies[meId])
+ except IndexError: meLabel=u'…'
+ self.plusLabel.setText(' '.join(meLabel.split()[1:])+' <b>+</b>') # do not repeat the id
+ self.bodyIdBox.setMaximum(len(O.bodies)-1)
+ others=[(i.id1 if i.id1!=meId else i.id2) for i in O.interactions.withBody(self.bodyIdBox.value()) if i.isReal]
+ others.sort()
+ self.intrWithCombo.clear()
+ self.intrWithCombo.addItems([makeBodyLabel(O.bodies[i]) for i in others])
+ if pos>self.intrWithCombo.count() or pos<0: pos=0
+ self.intrWithCombo.setCurrentIndex(pos);
+ other=self.intrWithCombo.itemText(pos)
+ if other=='':
+ self.gotoBodyButton.setEnabled(False); self.gotoIntrButton.setEnabled(False)
+ other=u'∅'
+ else:
+ self.gotoBodyButton.setEnabled(True); self.gotoIntrButton.setEnabled(True)
+ self.gotoBodyButton.setText(u'→ %s'%other)
+ self.gotoIntrButton.setText(u'→ %s + %s'%(meLabel,other))
+ self.displayForces()
+
+class InteractionInspector(QWidget):
+ def __init__(self,ids=None,parent=None,bodyLinkCallback=None):
+ QWidget.__init__(self,parent)
+ self.bodyLinkCallback=bodyLinkCallback
+ self.ids=ids
+ self.gotoId1Button=QPushButton(u'#…',self)
+ self.gotoId2Button=QPushButton(u'#…',self)
+ self.gotoId1Button.clicked.connect(self.gotoId1Slot)
+ self.gotoId2Button.clicked.connect(self.gotoId2Slot)
+ topBoxWidget=QWidget(self)
+ topBox=QHBoxLayout(topBoxWidget)
+ topBox.addWidget(self.gotoId1Button)
+ labelPlus=QLabel('+',self); labelPlus.setAlignment(Qt.AlignHCenter)
+ topBox.addWidget(labelPlus)
+ topBox.addWidget(self.gotoId2Button)
+ topBoxWidget.setLayout(topBox)
+ self.setWindowTitle(u'No interaction')
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ self.grid.addWidget(topBoxWidget,0,0)
+ self.scroll=QScrollArea(self)
+ self.scroll.setWidgetResizable(True)
+ self.grid.addWidget(self.scroll)
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(1000)
+ if self.ids: self.setupInteraction()
+ def setupInteraction(self):
+ try:
+ intr=O.interactions[self.ids[0],self.ids[1]]
+ self.serEd=SerializableEditor(intr,showType=True,parent=self.scroll,path='O.interactions[%d,%d]'%(self.ids[0],self.ids[1]))
+ self.scroll.setWidget(self.serEd)
+ self.gotoId1Button.setText('#'+makeBodyLabel(O.bodies[self.ids[0]]))
+ self.gotoId2Button.setText('#'+makeBodyLabel(O.bodies[self.ids[1]]))
+ self.setWindowTitle('Interaction #%d + #%d'%(self.ids[0],self.ids[1]))
+ except IndexError:
+ if self.ids: # reset view (there was an interaction)
+ self.ids=None
+ self.serEd=QFrame(self.scroll); self.scroll.setWidget(self.serEd)
+ self.setWindowTitle('No interaction')
+ self.gotoId1Button.setText(u'#…'); self.gotoId2Button.setText(u'#…');
+ def gotoId(self,bodyId):
+ if self.bodyLinkCallback: self.bodyLinkCallback(bodyId)
+ else: self.bi=BodyInspector(bodyId); self.bi.show()
+ def gotoId1Slot(self): self.gotoId(self.ids[0])
+ def gotoId2Slot(self): self.gotoId(self.ids[1])
+ def refreshEvent(self):
+ # no ids yet -- try getting the first interaction, if it exists
+ if not self.ids:
+ try:
+ i=O.interactions.nth(0)
+ self.ids=i.id1,i.id2
+ self.setupInteraction()
+ return
+ except IndexError: return # no interaction exists at all
+ try: # try to fetch an existing interaction
+ O.interactions[self.ids[0],self.ids[1]]
+ except IndexError:
+ self.ids=None
+
+class SimulationInspector(QWidget):
+ def __init__(self,parent=None):
+ QWidget.__init__(self,parent)
+ self.setWindowTitle("Simulation Inspection")
+ self.tabWidget=QTabWidget(self)
+
+ self.engineInspector=EngineInspector(parent=None)
+ self.bodyInspector=BodyInspector(parent=None,intrLinkCallback=self.changeIntrIds)
+ self.intrInspector=InteractionInspector(parent=None,bodyLinkCallback=self.changeBodyId)
+ self.cellInspector=CellInspector(parent=None)
+
+ for i,name,widget in [(0,'Engines',self.engineInspector),(1,'Bodies',self.bodyInspector),(2,'Interactions',self.intrInspector),(3,'Cell',self.cellInspector)]:
+ self.tabWidget.addTab(widget,name)
+ grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+ grid.addWidget(self.tabWidget)
+ self.setLayout(grid)
+ def changeIntrIds(self,ids):
+ self.tabWidget.removeTab(2); self.intrInspector.close()
+ self.intrInspector=InteractionInspector(ids=ids,parent=None,bodyLinkCallback=self.changeBodyId)
+ self.tabWidget.insertTab(2,self.intrInspector,'Interactions')
+ self.tabWidget.setCurrentIndex(2)
+ def changeBodyId(self,id):
+ self.bodyInspector.changeIdSlot(id)
+ self.tabWidget.setCurrentIndex(1)
+
=== added file 'gui/qt5/OpenGLManager.cpp'
--- gui/qt5/OpenGLManager.cpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/OpenGLManager.cpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,76 @@
+#include"OpenGLManager.hpp"
+
+CREATE_LOGGER(OpenGLManager);
+
+OpenGLManager* OpenGLManager::self=NULL;
+
+OpenGLManager::OpenGLManager(QObject* parent): QObject(parent){
+ if(self) throw runtime_error("OpenGLManager instance already exists, uses OpenGLManager::self to retrieve it.");
+ self=this;
+ renderer=shared_ptr<OpenGLRenderer>(new OpenGLRenderer);
+ renderer->init();
+ connect(this,SIGNAL(createView()),this,SLOT(createViewSlot()));
+ connect(this,SIGNAL(resizeView(int,int,int)),this,SLOT(resizeViewSlot(int,int,int)));
+ connect(this,SIGNAL(closeView(int)),this,SLOT(closeViewSlot(int)));
+ connect(this,SIGNAL(startTimerSignal()),this,SLOT(startTimerSlot()),Qt::QueuedConnection);
+}
+
+void OpenGLManager::timerEvent(QTimerEvent* event){
+ //cerr<<".";
+ boost::mutex::scoped_lock lock(viewsMutex);
+ // when sharing the 0th view widget, it should be enough to update the primary view only
+ //if(views.size()>0) views[0]->updateGL();
+ #if 1
+ FOREACH(const shared_ptr<GLViewer>& view, views){ if(view) view->updateGL(); }
+ #endif
+}
+
+void OpenGLManager::createViewSlot(){
+ boost::mutex::scoped_lock lock(viewsMutex);
+ if(views.size()==0){
+ views.push_back(shared_ptr<GLViewer>(new GLViewer(0,renderer,/*shareWidget*/(QGLWidget*)0)));
+ } else {
+ throw runtime_error("Secondary views not supported");
+ //views.push_back(shared_ptr<GLViewer>(new GLViewer(views.size(),renderer,views[0].get())));
+ }
+}
+
+void OpenGLManager::resizeViewSlot(int id, int wd, int ht){
+ views[id]->resize(wd,ht);
+}
+
+void OpenGLManager::closeViewSlot(int id){
+ boost::mutex::scoped_lock lock(viewsMutex);
+ for(size_t i=views.size()-1; (!views[i]); i--){ views.resize(i); } // delete empty views from the end
+ if(id<0){ // close the last one existing
+ assert(*views.rbegin()); // this should have been sanitized by the loop above
+ views.resize(views.size()-1); // releases the pointer as well
+ }
+ if(id==0){
+ LOG_DEBUG("Closing primary view.");
+ if(views.size()==1) views.clear();
+ else{ LOG_INFO("Cannot close primary view, secondary views still exist."); }
+ }
+}
+void OpenGLManager::centerAllViews(){
+ boost::mutex::scoped_lock lock(viewsMutex);
+ FOREACH(const shared_ptr<GLViewer>& g, views){ if(!g) continue; g->centerScene(); }
+}
+void OpenGLManager::startTimerSlot(){
+ startTimer(50);
+}
+
+int OpenGLManager::waitForNewView(float timeout,bool center){
+ size_t origViewCount=views.size();
+ emitCreateView();
+ float t=0;
+ while(views.size()!=origViewCount+1){
+ usleep(50000); t+=.05;
+ // wait at most 5 secs
+ if(t>=timeout) {
+ LOG_ERROR("Timeout waiting for the new view to open, giving up."); return -1;
+ }
+ }
+ if(center)(*views.rbegin())->centerScene();
+ return (*views.rbegin())->viewId;
+}
=== added file 'gui/qt5/OpenGLManager.hpp'
--- gui/qt5/OpenGLManager.hpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/OpenGLManager.hpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,47 @@
+#pragma once
+//#include
+
+#ifndef Q_MOC_RUN
+#include"GLViewer.hpp"
+#endif
+
+#include<QObject>
+#include<boost/thread/mutex.hpp>
+
+/*
+Singleton class managing OpenGL views,
+a renderer instance and timer to refresh the display.
+*/
+class OpenGLManager: public QObject{
+ Q_OBJECT
+ DECLARE_LOGGER;
+ public:
+ static OpenGLManager* self;
+ OpenGLManager(QObject *parent=0);
+ // manipulation must lock viewsMutex!
+ std::vector<shared_ptr<GLViewer> > views;
+ shared_ptr<OpenGLRenderer> renderer;
+ // signals are protected, emitting them is therefore wrapped with such funcs
+ void emitResizeView(int id, int wd, int ht){ emit resizeView(id,wd,ht); }
+ void emitCreateView(){ emit createView(); }
+ void emitStartTimer(){ emit startTimerSignal(); }
+ void emitCloseView(int id){ emit closeView(id); }
+ // create a new view and wait for it to become available; return the view number
+ // if timout (in seconds) elapses without the view to come up, reports error and returns -1
+ int waitForNewView(float timeout=5., bool center=true);
+ signals:
+ void createView();
+ void resizeView(int id, int wd, int ht);
+ void closeView(int id);
+ // this is used to start timer from the main thread via postEvent (ugly)
+ void startTimerSignal();
+ public slots:
+ virtual void createViewSlot();
+ virtual void resizeViewSlot(int id, int wd, int ht);
+ virtual void closeViewSlot(int id=-1);
+ virtual void timerEvent(QTimerEvent* event);
+ virtual void startTimerSlot();
+ void centerAllViews();
+ private:
+ boost::mutex viewsMutex;
+};
=== added file 'gui/qt5/SerializableEditor.py'
--- gui/qt5/SerializableEditor.py 1970-01-01 00:00:00 +0000
+++ gui/qt5/SerializableEditor.py 2015-06-26 20:19:22 +0000
@@ -0,0 +1,828 @@
+# encoding: utf-8
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5 import QtGui
+
+import re,itertools
+import logging
+logging.trace=logging.debug
+logging.basicConfig(level=logging.INFO)
+
+from yade import *
+import yade.qt
+
+from minieigen import *
+
+seqSerializableShowType=True # show type headings in serializable sequences (takes vertical space, but makes the type hyperlinked)
+
+# BUG: cursor is moved to the beginning of the input field even if it has focus
+# checking for focus seems to return True always and cursor is never moved
+# the 'True or' part effectively disables the condition (so that the cursor is moved always), but it might be fixed in the future somehow
+# if True or w.hasFocus(): w.home(False)
+# Janek: It looks like I've fixed this BUG, please do more testing.
+#
+
+def makeWrapperHref(text,className,attr=None,static=False):
+ """Create clickable HTML hyperlink to a Yade class or its attribute.
+
+ :param className: name of the class to link to.
+ :param attr: attribute to link to. If given, must exist directly in given *className*; if not given or empty, link to the class itself is created and *attr* is ignored.
+ :return: HTML with the hyperref.
+ """
+ if not static: return '<a href="%s#yade.wrapper.%s%s">%s</a>'%(yade.qt.sphinxDocWrapperPage,className,(('.'+attr) if attr else ''),text)
+ else: return '<a href="%s#ystaticattr-%s.%s">%s</a>'%(yade.qt.sphinxDocWrapperPage,className,attr,text)
+
+def serializableHref(ser,attr=None,text=None):
+ """Return HTML href to a *ser* optionally to the attribute *attr*.
+ The class hierarchy is crawled upwards to find out in which parent class is *attr* defined,
+ so that the href target is a valid link. In that case, only single inheritace is assumed and
+ the first class from the top defining *attr* is used.
+
+ :param ser: object of class deriving from :yref:`Serializable`, or string; if string, *attr* must be empty.
+ :param attr: name of the attribute to link to; if empty, linke to the class itself is created.
+ :param text: visible text of the hyperlink; if not given, either class name or attribute name without class name (when *attr* is not given) is used.
+
+ :returns: HTML with the hyperref.
+ """
+ # klass is a class name given as string
+ if isinstance(ser,str):
+ if attr: raise InvalidArgument("When *ser* is a string, *attr* must be empty (only class link can be created)")
+ return makeWrapperHref(text if text else ser,ser)
+ # klass is a type object
+ if attr:
+ klass=ser.__class__
+ while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0]
+ if not text: text=attr
+ else:
+ klass=ser.__class__
+ if not text: text=klass.__name__
+ return makeWrapperHref(text,klass.__name__,attr,static=(attr and getattr(klass,attr)==getattr(ser,attr)))
+
+class AttrEditor():
+ """Abstract base class handing some aspects common to all attribute editors.
+ Holds exacly one attribute which is updated whenever it changes."""
+ def __init__(self,getter=None,setter=None):
+ self.getter,self.setter=getter,setter
+ self.hot,self.focused=False,False
+ self.widget=None
+ def refresh(self): pass
+ def update(self): pass
+ def isHot(self,hot=True):
+ "Called when the widget gets focus; mark it hot, change colors etc."
+ if hot==self.hot: return
+ self.hot=hot
+ if hot: self.setStyleSheet('QWidget { background: red }')
+ else: self.setStyleSheet('QWidget { background: none }')
+ def sizeHint(self): return QSize(150,12)
+ def trySetter(self,val):
+ try: self.setter(val)
+ except AttributeError: self.setEnabled(False)
+ self.isHot(False)
+
+class AttrEditor_Bool(AttrEditor,QFrame):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.checkBox=QCheckBox(self)
+ lay=QVBoxLayout(self); lay.setSpacing(0); lay.setMargin(0); lay.addStretch(1); lay.addWidget(self.checkBox); lay.addStretch(1)
+ self.checkBox.clicked.connect(self.update)
+ def refresh(self): self.checkBox.setChecked(self.getter())
+ def update(self): self.trySetter(self.checkBox.isChecked())
+
+class AttrEditor_Int(AttrEditor,QSpinBox):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QSpinBox.__init__(self,parent)
+ self.setRange(int(-1e9),int(1e9)); self.setSingleStep(1);
+ self.valueChanged.connect(self.update)
+ def refresh(self):
+ if (not self.hasFocus()): self.setValue(self.getter())
+ def update(self): self.trySetter(self.value())
+
+class AttrEditor_Str(AttrEditor,QLineEdit):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QLineEdit.__init__(self,parent)
+ self.textEdited.connect(self.isHot)
+ self.selectionChanged.connect(self.isHot)
+ self.editingFinished.connect(self.update)
+ def refresh(self):
+ if (not self.hasFocus()): self.setText(self.getter())
+ def update(self): self.trySetter(str(self.text()))
+
+class AttrEditor_Float(AttrEditor,QLineEdit):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QLineEdit.__init__(self,parent)
+ self.textEdited.connect(self.isHot)
+ self.selectionChanged.connect(self.isHot)
+ self.editingFinished.connect(self.update)
+ def refresh(self):
+ #if True or not self.hasFocus(): self.home(False)
+ if (not self.hasFocus()):
+ self.setText(str(self.getter()));
+ self.home(False)
+ def update(self):
+ try: self.trySetter(float(self.text()))
+ except ValueError: self.refresh()
+
+class AttrEditor_Complex(AttrEditor,QLineEdit):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.rows,self.cols=1,2
+ self.setContentsMargins(0,0,0,0)
+ self.first=True
+ val=self.getter()
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=QLineEdit('')
+ self.grid.addWidget(w,row,col);
+ w.textEdited.connect(self.isHot)
+ w.selectionChanged.connect(self.isHot)
+ w.editingFinished.connect(self.update)
+ def refresh(self,force=False):
+ val=self.getter()
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=self.grid.itemAtPosition(row,col).widget()
+ if(self.first or force):
+ w.setText(str(val.real if col==0 else val.imag))
+ #if True or not w.hasFocus: w.home(False) # make the left-most part visible, if the text is wider than the widget
+ if (not w.hasFocus):
+ w.setText(str(val.real if col==0 else val.imag))
+ w.home(False) # make the left-most part visible, if the text is wider than the widget
+ self.first=False
+ def update(self):
+ try:
+ val=self.getter()
+ w1=self.grid.itemAtPosition(0,0).widget()
+ w2=self.grid.itemAtPosition(0,1).widget()
+ if w1.isModified() or w2.isModified():
+ val=complex(float(w1.text()),float(w2.text()))
+ logging.debug('setting'+str(val))
+ self.trySetter(val)
+ except ValueError:
+ self.refresh(force=True)
+ def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_Quaternion(AttrEditor,QFrame):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.grid=QHBoxLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ for i in range(4):
+ if i==3:
+ # add vertical divider (axis | angle)
+ f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4)
+ self.grid.addWidget(f)
+ w=QLineEdit('')
+ self.grid.addWidget(w);
+ w.textEdited.connect(self.isHot)
+ w.selectionChanged.connect(self.isHot)
+ w.editingFinished.connect(self.update)
+ def refresh(self):
+ val=self.getter(); axis,angle=val.toAxisAngle()
+ for i in (0,1,2,4):
+ #if True or not w.hasFocus(): w.home(False)
+ w=self.grid.itemAt(i).widget();
+ if (not w.hasFocus()):
+ w.setText(str(axis[i] if i<3 else angle));
+ w.home(False)
+ def update(self):
+ try:
+ x=[float((self.grid.itemAt(i).widget().text())) for i in (0,1,2,4)]
+ except ValueError:
+ self.refresh()
+ return
+ q=Quaternion(Vector3(x[0],x[1],x[2]),x[3]); q.normalize() # from axis-angle
+ self.trySetter(q)
+ def setFocus(self): self.grid.itemAt(0).widget().setFocus()
+
+class AttrEditor_Se3(AttrEditor,QFrame):
+ def __init__(self,parent,getter,setter):
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ for row,col in itertools.product(range(2),range(5)): # one additional column for vertical line in quaternion
+ if (row,col)==(0,3): continue
+ if (row,col)==(0,4): self.grid.addWidget(QLabel(u'←<i>pos</i> ↙<i>ori</i>',self),row,col); continue
+ if (row,col)==(1,3):
+ f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4); self.grid.addWidget(f,row,col); continue
+ w=QLineEdit('')
+ self.grid.addWidget(w,row,col);
+ w.textEdited.connect(self.isHot)
+ w.selectionChanged.connect(self.isHot)
+ w.editingFinished.connect(self.update)
+ def refresh(self):
+ pos,ori=self.getter(); axis,angle=ori.toAxisAngle()
+ for i in (0,1,2,4):
+ #if True or not w.hasFocus(): w.home(False)
+ w=self.grid.itemAtPosition(1,i).widget();
+ if (not w.hasFocus()):
+ w.setText(str(axis[i] if i<3 else angle));
+ w.home(False)
+ for i in (0,1,2):
+ #if True or not w.hasFocus(): w.home(False)
+ w=self.grid.itemAtPosition(0,i).widget();
+ if (not w.hasFocus()):
+ w.setText(str(pos[i]));
+ w.home(False)
+ def update(self):
+ try:
+ q=[float((self.grid.itemAtPosition(1,i).widget().text())) for i in (0,1,2,4)]
+ v=[float((self.grid.itemAtPosition(0,i).widget().text())) for i in (0,1,2)]
+ except ValueError:
+ self.refresh()
+ return
+ qq=Quaternion(Vector3(q[0],q[1],q[2]),q[3]); qq.normalize() # from axis-angle
+ self.trySetter((v,qq))
+ def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_MatrixX(AttrEditor,QFrame):
+ def __init__(self,parent,getter,setter,rows,cols,idxConverter):
+ 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.rows,self.cols=rows,cols
+ self.idxConverter=idxConverter
+ self.setContentsMargins(0,0,0,0)
+ self.first=True
+ val=self.getter()
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=QLineEdit('')
+ self.grid.addWidget(w,row,col);
+ w.textEdited.connect(self.isHot)
+ w.selectionChanged.connect(self.isHot)
+ w.editingFinished.connect(self.update)
+ def refresh(self,force=False):
+ val=self.getter()
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=self.grid.itemAtPosition(row,col).widget()
+ if(self.first or force):
+ w.setText(str(val[self.idxConverter(row,col)]))
+ if (not w.hasFocus):
+ w.setText(str(val[self.idxConverter(row,col)]))
+ w.home(False) # make the left-most part visible, if the text is wider than the widget
+ self.first=False
+ def update(self):
+ try:
+ val=self.getter()
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=self.grid.itemAtPosition(row,col).widget()
+ if w.isModified(): val[self.idxConverter(row,col)]=float(w.text())
+ logging.debug('setting'+str(val))
+ self.trySetter(val)
+ except ValueError:
+ self.refresh(force=True)
+ def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_MatrixXi(AttrEditor,QFrame):
+ def __init__(self,parent,getter,setter,rows,cols,idxConverter):
+ 'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
+ AttrEditor.__init__(self,getter,setter)
+ QFrame.__init__(self,parent)
+ self.rows,self.cols=rows,cols
+ self.idxConverter=idxConverter
+ self.setContentsMargins(0,0,0,0)
+ self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=QSpinBox()
+ w.setRange(int(-1e9),int(1e9)); w.setSingleStep(1);
+ self.grid.addWidget(w,row,col);
+ self.refresh() # refresh before connecting signals!
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ self.grid.itemAtPosition(row,col).widget().valueChanged.connect(self.update)
+ def refresh(self):
+ val=self.getter()
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=self.grid.itemAtPosition(row,col).widget().setValue(val[self.idxConverter(row,col)])
+ def update(self):
+ val=self.getter(); modified=False
+ for row,col in itertools.product(range(self.rows),range(self.cols)):
+ w=self.grid.itemAtPosition(row,col).widget()
+ if w.value()!=val[self.idxConverter(row,col)]:
+ modified=True; val[self.idxConverter(row,col)]=w.value()
+ if not modified: return
+ logging.debug('setting'+str(val))
+ self.trySetter(val)
+ def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_Vector6i(AttrEditor_MatrixXi):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,6,lambda r,c:c)
+class AttrEditor_Vector3i(AttrEditor_MatrixXi):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,3,lambda r,c:c)
+class AttrEditor_Vector2i(AttrEditor_MatrixXi):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,2,lambda r,c:c)
+
+class AttrEditor_Vector6(AttrEditor_MatrixX):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,6,lambda r,c:c)
+class AttrEditor_Vector3(AttrEditor_MatrixX):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,3,lambda r,c:c)
+class AttrEditor_Vector2(AttrEditor_MatrixX):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,2,lambda r,c:c)
+class AttrEditor_Matrix3(AttrEditor_MatrixX):
+ def __init__(self,parent,getter,setter):
+ AttrEditor_MatrixX.__init__(self,parent,getter,setter,3,3,lambda r,c:(r,c))
+
+class Se3FakeType: pass
+
+_fundamentalEditorMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,complex:AttrEditor_Complex,Quaternion:AttrEditor_Quaternion,Vector2:AttrEditor_Vector2,Vector3:AttrEditor_Vector3,Vector6:AttrEditor_Vector6,Matrix3:AttrEditor_Matrix3,Vector6i:AttrEditor_Vector6i,Vector3i:AttrEditor_Vector3i,Vector2i:AttrEditor_Vector2i,Se3FakeType:AttrEditor_Se3}
+_fundamentalInitValues={bool:True,str:'',int:0,float:0.0,complex:0.0j,Quaternion:Quaternion((0,1,0),0.0),Vector3:Vector3.Zero,Matrix3:Matrix3.Zero,Vector6:Vector6.Zero,Vector6i:Vector6i.Zero,Vector3i:Vector3i.Zero,Vector2i:Vector2i.Zero,Vector2:Vector2.Zero,Se3FakeType:(Vector3.Zero,Quaternion((0,1,0),0.0))}
+
+class SerQLabel(QLabel):
+ def __init__(self,parent,label,tooltip,path):
+ QLabel.__init__(self,parent)
+ self.path=path
+ self.setText(label)
+ if tooltip or path: self.setToolTip(('<b>'+path+'</b><br>' if self.path else '')+(tooltip if tooltip else ''))
+ self.linkActivated.connect(yade.qt.openUrl)
+ def mousePressEvent(self,event):
+ if event.button()!=Qt.MidButton:
+ event.ignore(); return
+ # middle button clicked, paste pasteText to clipboard
+ cb=QApplication.clipboard()
+ cb.setText(self.path,mode=QClipboard.Clipboard)
+ cb.setText(self.path,mode=QClipboard.Selection) # X11 global selection buffer
+ event.accept()
+
+class SerializableEditor(QFrame):
+ "Class displaying and modifying serializable attributes of a yade object."
+ import collections
+ import logging
+ # each attribute has one entry associated with itself
+ class EntryData:
+ def __init__(self,name,T,flags=0):
+ self.name,self.T,self.flags=name,T,flags
+ self.lineNo,self.widget=None,None
+ def __init__(self,ser,parent=None,ignoredAttrs=set(),showType=False,path=None):
+ "Construct window, *ser* is the object we want to show."
+ QtGui.QFrame.__init__(self,parent)
+ self.ser=ser
+ self.path=(ser.label if (hasattr(ser,'label') and ser.label) else path)
+ self.showType=showType
+ self.hot=False
+ self.entries=[]
+ self.ignoredAttrs=ignoredAttrs
+ logging.debug('New Serializable of type %s'%ser.__class__.__name__)
+ self.setWindowTitle(str(ser))
+ self.mkWidgets()
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(500)
+ def getListTypeFromDocstring(self,attr):
+ "Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works."
+ doc=getattr(self.ser.__class__,attr).__doc__
+ if doc==None:
+ logging.error("Attribute %s has no docstring."%attr)
+ return None
+ m=re.search(r':yattrtype:`([^`]*)`',doc)
+ if not m:
+ logging.error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc))
+ return None
+ cxxT=m.group(1)
+ logging.debug('Got type "%s" from :yattrtype:'%cxxT)
+ def vecTest(T,cxxT):
+ #regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$'
+ regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*('+T+r')(\s*>)?\s*>\s*$'
+ m=re.match(regexp,cxxT)
+ return m
+ vecMap={
+ 'bool':bool,'int':int,'long':int,'Body::id_t':long,'size_t':long,
+ 'Real':float,'float':float,'double':float,'complex':complex,'std::complex<Real>':complex,
+ 'Vector6r':Vector6,'Vector6i':Vector6i,'Vector3i':Vector3i,'Vector2r':Vector2,'Vector2i':Vector2i,
+ 'Vector3r':Vector3,'Matrix3r':Matrix3,'Se3r':Se3FakeType,
+ 'string':str,
+ #'BodyCallback':BodyCallback,
+ 'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'IGeomFunctor':IGeomFunctor,'IPhysFunctor':IPhysFunctor,'LawFunctor':LawFunctor,'KinematicEngine':KinematicEngine,
+ 'GlShapeFunctor':GlShapeFunctor,'GlStateFunctor':GlStateFunctor,'GlIGeomFunctor':GlIGeomFunctor,'GlIPhysFunctor':GlIPhysFunctor,'GlBoundFunctor':GlBoundFunctor,'GlExtraDrawer':GlExtraDrawer
+ }
+ for T,ret in vecMap.items():
+ if vecTest(T,cxxT):
+ logging.debug("Got type %s from cxx type %s"%(repr(ret),cxxT))
+ return (ret,)
+ logging.error("Unable to guess python type from cxx type '%s'"%cxxT)
+ return None
+ def mkAttrEntries(self):
+ if self.ser==None: return
+ try:
+ d=self.ser.dict()
+ except TypeError:
+ logging.error('TypeError when getting attributes of '+str(self.ser)+',skipping. ')
+ import traceback
+ traceback.print_exc()
+ attrs=self.ser.dict().keys(); attrs.sort()
+ for attr in attrs:
+ val=getattr(self.ser,attr) # get the value using serattr, as it might be different from what the dictionary provides (e.g. Body.blockedDOFs)
+ t=None
+ doc=getattr(self.ser.__class__,attr).__doc__;
+ # some attributes are not shown
+ if '|yhidden|' in doc: continue
+ if attr in self.ignoredAttrs: continue
+# FIXME: (Janek) Implementing Quantum Mechanics makes some DEM assumptions
+# invalid. I think that we should rethink what base class Body contains, so
+# that in QM we would not need to use this hack to hide some variables.
+# However it is great to note that only this little 'cosmetic' hack is needed
+# to make Quantum Mechanics possible in yade
+# See also: class QuantumMechanicalState, class QuantumMechanicalBody, gui/qt4/SerializableEditor.py
+ if hasattr(self.ser,"qtHide") and (attr in getattr(self.ser,"qtHide").split()): continue
+ if isinstance(val,list):
+ t=self.getListTypeFromDocstring(attr)
+ if not t and len(val)==0: t=(val[0].__class__,) # 1-tuple is list of the contained type
+ #if not t: raise RuntimeError('Unable to guess type of '+str(self.ser)+'.'+attr)
+ # hack for Se3, which is returned as (Vector3,Quaternion) in python
+ elif isinstance(val,tuple) and len(val)==2 and val[0].__class__==Vector3 and val[1].__class__==Quaternion: t=Se3FakeType
+ else: t=val.__class__
+ match=re.search(':yattrflags:`\s*([0-9]+)\s*`',doc) # non-empty attribute
+ flags=int(match.group(1)) if match else 0
+ #logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__)))
+ self.entries.append(self.EntryData(name=attr,T=t))
+ #print("name: "+str(attr)+"\tflag: "+str(flags))
+ def getDocstring(self,attr=None):
+ "If attr is *None*, return docstring of the Serializable itself"
+ doc=(getattr(self.ser.__class__,attr).__doc__ if attr else self.ser.__class__.__doc__)
+ if not doc: return ''
+ doc=re.sub(':y(attrtype|default|attrflags):`[^`]*`','',doc)
+ statAttr=re.compile('^.. ystaticattr::.*$',re.MULTILINE|re.DOTALL)
+ doc=re.sub(statAttr,'',doc) # static classes have their proper docs at the beginning, discard static memeber docs
+ # static: attribute of the type is the same object as attribute of the instance
+ # in that case, get docstring from the class documentation by parsing it
+ if attr and getattr(self.ser.__class__,attr)==getattr(self.ser,attr): doc=self.getStaticAttrDocstring(attr)
+ doc=re.sub(':yref:`([^`]*)`','\\1',doc)
+ import textwrap
+ wrapper=textwrap.TextWrapper(replace_whitespace=False)
+ return wrapper.fill(textwrap.dedent(doc))
+ def getStaticAttrDocstring(self,attr):
+ ret=''; c=self.ser.__class__
+ while hasattr(c,attr) and hasattr(c.__base__,attr): c=c.__base__
+ start='.. ystaticattr:: %s.%s('%(c.__name__,attr)
+ if start in c.__doc__:
+ ll=c.__doc__.split('\n')
+ for i in range(len(ll)):
+ if ll[i].startswith(start): break
+ for i in range(i+1,len(ll)):
+ if len(ll[i])>0 and ll[i][0] not in ' \t': break
+ ret+=ll[i]
+ return ret
+ else: return '[no documentation found]'
+ def mkWidget(self,entry):
+ if not entry.T: return None
+ # single fundamental object
+ Klass=_fundamentalEditorMap.get(entry.T,None)
+ getter,setter=lambda: getattr(self.ser,entry.name), lambda x: setattr(self.ser,entry.name,x)
+ if Klass:
+ widget=Klass(self,getter=getter,setter=setter)
+ widget.setFocusPolicy(Qt.StrongFocus)
+ if (entry.flags & AttrFlags.readonly): widget.setEnabled(False)
+ return widget
+ # sequences
+ if entry.T.__class__==tuple:
+ assert(len(entry.T)==1) # we don't handle tuples of other lenghts
+ # sequence of serializables
+ T=entry.T[0]
+ if (issubclass(T,Serializable) or T==Serializable):
+ widget=SeqSerializable(self,getter,setter,T,path=(self.path+'.'+entry.name if self.path else None),shrink=True)
+ return widget
+ if (T in _fundamentalEditorMap):
+ widget=SeqFundamentalEditor(self,getter,setter,T)
+ return widget
+ return None
+ # a serializable
+ if issubclass(entry.T,Serializable) or entry.T==Serializable:
+ obj=getattr(self.ser,entry.name)
+ if hasattr(obj,'label') and obj.label: path=obj.label
+ elif self.path: path=self.path+'.'+entry.name
+ else: path=None
+ widget=SerializableEditor(getattr(self.ser,entry.name),parent=self,showType=self.showType,path=(self.path+'.'+entry.name if self.path else None))
+ widget.setFrameShape(QFrame.Box); widget.setFrameShadow(QFrame.Raised); widget.setLineWidth(1)
+ return widget
+ return None
+ def mkWidgets(self):
+ self.mkAttrEntries()
+ grid=QFormLayout()
+ grid.setContentsMargins(2,2,2,2)
+ grid.setVerticalSpacing(0)
+ grid.setLabelAlignment(Qt.AlignRight)
+ if self.showType:
+ lab=SerQLabel(self,makeSerializableLabel(self.ser,addr=True,href=True),tooltip=self.getDocstring(),path=self.path)
+ lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter); lab.linkActivated.connect(yade.qt.openUrl)
+ grid.setWidget(0,QFormLayout.SpanningRole,lab)
+ for entry in self.entries:
+ entry.widget=self.mkWidget(entry)
+ objPath=(self.path+'.'+entry.name) if self.path else None
+ label=SerQLabel(self,serializableHref(self.ser,entry.name),tooltip=self.getDocstring(entry.name),path=objPath)
+ grid.addRow(label,entry.widget if entry.widget else QLabel('<i>unhandled type</i>'))
+ self.setLayout(grid)
+ self.refreshEvent()
+ def refreshEvent(self):
+ for e in self.entries:
+ if e.widget and not e.widget.hot: e.widget.refresh()
+ def refresh(self): pass
+
+def makeSerializableLabel(ser,href=False,addr=True,boldHref=True,num=-1,count=-1):
+ ret=u''
+ if num>=0:
+ if count>=0: ret+=u'%d/%d. '%(num,count)
+ else: ret+=u'%d. '%num
+ if href: ret+=(u' <b>' if boldHref else u' ')+serializableHref(ser)+(u'</b> ' if boldHref else u' ')
+ else: ret+=ser.__class__.__name__+' '
+ if hasattr(ser,'label') and ser.label: ret+=u' “'+unicode(ser.label)+u'”'
+ # do not show address if there is a label already
+ elif addr:
+ import re
+ ss=unicode(ser); m=re.match(u'<(.*) instance at (0x.*)>',ss)
+ if m: ret+=m.group(2)
+ else: logging.warning(u"Serializable converted to str ('%s') does not contain 'instance at 0x…'"%ss)
+ return ret
+
+class SeqSerializableComboBox(QFrame):
+ def __init__(self,parent,getter,setter,serType,path=None,shrink=False):
+ QFrame.__init__(self,parent)
+ self.getter,self.setter,self.serType,self.path,self.shrink=getter,setter,serType,path,shrink
+ self.layout=QVBoxLayout(self)
+ topLineFrame=QFrame(self)
+ topLineLayout=QHBoxLayout(topLineFrame);
+ for l in self.layout, topLineLayout: l.setSpacing(0); l.setContentsMargins(0,0,0,0)
+ topLineFrame.setLayout(topLineLayout)
+ buttons=(self.newButton,self.killButton,self.upButton,self.downButton)=[QPushButton(label,self) for label in (u'☘',u'☠',u'↑',u'↓')]
+ buttonSlots=(self.newSlot,self.killSlot,self.upSlot,self.downSlot) # same order as buttons
+ for b in buttons: b.setStyleSheet('QPushButton { font-size: 15pt; }'); b.setFixedWidth(30); b.setFixedHeight(30)
+ self.combo=QComboBox(self)
+ self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
+ for w in buttons[0:2]+[self.combo,]+buttons[2:4]: topLineLayout.addWidget(w)
+ self.layout.addWidget(topLineFrame) # nested layout
+ self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True)
+ self.layout.addWidget(self.scroll)
+ self.seqEdit=None # currently edited serializable
+ self.setLayout(self.layout)
+ self.hot=None # API compat with SerializableEditor
+ self.setFrameShape(QFrame.Box); self.setFrameShadow(QFrame.Raised); self.setLineWidth(1)
+ # signals
+ for b,slot in zip(buttons,buttonSlots): b.clicked.connect(slot)
+ self.combo.currentIndexChanged.connect(self.comboIndexSlot)
+ self.refreshEvent()
+ # periodic refresh
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(1000) # 1s should be enough
+ #print 'SeqSerializable path is',self.path
+ def comboIndexSlot(self,ix): # different seq item selected
+ currSeq=self.getter();
+ if len(currSeq)==0: ix=-1
+ logging.debug('%s comboIndexSlot len=%d, ix=%d'%(self.serType.__name__,len(currSeq),ix))
+ self.downButton.setEnabled(ix<len(currSeq)-1)
+ self.upButton.setEnabled(ix>0)
+ self.combo.setEnabled(ix>=0)
+ if ix>=0:
+ ser=currSeq[ix]
+ self.seqEdit=SerializableEditor(ser,parent=self,showType=seqSerializableShowType,path=(self.path+'['+str(ix)+']') if self.path else None)
+ self.scroll.setWidget(self.seqEdit)
+ if self.shrink:
+ self.sizeHint=lambda: QSize(100,1000)
+ self.scroll.sizeHint=lambda: QSize(100,1000)
+ self.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding)
+ self.scroll.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding)
+ self.setMinimumHeight(min(300,self.seqEdit.height()+self.combo.height()+10))
+ self.setMaximumHeight(100000)
+ self.scroll.setMaximumHeight(100000)
+ else:
+ self.scroll.setWidget(QFrame())
+ if self.shrink:
+ self.setMaximumHeight(self.combo.height()+10);
+ self.scroll.setMaximumHeight(0)
+ def serLabel(self,ser,i=-1):
+ return ('' if i<0 else str(i)+'. ')+str(ser)[1:-1].replace('instance at ','')
+ def refreshEvent(self,forceIx=-1):
+ currSeq=self.getter()
+ comboEnabled=self.combo.isEnabled()
+ if comboEnabled and len(currSeq)==0: self.comboIndexSlot(-1) # force refresh, otherwise would not happen from the initially empty state
+ ix,cnt=self.combo.currentIndex(),self.combo.count()
+ # serializable currently being edited (which can be absent) or the one of which index is forced
+ ser=(self.seqEdit.ser if self.seqEdit else None) if forceIx<0 else currSeq[forceIx]
+ if comboEnabled and len(currSeq)==cnt and (ix<0 or ser==currSeq[ix]): return
+ if not comboEnabled and len(currSeq)==0: return
+ logging.debug(self.serType.__name__+' rebuilding list from scratch')
+ self.combo.clear()
+ if len(currSeq)>0:
+ prevIx=-1
+ for i,s in enumerate(currSeq):
+ self.combo.addItem(makeSerializableLabel(s,num=i,count=len(currSeq),addr=False))
+ if s==ser: prevIx=i
+ if forceIx>=0: newIx=forceIx # force the index (used from newSlot to make the new element active)
+ elif prevIx>=0: newIx=prevIx # if found what was active before, use it
+ elif ix>=0: newIx=ix # otherwise use the previous index (e.g. after deletion)
+ else: newIx=0 # fallback to 0
+ logging.debug('%s setting index %d'%(self.serType.__name__,newIx))
+ self.combo.setCurrentIndex(newIx)
+ else:
+ logging.debug('%s EMPTY, setting index 0'%(self.serType.__name__))
+ self.combo.setCurrentIndex(-1)
+ self.killButton.setEnabled(len(currSeq)>0)
+ def newSlot(self):
+ dialog=NewSerializableDialog(self,self.serType.__name__)
+ if not dialog.exec_(): return # cancelled
+ ser=dialog.result()
+ ix=self.combo.currentIndex()
+ currSeq=self.getter(); currSeq.insert(ix,ser); self.setter(currSeq)
+ logging.debug('%s new item created at index %d'%(self.serType.__name__,ix))
+ self.refreshEvent(forceIx=ix)
+ def killSlot(self):
+ ix=self.combo.currentIndex()
+ currSeq=self.getter(); del currSeq[ix]; self.setter(currSeq)
+ self.refreshEvent()
+ def upSlot(self):
+ i=self.combo.currentIndex()
+ assert(i>0)
+ currSeq=self.getter();
+ prev,curr=currSeq[i-1:i+1]; currSeq[i-1],currSeq[i]=curr,prev; self.setter(currSeq)
+ self.refreshEvent(forceIx=i-1)
+ def downSlot(self):
+ i=self.combo.currentIndex()
+ currSeq=self.getter(); assert(i<len(currSeq)-1);
+ curr,nxt=currSeq[i:i+2]; currSeq[i],currSeq[i+1]=nxt,curr; self.setter(currSeq)
+ self.refreshEvent(forceIx=i+1)
+ def refresh(self): pass # API compat with SerializableEditor
+
+SeqSerializable=SeqSerializableComboBox
+
+
+class NewFundamentalDialog(QDialog):
+ def __init__(self,parent,attrName,typeObj,typeStr):
+ QDialog.__init__(self,parent)
+ self.setWindowTitle('%s (type %s)'%(attrName,typeStr))
+ self.layout=QVBoxLayout(self)
+ self.scroll=QScrollArea(self)
+ self.scroll.setWidgetResizable(True)
+ self.buttons=QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel);
+ self.buttons.accepted.connect(self.accept)
+ self.buttons.rejected.connect(self.reject)
+ self.layout.addWidget(self.scroll)
+ self.layout.addWidget(self.buttons)
+ self.setWindowModality(Qt.WindowModal)
+ class FakeObjClass: pass
+ self.fakeObj=FakeObjClass()
+ self.attrName=attrName
+ Klass=_fundamentalEditorMap.get(typeObj,None)
+ initValue=_fundamentalInitValues.get(typeObj,typeObj())
+ setattr(self.fakeObj,attrName,initValue)
+ if Klass:
+ self.widget=Klass(None,self.fakeObj,attrName)
+ self.scroll.setWidget(self.widget)
+ self.scroll.show()
+ self.widget.refresh()
+ else: raise RuntimeError("Unable to construct new dialog for type %s"%(typeStr))
+ def result(self):
+ self.widget.update()
+ return getattr(self.fakeObj,self.attrName)
+
+class NewSerializableDialog(QDialog):
+ def __init__(self,parent,baseClassName,includeBase=True):
+ import yade.system
+ QDialog.__init__(self,parent)
+ self.setWindowTitle('Create new object of type %s'%baseClassName)
+ self.layout=QVBoxLayout(self)
+ self.combo=QComboBox(self)
+ childs=list(yade.system.childClasses(baseClassName,includeBase=False)); childs.sort()
+ if includeBase:
+ self.combo.addItem(baseClassName)
+ self.combo.insertSeparator(1000)
+ self.combo.addItems(childs)
+ self.combo.currentIndexChanged.connect(self.comboSlot)
+ self.scroll=QScrollArea(self)
+ self.scroll.setWidgetResizable(True)
+ self.buttons=QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel);
+ self.buttons.accepted.connect(self.accept)
+ self.buttons.rejected.connect(self.reject)
+ self.layout.addWidget(self.combo)
+ self.layout.addWidget(self.scroll)
+ self.layout.addWidget(self.buttons)
+ self.ser=None
+ self.combo.setCurrentIndex(0); self.comboSlot(0)
+ self.setWindowModality(Qt.WindowModal)
+ def comboSlot(self,index):
+ item=str(self.combo.itemText(index))
+ self.ser=eval(item+'()')
+ self.scroll.setWidget(SerializableEditor(self.ser,self.scroll,showType=True))
+ self.scroll.show()
+ def result(self): return self.ser
+ def sizeHint(self): return QSize(180,400)
+
+class SeqFundamentalEditor(QFrame):
+ def __init__(self,parent,getter,setter,itemType):
+ QFrame.__init__(self,parent)
+ self.getter,self.setter,self.itemType=getter,setter,itemType
+ self.layout=QVBoxLayout()
+ topLineFrame=QFrame(self); topLineLayout=QHBoxLayout(topLineFrame)
+ self.form=QFormLayout()
+ self.form.setContentsMargins(0,0,0,0)
+ self.form.setVerticalSpacing(0)
+ self.form.setLabelAlignment(Qt.AlignLeft)
+ self.formFrame=QFrame(self); self.formFrame.setLayout(self.form)
+ self.layout.addWidget(self.formFrame)
+ self.setLayout(self.layout)
+ # SerializableEditor API compat
+ self.hot=False
+ self.rebuild()
+ # periodic refresh
+ self.refreshTimer=QTimer(self)
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(1000) # 1s should be enough
+ def contextMenuEvent(self, event):
+ index=self.localPositionToIndex(event.pos())
+ seq=self.getter()
+ if len(seq)==0: index=-1
+ field=self.form.itemAt(index,QFormLayout.LabelRole).widget() if index>=0 else None
+ menu=QMenu(self)
+ actNew,actKill,actUp,actDown=[menu.addAction(name) for name in (u'☘ New',u'☠ Remove',u'↑ Up',u'↓ Down')]
+ if index<0: [a.setEnabled(False) for a in actKill,actUp,actDown]
+ if index==len(seq)-1: actDown.setEnabled(False)
+ if index==0: actUp.setEnabled(False)
+ if field: field.setStyleSheet('QWidget { background: green }')
+ act=menu.exec_(self.mapToGlobal(event.pos()))
+ if field: field.setStyleSheet('QWidget { background: none }')
+ if not act: return
+ if act==actNew: self.newSlot(index)
+ elif act==actKill: self.killSlot(index)
+ elif act==actUp: self.upSlot(index)
+ elif act==actDown: self.downSlot(index)
+ def localPositionToIndex(self,pos):
+ gp=self.mapToGlobal(pos)
+ for row in range(self.form.count()/2):
+ w,i=self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole)
+ for wi in w.widget(),i.widget():
+ x0,y0,x1,y1=wi.geometry().getCoords(); globG=QRect(self.mapToGlobal(QPoint(x0,y0)),self.mapToGlobal(QPoint(x1,y1)))
+ if globG.contains(gp):
+ return row
+ return -1
+ def newSlot(self,i):
+ seq=self.getter();
+ seq.insert(i,_fundamentalInitValues.get(self.itemType,self.itemType()))
+ self.setter(seq)
+ self.rebuild()
+ def killSlot(self,i):
+ seq=self.getter(); assert(i<len(seq)); del seq[i]; self.setter(seq)
+ self.refreshEvent()
+ def upSlot(self,i):
+ seq=self.getter(); assert(i<len(seq));
+ prev,curr=seq[i-1:i+1]; seq[i-1],seq[i]=curr,prev; self.setter(seq)
+ self.refreshEvent(forceIx=i-1)
+ def downSlot(self,i):
+ seq=self.getter(); assert(i<len(seq)-1);
+ curr,nxt=seq[i:i+2]; seq[i],seq[i+1]=nxt,curr; self.setter(seq)
+ self.refreshEvent(forceIx=i+1)
+ def rebuild(self):
+ currSeq=self.getter()
+ # clear everything
+ rows=self.form.count()/2
+ for row in range(rows):
+ logging.trace('counts',self.form.rowCount(),self.form.count())
+ for wi in self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole):
+ self.form.removeItem(wi)
+ logging.trace('deleting widget',wi.widget())
+ widget=wi.widget(); widget.hide(); del widget # for some reason, deleting does not make the thing disappear visually; hiding does, however
+ logging.trace('counts after ',self.form.rowCount(),self.form.count())
+ logging.debug('cleared')
+ # add everything
+ Klass=_fundamentalEditorMap.get(self.itemType,None)
+ if not Klass:
+ errMsg=QTextEdit(self)
+ errMsg.setReadOnly(True); errMsg.setText("Sorry, editing sequences of %s's is not (yet?) implemented."%(self.itemType.__name__))
+ self.form.insertRow(0,'<b>Error</b>',errMsg)
+ return
+ class ItemGetter():
+ def __init__(self,getter,index): self.getter,self.index=getter,index
+ def __call__(self): return self.getter()[self.index]
+ class ItemSetter():
+ def __init__(self,getter,setter,index): self.getter,self.setter,self.index=getter,setter,index
+ def __call__(self,val): seq=self.getter(); seq[self.index]=val; self.setter(seq)
+ for i,item in enumerate(currSeq):
+ widget=Klass(self,ItemGetter(self.getter,i),ItemSetter(self.getter,self.setter,i)) #proxy,'value')
+ self.form.insertRow(i,'%d. '%i,widget)
+ logging.debug('added item %d %s'%(i,str(widget)))
+ if len(currSeq)==0: self.form.insertRow(0,'<i>empty</i>',QLabel('<i>(right-click for menu)</i>'))
+ logging.debug('rebuilt, will refresh now')
+ self.refreshEvent(dontRebuild=True) # avoid infinite recursion it the length would change meanwhile
+ def refreshEvent(self,dontRebuild=False,forceIx=-1):
+ currSeq=self.getter()
+ if len(currSeq)!=self.form.count()/2: #rowCount():
+ if dontRebuild: return # length changed behind our back, just pretend nothing happened and update next time instead
+ self.rebuild()
+ currSeq=self.getter()
+ for i in range(len(currSeq)):
+ item=self.form.itemAt(i,QFormLayout.FieldRole)
+ logging.trace('got item #%d %s'%(i,str(item.widget())))
+ widget=item.widget()
+ if not widget.hot:
+ widget.refresh()
+ if forceIx>=0 and forceIx==i: widget.setFocus()
+ def refresh(self): pass # SerializableEditor API
+
+
+
+
=== added file 'gui/qt5/XYZ.png'
Binary files gui/qt5/XYZ.png 1970-01-01 00:00:00 +0000 and gui/qt5/XYZ.png 2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/XYZ.xpm'
--- gui/qt5/XYZ.xpm 1970-01-01 00:00:00 +0000
+++ gui/qt5/XYZ.xpm 2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * XYZ_xpm[] = {
+"40 40 2 1",
+" c None",
+". c #000000",
+" .. .. ",
+" . . ",
+" .... ",
+" .... ",
+" .. ",
+" .. ",
+" .. ",
+" .. ",
+" ",
+" . ",
+" . ",
+" ... ",
+" ... ",
+" ... ",
+" ..... ",
+" ..... ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" ........... ",
+" . . ",
+" . ...... . .. .. ",
+" . .. . .. . . ",
+" . .. . ..... .... ",
+" . .. .................... .. ",
+" . . . ..... .. ",
+" . .. . ... .... ",
+" . .. . . . ",
+" . ...... . .. .. ",
+" . . ",
+" ........... ",
+" ",
+" "};
=== added file 'gui/qt5/YZX.png'
Binary files gui/qt5/YZX.png 1970-01-01 00:00:00 +0000 and gui/qt5/YZX.png 2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/YZX.xpm'
--- gui/qt5/YZX.xpm 1970-01-01 00:00:00 +0000
+++ gui/qt5/YZX.xpm 2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * YZX_xpm[] = {
+"40 40 2 1",
+" c None",
+". c #000000",
+" ...... ",
+" .. ",
+" .. ",
+" .. ",
+" . ",
+" .. ",
+" .. ",
+" ...... ",
+" ",
+" . ",
+" . ",
+" ... ",
+" ... ",
+" ... ",
+" ..... ",
+" ..... ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" ........... ",
+" . . ",
+" . .. .. . .. .. ",
+" . . . . .. . . ",
+" . .... . ..... .... ",
+" . .. .................... .... ",
+" . .. . ..... .. ",
+" . .... . ... .. ",
+" . . . . .. ",
+" . .. .. . .. ",
+" . . ",
+" ........... ",
+" ",
+" "};
=== added file 'gui/qt5/ZXY.png'
Binary files gui/qt5/ZXY.png 1970-01-01 00:00:00 +0000 and gui/qt5/ZXY.png 2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/ZXY.xpm'
--- gui/qt5/ZXY.xpm 1970-01-01 00:00:00 +0000
+++ gui/qt5/ZXY.xpm 2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * ZXY_xpm[] = {
+"40 40 2 1",
+" c None",
+". c #000000",
+" .. .. ",
+" . . ",
+" .... ",
+" .. ",
+" .. ",
+" .... ",
+" . . ",
+" .. .. ",
+" ",
+" . ",
+" . ",
+" ... ",
+" ... ",
+" ... ",
+" ..... ",
+" ..... ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" . ",
+" ........... ",
+" . . ",
+" . .. .. . ...... ",
+" . . . . .. .. ",
+" . .... . ..... .. ",
+" . .... .................... .. ",
+" . .. . ..... . ",
+" . .. . ... .. ",
+" . .. . .. ",
+" . .. . ...... ",
+" . . ",
+" ........... ",
+" ",
+" "};
=== added file 'gui/qt5/_GLViewer.cpp'
--- gui/qt5/_GLViewer.cpp 1970-01-01 00:00:00 +0000
+++ gui/qt5/_GLViewer.cpp 2015-06-26 20:19:22 +0000
@@ -0,0 +1,107 @@
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+#include<pkg/common/OpenGLRenderer.hpp>
+#include<lib/pyutil/doc_opts.hpp>
+
+#include<QApplication>
+#include<QCloseEvent>
+
+namespace py=boost::python;
+
+qglviewer::Vec tuple2vec(py::tuple t){ qglviewer::Vec ret; for(int i=0;i<3;i++){py::extract<Real> e(t[i]); if(!e.check()) throw invalid_argument("Element #"+boost::lexical_cast<string>(i)+" is not a number"); ret[i]=e();} return ret;};
+py::tuple vec2tuple(qglviewer::Vec v){return py::make_tuple(v[0],v[1],v[2]);};
+
+class pyGLViewer{
+ const size_t viewNo;
+ public:
+ #define GLV if((OpenGLManager::self->views.size()<=viewNo) || !(OpenGLManager::self->views[viewNo])) throw runtime_error("No view #"+boost::lexical_cast<string>(viewNo)); GLViewer* glv=OpenGLManager::self->views[viewNo].get();
+ pyGLViewer(size_t _viewNo=0): viewNo(_viewNo){}
+ void close(){ GLV; QCloseEvent* e(new QCloseEvent); QApplication::postEvent(glv,e); }
+ py::tuple get_grid(){GLV; return py::make_tuple(bool(glv->drawGrid & 1),bool(glv->drawGrid & 2),bool(glv->drawGrid & 4));}
+ void set_grid(py::tuple t){GLV; glv->drawGrid=0; for(int i=0;i<3;i++) if(py::extract<bool>(t[i])()) glv->drawGrid+=1<<i;}
+ #define VEC_GET_SET(property,getter,setter) Vector3r get_##property(){GLV; qglviewer::Vec v=getter(); return Vector3r(v[0],v[1],v[2]); } void set_##property(const Vector3r& t){GLV; setter(qglviewer::Vec(t[0],t[1],t[2]));}
+ VEC_GET_SET(upVector,glv->camera()->upVector,glv->camera()->setUpVector);
+ VEC_GET_SET(lookAt,glv->camera()->position()+glv->camera()->viewDirection,glv->camera()->lookAt);
+ VEC_GET_SET(viewDir,glv->camera()->viewDirection,glv->camera()->setViewDirection);
+ VEC_GET_SET(eyePosition,glv->camera()->position,glv->camera()->setPosition);
+ #define BOOL_GET_SET(property,getter,setter)void set_##property(bool b){GLV; setter(b);} bool get_##property(){GLV; return getter();}
+ BOOL_GET_SET(axes,glv->axisIsDrawn,glv->setAxisIsDrawn);
+ BOOL_GET_SET(fps,glv->FPSIsDisplayed,glv->setFPSIsDisplayed);
+ bool get_scale(){GLV; return glv->drawScale;} void set_scale(bool b){GLV; glv->drawScale=b;}
+ bool get_orthographic(){GLV; return glv->camera()->type()==qglviewer::Camera::ORTHOGRAPHIC;}
+ void set_orthographic(bool b){GLV; return glv->camera()->setType(b ? qglviewer::Camera::ORTHOGRAPHIC : qglviewer::Camera::PERSPECTIVE);}
+ int get_selection(void){ GLV; return glv->selectedName(); } void set_selection(int s){ GLV; glv->setSelectedName(s); }
+ #define FLOAT_GET_SET(property,getter,setter)void set_##property(Real r){GLV; setter(r);} Real get_##property(){GLV; return getter();}
+ FLOAT_GET_SET(sceneRadius,glv->sceneRadius,glv->setSceneRadius);
+ void fitAABB(const Vector3r& min, const Vector3r& max){GLV; glv->camera()->fitBoundingBox(qglviewer::Vec(min[0],min[1],min[2]),qglviewer::Vec(max[0],max[1],max[2]));}
+ void fitSphere(const Vector3r& center,Real radius){GLV; glv->camera()->fitSphere(qglviewer::Vec(center[0],center[1],center[2]),radius);}
+ void showEntireScene(){GLV; glv->camera()->showEntireScene();}
+ void center(bool median){GLV; if(median)glv->centerMedianQuartile(); else glv->centerScene();}
+ Vector2i get_screenSize(){GLV; return Vector2i(glv->width(),glv->height());}
+ void set_screenSize(Vector2i t){ /*GLV;*/ OpenGLManager::self->emitResizeView(viewNo,t[0],t[1]);}
+ string pyStr(){return string("<GLViewer for view #")+boost::lexical_cast<string>(viewNo)+">";}
+ void saveDisplayParameters(size_t n){GLV; glv->saveDisplayParameters(n);}
+ void useDisplayParameters(size_t n){GLV; glv->useDisplayParameters(n);}
+ void loadState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->restoreStateFromFile(); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);}
+ void saveState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);}
+ string get_timeDisp(){GLV; const int& m(glv->timeDispMask); string ret; if(m&GLViewer::TIME_REAL) ret+='r'; if(m&GLViewer::TIME_VIRT) ret+="v"; if(m&GLViewer::TIME_ITER) ret+="i"; return ret;}
+ void set_timeDisp(string s){GLV; int& m(glv->timeDispMask); m=0; FOREACH(char c, s){switch(c){case 'r': m|=GLViewer::TIME_REAL; break; case 'v': m|=GLViewer::TIME_VIRT; break; case 'i': m|=GLViewer::TIME_ITER; break; default: throw invalid_argument(string("Invalid flag for timeDisp: `")+c+"'");}}}
+ void set_bgColor(const Vector3r& c){ QColor cc(255*c[0],255*c[1],255*c[2]); GLV; glv->setBackgroundColor(cc);} Vector3r get_bgColor(){ GLV; QColor c(glv->backgroundColor()); return Vector3r(c.red()/255.,c.green()/255.,c.blue()/255.);}
+ void saveSnapshot(string filename) {GLV; glv->nextFrameSnapshotFilename = filename;}
+ #undef GLV
+ #undef VEC_GET_SET
+ #undef BOOL_GET_SET
+ #undef FLOAT_GET_SET
+};
+
+// ask to create a new view and wait till it exists
+pyGLViewer createView(){
+ int id=OpenGLManager::self->waitForNewView();
+ if(id<0) throw std::runtime_error("Unable to open new 3d view.");
+ return pyGLViewer((*OpenGLManager::self->views.rbegin())->viewId);
+}
+
+py::list getAllViews(){ py::list ret; FOREACH(const shared_ptr<GLViewer>& v, OpenGLManager::self->views){ if(v) ret.append(pyGLViewer(v->viewId)); } return ret; };
+void centerViews(void){ OpenGLManager::self->centerAllViews(); }
+
+shared_ptr<OpenGLRenderer> getRenderer(){ return OpenGLManager::self->renderer; }
+
+BOOST_PYTHON_MODULE(_GLViewer){
+ YADE_SET_DOCSTRING_OPTS;
+
+ OpenGLManager* glm=new OpenGLManager(); // keep this singleton object forever
+ glm->emitStartTimer();
+
+ py::def("View",createView,"Create a new 3d view.");
+ py::def("center",centerViews,"Center all views.");
+ py::def("views",getAllViews,"Return list of all open :yref:`yade.qt.GLViewer` objects");
+
+ py::def("Renderer",&getRenderer,"Return the active :yref:`OpenGLRenderer` object.");
+
+ py::class_<pyGLViewer>("GLViewer",py::no_init)
+ .add_property("upVector",&pyGLViewer::get_upVector,&pyGLViewer::set_upVector,"Vector that will be shown oriented up on the screen.")
+ .add_property("lookAt",&pyGLViewer::get_lookAt,&pyGLViewer::set_lookAt,"Point at which camera is directed.")
+ .add_property("viewDir",&pyGLViewer::get_viewDir,&pyGLViewer::set_viewDir,"Camera orientation (as vector).")
+ .add_property("eyePosition",&pyGLViewer::get_eyePosition,&pyGLViewer::set_eyePosition,"Camera position.")
+ .add_property("grid",&pyGLViewer::get_grid,&pyGLViewer::set_grid,"Display square grid in zero planes, as 3-tuple of bools for yz, xz, xy planes.")
+ .add_property("fps",&pyGLViewer::get_fps,&pyGLViewer::set_fps,"Show frames per second indicator.")
+ .add_property("axes",&pyGLViewer::get_axes,&pyGLViewer::set_axes,"Show arrows for axes.")
+ .add_property("scale",&pyGLViewer::get_scale,&pyGLViewer::set_scale,"Scale of the view (?)")
+ .add_property("sceneRadius",&pyGLViewer::get_sceneRadius,&pyGLViewer::set_sceneRadius,"Visible scene radius.")
+ .add_property("ortho",&pyGLViewer::get_orthographic,&pyGLViewer::set_orthographic,"Whether orthographic projection is used; if false, use perspective projection.")
+ .add_property("screenSize",&pyGLViewer::get_screenSize,&pyGLViewer::set_screenSize,"Size of the viewer's window, in scree pixels")
+ .add_property("timeDisp",&pyGLViewer::get_timeDisp,&pyGLViewer::set_timeDisp,"Time displayed on in the vindow; is a string composed of characters *r*, *v*, *i* standing respectively for real time, virtual time, iteration number.")
+ // .add_property("bgColor",&pyGLViewer::get_bgColor,&pyGLViewer::set_bgColor) // useless: OpenGLRenderer::Background_color is used via openGL directly, bypassing QGLViewer background property
+ .def("fitAABB",&pyGLViewer::fitAABB,(py::arg("mn"),py::arg("mx")),"Adjust scene bounds so that Axis-aligned bounding box given by its lower and upper corners *mn*, *mx* fits in.")
+ .def("fitSphere",&pyGLViewer::fitSphere,(py::arg("center"),py::arg("radius")),"Adjust scene bounds so that sphere given by *center* and *radius* fits in.")
+ .def("showEntireScene",&pyGLViewer::showEntireScene)
+ .def("center",&pyGLViewer::center,(py::arg("median")=true),"Center view. View is centered either so that all bodies fit inside (*median* = False), or so that 75\% of bodies fit inside (*median* = True).")
+ .def("saveState",&pyGLViewer::saveState,(py::arg("stateFilename")=".qglviewer.xml"),"Save display parameters into a file. Saves state for both :yref:`GLViewer<yade._qt.GLViewer>` and associated :yref:`OpenGLRenderer`.")
+ .def("loadState",&pyGLViewer::loadState,(py::arg("stateFilename")=".qglviewer.xml"),"Load display parameters from file saved previously into.")
+ .def("__repr__",&pyGLViewer::pyStr).def("__str__",&pyGLViewer::pyStr)
+ .def("close",&pyGLViewer::close)
+ .def("saveSnapshot",&pyGLViewer::saveSnapshot,(py::arg("filename")),"Save the current view to image file")
+ .add_property("selection",&pyGLViewer::get_selection,&pyGLViewer::set_selection)
+ ;
+}
+
=== added file 'gui/qt5/__init__.py'
--- gui/qt5/__init__.py 1970-01-01 00:00:00 +0000
+++ gui/qt5/__init__.py 2015-06-26 20:19:22 +0000
@@ -0,0 +1,289 @@
+# encoding: utf-8
+import yade.runtime
+if not yade.runtime.hasDisplay:
+ msg = "Connecting to DISPLAY at Yade startup failed, unable to activate the qt4 interface."
+ import os
+ if 'YADE_BATCH' in os.environ:
+ msg += "\nDo not import qt when running in batch mode."
+ raise ImportError(msg)
+
+from PyQt5.QtGui import *
+from PyQt5 import QtCore
+
+from yade.qt.ui_controller import Ui_Controller
+
+from yade.qt.Inspector import *
+from yade import *
+import yade.system, yade.config
+
+from yade.qt._GLViewer import *
+
+maxWebWindows=1
+"Number of webkit windows that will be cycled to show help on clickable objects"
+webWindows=[]
+"holds instances of QtWebKit windows; clicking an url will open it in the window that was the least recently updated"
+sphinxOnlineDocPath='https://www.yade-dem.org/doc/'
+"Base URL for the documentation. Packaged versions should change to the local installation directory."
+
+
+import os.path
+# find if we have docs installed locally from package
+sphinxLocalDocPath=yade.config.prefix+'/share/doc/yade'+yade.config.suffix+'-doc/html/'
+sphinxBuildDocPath=yade.config.sourceRoot+'/doc/sphinx/_build/html/'
+# we prefer the packaged documentation for this version, if installed
+if os.path.exists(sphinxLocalDocPath+'/index.html'): sphinxPrefix='file://'+sphinxLocalDocPath
+# otherwise look for documentation generated in the source tree
+elif os.path.exists(sphinxBuildDocPath+'/index.html'): sphinxPrefix='file://'+sphinxBuildDocPath
+# fallback to online docs
+else: sphinxPrefix=sphinxOnlineDocPath
+
+sphinxDocWrapperPage=sphinxPrefix+'/yade.wrapper.html'
+
+
+
+
+def openUrl(url):
+ from PyQt5 import QtWebKit
+ global maxWebWindows,webWindows
+ reuseLast=False
+ # use the last window if the class is the same and only the attribute differs
+ try:
+ reuseLast=(len(webWindows)>0 and str(webWindows[-1].url()).split('#')[-1].split('.')[2]==url.split('#')[-1].split('.')[2])
+ #print str(webWindows[-1].url()).split('#')[-1].split('.')[2],url.split('#')[-1].split('.')[2]
+ except: pass
+ if not reuseLast:
+ if len(webWindows)<maxWebWindows: webWindows.append(QtWebKit.QWebView())
+ else: webWindows=webWindows[1:]+[webWindows[0]]
+ web=webWindows[-1]
+ web.load(QUrl(url)); web.setWindowTitle(url);
+ if 0:
+ def killSidebar(result):
+ frame=web.page().mainFrame()
+ frame.evaluateJavaScript("var bv=$('.bodywrapper'); bv.css('margin','0 0 0 0');")
+ frame.evaluateJavaScript("var sbw=$('.sphinxsidebarwrapper'); sbw.css('display','none');")
+ frame.evaluateJavaScript("var sb=$('.sphinxsidebar'); sb.css('display','none'); ")
+ frame.evaluateJavaScript("var sb=$('.sidebar'); sb.css('width','0px'); ")
+ web.loadFinished.disconnect(killSidebar)
+ web.loadFinished.connect(killSidebar)
+ web.show(); web.raise_()
+
+
+controller=None
+
+class ControllerClass(QWidget,Ui_Controller):
+ def __init__(self,parent=None):
+ QWidget.__init__(self)
+ self.setupUi(self)
+ self.generator=None # updated automatically
+ self.renderer=Renderer() # only hold one instance, managed by OpenGLManager
+ self.addPreprocessors()
+ self.addRenderers()
+ global controller
+ controller=self
+ self.refreshTimer=QtCore.QTimer()
+ self.refreshTimer.timeout.connect(self.refreshEvent)
+ self.refreshTimer.start(200)
+ self.iterPerSecTimeout=1 # how often to recompute the number of iterations per second
+ self.iterTimes,self.iterValues,self.iterPerSec=[],[],0 # arrays to keep track of the simulation speed
+ self.dtEditUpdate=True # to avoid updating while being edited
+ # show off with this one as well now
+ def addPreprocessors(self):
+ for c in yade.system.childClasses('FileGenerator'):
+ self.generatorCombo.addItem(c)
+ def addRenderers(self):
+ self.displayCombo.addItem('OpenGLRenderer'); afterSep=1
+ for bc in ('GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlIGeomFunctor','GlIPhysFunctor'):
+ if afterSep>0: self.displayCombo.insertSeparator(10000); afterSep=0
+ for c in yade.system.childClasses(bc) | set([bc]):
+ inst=eval(c+'()');
+ if len(set(inst.dict().keys())-set(['label']))>0:
+ self.displayCombo.addItem(c); afterSep+=1
+ def inspectSlot(self):
+ self.inspector=SimulationInspector(parent=None)
+ self.inspector.show()
+ def setTabActive(self,what):
+ if what=='simulation': ix=0
+ elif what=='display': ix=1
+ elif what=='generator': ix=2
+ elif what=='python': ix=3
+ else: raise ValueErorr("No such tab: "+what)
+ self.controllerTabs.setCurrentIndex(ix)
+ def generatorComboSlot(self,genStr):
+ "update generator parameters when a new one is selected"
+ gen=eval(str(genStr)+'()')
+ self.generator=gen
+ se=SerializableEditor(gen,parent=self.generatorArea,showType=True)
+ self.generatorArea.setWidget(se)
+ def pythonComboSlot(self,cmd):
+ try:
+ code=compile(str(cmd),'<UI entry>','exec')
+ exec code in globals()
+ except:
+ import traceback
+ traceback.print_exc()
+ def generateSlot(self):
+ filename=str(self.generatorFilenameEdit.text())
+ if self.generatorMemoryCheck.isChecked():
+ filename=':memory:'+filename
+ print 'BUG: Saving to memory slots freezes Yade (cause unknown). Cross fingers.'
+ #print 'Will save to ',filename
+ self.generator.generate(filename)
+ if self.generatorAutoCheck:
+ O.load(filename)
+ self.setTabActive('simulation')
+ if len(views())==0:
+ v=View(); v.center()
+ def displayComboSlot(self,dispStr):
+ ser=(self.renderer if dispStr=='OpenGLRenderer' else eval(str(dispStr)+'()'))
+ path='yade.qt.Renderer()' if dispStr=='OpenGLRenderer' else dispStr
+ se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']),showType=True,path=path)
+ self.displayArea.setWidget(se)
+ def loadSlot(self):
+ f=QFileDialog.getOpenFileName(self,'Load simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*')
+ f=str(f)
+ if not f: return # cancelled
+ self.deactivateControls()
+ O.load(f)
+ def saveSlot(self):
+ f=QFileDialog.getSaveFileName(self,'Save simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*')
+ f=str(f)
+ if not f: return # cancelled
+ O.save(f)
+ def reloadSlot(self):
+ self.deactivateControls()
+ from yade import plot
+ plot.splitData()
+ O.reload()
+ def dtFixedSlot(self):
+ O.dt=O.dt
+ O.dynDt=False
+ def dtDynSlot(self):
+ O.dt=-O.dt
+ def dtEditNoupdateSlot(self):
+ self.dtEditUpdate=False
+ def dtEditedSlot(self):
+ try:
+ t=float(self.dtEdit.text())
+ O.dt=t
+ except ValueError: pass
+ self.dtEdit.setText(str(O.dt))
+ self.dtEditUpdate=True
+ def playSlot(self): O.run()
+ def pauseSlot(self): O.pause()
+ def stepSlot(self): O.step()
+ def subStepSlot(self,value): O.subStepping=bool(value)
+ def show3dSlot(self, show):
+ vv=views()
+ assert(len(vv) in (0,1))
+ if show:
+ if len(vv)==0: View()
+ else:
+ if len(vv)>0: vv[0].close()
+ def setReferenceSlot(self):
+ # sets reference periodic cell as well
+ utils.setRefSe3()
+ def centerSlot(self):
+ for v in views(): v.center()
+ def setViewAxes(self,dir,up):
+ try:
+ v=views()[0]
+ v.viewDir=dir
+ v.upVector=up
+ v.center()
+ except IndexError: pass
+ def xyzSlot(self): self.setViewAxes((0,0,-1),(0,1,0))
+ def yzxSlot(self): self.setViewAxes((-1,0,0),(0,0,1))
+ def zxySlot(self): self.setViewAxes((0,-1,0),(1,0,0))
+ def refreshEvent(self):
+ self.refreshValues()
+ self.activateControls()
+ def deactivateControls(self):
+ self.realTimeLabel.setText('')
+ self.virtTimeLabel.setText('')
+ self.iterLabel.setText('')
+ self.fileLabel.setText('<i>[loading]</i>')
+ self.playButton.setEnabled(False)
+ self.pauseButton.setEnabled(False)
+ self.stepButton.setEnabled(False)
+ self.subStepCheckbox.setEnabled(False)
+ self.reloadButton.setEnabled(False)
+ self.dtFixedRadio.setEnabled(False)
+ self.dtDynRadio.setEnabled(False)
+ self.dtEdit.setEnabled(False)
+ self.dtEdit.setText('')
+ self.dtEditUpdate=True
+ def activateControls(self):
+ hasSim=len(O.engines)>0
+ running=O.running
+ if hasSim:
+ self.playButton.setEnabled(not running)
+ self.pauseButton.setEnabled(running)
+ self.reloadButton.setEnabled(O.filename is not None)
+ self.stepButton.setEnabled(not running)
+ self.subStepCheckbox.setEnabled(not running)
+ else:
+ self.playButton.setEnabled(False)
+ self.pauseButton.setEnabled(False)
+ self.reloadButton.setEnabled(False)
+ self.stepButton.setEnabled(False)
+ self.subStepCheckbox.setEnabled(False)
+ self.dtFixedRadio.setEnabled(True)
+ self.dtDynRadio.setEnabled(O.dynDtAvailable)
+ dynDt=O.dynDt
+ self.dtFixedRadio.setChecked(not dynDt)
+ self.dtDynRadio.setChecked(dynDt)
+ if dynDt or self.dtEditUpdate:
+ self.dtEdit.setText(str(O.dt))
+ if dynDt: self.dtEditUpdate=True
+ self.dtEdit.setEnabled(not dynDt)
+ fn=O.filename
+ self.fileLabel.setText(fn if fn else '<i>[no file]</i>')
+
+ def refreshValues(self):
+ rt=int(O.realtime); t=O.time; iter=O.iter;
+ assert(len(self.iterTimes)==len(self.iterValues))
+ if len(self.iterTimes)==0: self.iterTimes.append(rt); self.iterValues.append(iter); self.iterPerSec=0 # update always for the first time
+ elif rt-self.iterTimes[-1]>self.iterPerSecTimeout: # update after a timeout
+ if len(self.iterTimes)==1: self.iterTimes.append(self.iterTimes[0]); self.iterValues.append(self.iterValues[0]) # 2 values, first one is bogus
+ self.iterTimes[0]=self.iterTimes[1]; self.iterValues[0]=self.iterValues[1]
+ self.iterTimes[1]=rt; self.iterValues[1]=iter;
+ self.iterPerSec=(self.iterValues[-1]-self.iterValues[-2])/(self.iterTimes[-1]-self.iterTimes[-2])
+ if not O.running: self.iterPerSec=0
+ stopAtIter=O.stopAtIter
+ subStepInfo=''
+ if O.subStepping:
+ subStep=O.subStep
+ if subStep==-1: subStepInfo=u'→ <i>prologue</i>'
+ elif subStep>=0 and subStep<len(O.engines):
+ e=O.engines[subStep]; subStepInfo=u'→ %s'%(e.label if e.label else e.__class__.__name__)
+ elif subStep==len(O.engines): subStepInfo=u'→ <i>epilogue</i>'
+ else: raise RuntimeError("Invalid O.subStep value %d, should be ∈{-1,…,len(o.engines)}"%subStep)
+ subStepInfo="<br><small>sub %d/%d [%s]</small>"%(subStep,len(O.engines),subStepInfo)
+ self.subStepCheckbox.setChecked(O.subStepping) # might have been changed async
+ if stopAtIter<=iter:
+ self.realTimeLabel.setText('%02d:%02d:%02d'%(rt//3600,(rt%3600)//60,rt%60))
+ self.iterLabel.setText('#%ld, %.1f/s %s'%(iter,self.iterPerSec,subStepInfo))
+ else:
+ e=int((stopAtIter-iter)*self.iterPerSec)
+ self.realTimeLabel.setText('%02d:%02d:%02d (ETA %02d:%02d:%02d)'%(rt//3600,rt//60,rt%60,e//3600,e//60,e%60))
+ self.iterLabel.setText('#%ld / %ld, %.1f/s %s'%(O.iter,stopAtIter,self.iterPerSec,subStepInfo))
+ if t!=float('inf'):
+ s=int(t); ms=int(t*1000)%1000; us=int(t*1000000)%1000; ns=int(t*1000000000)%1000
+ self.virtTimeLabel.setText(u'%03ds%03dm%03dμ%03dn'%(s,ms,us,ns))
+ else: self.virtTimeLabel.setText(u'[ ∞ ] ?!')
+ self.show3dButton.setChecked(len(views())>0)
+
+def Generator():
+ global controller
+ if not controller: controller=ControllerClass();
+ controller.show(); controller.raise_()
+ controller.setTabActive('generator')
+def Controller():
+ global controller
+ if not controller: controller=ControllerClass();
+ controller.show(); controller.raise_()
+ controller.setTabActive('simulation')
+def Inspector():
+ global controller
+ if not controller: controller=ControllerClass();
+ controller.inspectSlot()
=== added file 'gui/qt5/controller.ui'
--- gui/qt5/controller.ui 1970-01-01 00:00:00 +0000
+++ gui/qt5/controller.ui 2015-06-26 20:19:22 +0000
@@ -0,0 +1,1135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Controller</class>
+ <widget class="QDialog" name="Controller">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>290</width>
+ <height>495</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Yade</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="img.qrc">
+ <normaloff>:/img/yade-favicon.xpm</normaloff>:/img/yade-favicon.xpm</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="controllerTabs">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Simulation</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="margin">
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinAndMaxSize</enum>
+ </property>
+ <item>
+ <widget class="QPushButton" name="loadButton">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Load</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveButton">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Save</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="inspectButton">
+ <property name="text">
+ <string>Inspect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinimumSize</enum>
+ </property>
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+ </property>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>real</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="realTimeLabel">
+ <property name="text">
+ <string>00:00:00</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>virt</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="virtTimeLabel">
+ <property name="text">
+ <string>00:000.000m000μ000n</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>iter</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="iterLabel">
+ <property name="text">
+ <string>#0, 0.0/s</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Δt</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QRadioButton" name="dtFixedRadio">
+ <property name="text">
+ <string>fixed</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="dtDynRadio">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>time stepper</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="dtEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="fileLabel">
+ <property name="text">
+ <string>[no file]</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="playButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>5</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>▶</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pauseButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>▮▮</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0" columnstretch="0" rowminimumheight="0,0" columnminimumwidth="0">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="stepButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>12</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>▶▮</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="subStepCheckbox">
+ <property name="font">
+ <font>
+ <pointsize>7</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>sub-step</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="reloadButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>5</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>22</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>↻</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QPushButton" name="show3dButton">
+ <property name="text">
+ <string>Show 3D</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="referenceButton">
+ <property name="text">
+ <string>Reference</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="centerButton">
+ <property name="text">
+ <string>Center</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="xyzButton">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="img.qrc">
+ <normaloff>:/img/XYZ.xpm</normaloff>:/img/XYZ.xpm</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>40</width>
+ <height>40</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="yzxButton">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="img.qrc">
+ <normaloff>:/img/YZX.xpm</normaloff>:/img/YZX.xpm</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>40</width>
+ <height>40</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="zxyButton">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="img.qrc">
+ <normaloff>:/img/ZXY.xpm</normaloff>:/img/ZXY.xpm</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>40</width>
+ <height>40</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Display</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_7">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="displayCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollArea" name="displayArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="displayAreaWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>284</width>
+ <height>433</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Generate</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="generatorCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollArea" name="generatorArea">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>200</horstretch>
+ <verstretch>200</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="generatorAreaWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>500</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>398</width>
+ <height>336</height>
+ </size>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="generatorMemoryCheck">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>memory slot</string>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QLineEdit" name="generatorFilenameEdit">
+ <property name="text">
+ <string>/tmp/scene.yade.gz</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QCheckBox" name="generatorAutoCheck">
+ <property name="text">
+ <string>open automatically</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="generateButton">
+ <property name="text">
+ <string>Generate</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>Python</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QComboBox" name="pythonCombo">
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ <property name="insertPolicy">
+ <enum>QComboBox::InsertAtTop</enum>
+ </property>
+ <property name="minimumContentsLength">
+ <number>1</number>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string><i>(Output appears in the terminal)</i></string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="img.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>loadButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>loadSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>93</x>
+ <y>64</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>0</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>saveButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>saveSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>184</x>
+ <y>64</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>0</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dtFixedRadio</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>dtFixedSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>143</x>
+ <y>170</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>0</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dtDynRadio</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>dtDynSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>268</x>
+ <y>176</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>4</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dtEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>Controller</receiver>
+ <slot>dtEditedSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>147</x>
+ <y>204</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>43</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>playButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>playSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>116</x>
+ <y>304</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>3</x>
+ <y>309</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pauseButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>pauseSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>268</x>
+ <y>304</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>311</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>referenceButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>setReferenceSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>184</x>
+ <y>429</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>5</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>centerButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>centerSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>275</x>
+ <y>429</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>xyzButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>xyzSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>93</x>
+ <y>460</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>0</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>yzxButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>yzxSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>184</x>
+ <y>485</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>243</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>zxyButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>zxySlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>275</x>
+ <y>485</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>generateButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>generateSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>101</x>
+ <y>60</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>469</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dtEdit</sender>
+ <signal>textEdited(QString)</signal>
+ <receiver>Controller</receiver>
+ <slot>dtEditNoupdateSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>147</x>
+ <y>204</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>205</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dtEdit</sender>
+ <signal>cursorPositionChanged(int,int)</signal>
+ <receiver>Controller</receiver>
+ <slot>dtEditNoupdateSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>147</x>
+ <y>204</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>181</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>generatorCombo</sender>
+ <signal>currentIndexChanged(QString)</signal>
+ <receiver>Controller</receiver>
+ <slot>generatorComboSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>101</x>
+ <y>60</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>displayCombo</sender>
+ <signal>currentIndexChanged(QString)</signal>
+ <receiver>Controller</receiver>
+ <slot>displayComboSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>101</x>
+ <y>45</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>108</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pythonCombo</sender>
+ <signal>activated(QString)</signal>
+ <receiver>Controller</receiver>
+ <slot>pythonComboSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>107</x>
+ <y>66</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>68</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>inspectButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>inspectSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>247</x>
+ <y>54</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>0</x>
+ <y>58</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>reloadButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>reloadSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>194</x>
+ <y>363</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>284</x>
+ <y>437</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>stepButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Controller</receiver>
+ <slot>stepSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>79</x>
+ <y>363</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>6</x>
+ <y>450</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>subStepCheckbox</sender>
+ <signal>stateChanged(int)</signal>
+ <receiver>Controller</receiver>
+ <slot>subStepSlot()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>42</x>
+ <y>376</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-2</x>
+ <y>381</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>show3dButton</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Controller</receiver>
+ <slot>show3dSlot(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>42</x>
+ <y>418</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>-2</x>
+ <y>422</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <slot>loadSlot()</slot>
+ <slot>saveSlot()</slot>
+ <slot>reloadSlot()</slot>
+ <slot>playSlot()</slot>
+ <slot>pauseSlot()</slot>
+ <slot>stepSlot()</slot>
+ <slot>setReferenceSlot()</slot>
+ <slot>centerSlot()</slot>
+ <slot>dtFixedSlot()</slot>
+ <slot>dtDynSlot()</slot>
+ <slot>dtEditedSlot()</slot>
+ <slot>xyzSlot()</slot>
+ <slot>yzxSlot()</slot>
+ <slot>zxySlot()</slot>
+ <slot>generateSlot()</slot>
+ <slot>dtEditNoupdateSlot()</slot>
+ <slot>generatorComboSlot()</slot>
+ <slot>displayComboSlot()</slot>
+ <slot>pythonComboSlot()</slot>
+ <slot>pythonEditSlot()</slot>
+ <slot>inspectSlot()</slot>
+ <slot>subStepSlot()</slot>
+ <slot>show3dSlot(bool)</slot>
+ </slots>
+</ui>
=== added file 'gui/qt5/img.qrc'
--- gui/qt5/img.qrc 1970-01-01 00:00:00 +0000
+++ gui/qt5/img.qrc 2015-06-26 20:19:22 +0000
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="img">
+ <file>yade-favicon.xpm</file>
+ <file>XYZ.xpm</file>
+ <file>YZX.xpm</file>
+ <file>ZXY.xpm</file>
+ </qresource>
+</RCC>
=== added file 'gui/qt5/yade-favicon.png'
Binary files gui/qt5/yade-favicon.png 1970-01-01 00:00:00 +0000 and gui/qt5/yade-favicon.png 2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/yade-favicon.xpm'
--- gui/qt5/yade-favicon.xpm 1970-01-01 00:00:00 +0000
+++ gui/qt5/yade-favicon.xpm 2015-06-26 20:19:22 +0000
@@ -0,0 +1,229 @@
+/* XPM */
+static char * yade_favicon_xpm[] = {
+"16 16 210 2",
+" c None",
+". c #F47372",
+"+ c #F1B4B5",
+"@ c #909092",
+"# c #EA5A5A",
+"$ c #8E4D3C",
+"% c #746C35",
+"& c #616233",
+"* c #4B4B0E",
+"= c #939191",
+"- c #8A8A8A",
+"; c #808380",
+"> c #6F6E6D",
+", c #4F4D2B",
+"' c #646427",
+") c #383827",
+"! c #EB9D9E",
+"~ c #7F6B5D",
+"{ c #828500",
+"] c #A2A200",
+"^ c #838500",
+"/ c #404031",
+"( c #AEAEAE",
+"_ c #8C8C8C",
+": c #ABAAAF",
+"< c #444319",
+"[ c #9E9C01",
+"} c #B4B106",
+"| c #908E0C",
+"1 c #4C4C4F",
+"2 c #D0BBBB",
+"3 c #B07E83",
+"4 c #706137",
+"5 c #B9B704",
+"6 c #C8C901",
+"7 c #686800",
+"8 c #4C4C51",
+"9 c #A3A1A3",
+"0 c #ABA9AC",
+"a c #45444A",
+"b c #8F8E00",
+"c c #A3A005",
+"d c #AAA809",
+"e c #25241B",
+"f c #D79090",
+"g c #E25E5E",
+"h c #FCBEC9",
+"i c #E6AF6F",
+"j c #D6B700",
+"k c #ACAA00",
+"l c #3F4000",
+"m c #53525D",
+"n c #666472",
+"o c #6A6A02",
+"p c #B7B509",
+"q c #A3A407",
+"r c #3F4008",
+"s c #E07B7A",
+"t c #E35151",
+"u c #DC6565",
+"v c #C76B67",
+"w c #D79E42",
+"x c #ABA10D",
+"y c #99A400",
+"z c #393C21",
+"A c #40401D",
+"B c #9A9A01",
+"C c #B6B406",
+"D c #737100",
+"E c #545157",
+"F c #DCA8A8",
+"G c #DAA5A7",
+"H c #D86261",
+"I c #D85D5D",
+"J c #DEC7D1",
+"K c #E1BE91",
+"L c #B5C100",
+"M c #848D04",
+"N c #818103",
+"O c #8C8C08",
+"P c #7A7A02",
+"Q c #504F33",
+"R c #827F8D",
+"S c #C85757",
+"T c #CC6666",
+"U c #D65757",
+"V c #DB5353",
+"W c #E59596",
+"X c #E58A82",
+"Y c #BFB500",
+"Z c #B4BD05",
+"` c #BBBA0B",
+" . c #A4A306",
+".. c #5E5D13",
+"+. c #565463",
+"@. c #B47070",
+"#. c #C69392",
+"$. c #D57E7E",
+"%. c #DA2929",
+"&. c #ED393A",
+"*. c #8B3537",
+"=. c #949700",
+"-. c #9AA706",
+";. c #ABB10A",
+">. c #747502",
+",. c #2C2B35",
+"'. c #B2AFBB",
+"). c #B86363",
+"!. c #C8BEBE",
+"~. c #DBCFCF",
+"{. c #DF7373",
+"]. c #F16466",
+"^. c #E79DA6",
+"/. c #5A5408",
+"(. c #EEAC06",
+"_. c #EBBA08",
+":. c #312E0B",
+"<. c #7D7B8A",
+"[. c #8C899A",
+"}. c #858394",
+"|. c #777588",
+"1. c #B75E5E",
+"2. c #C38181",
+"3. c #D58383",
+"4. c #AB5B5A",
+"5. c #DF484A",
+"6. c #F28088",
+"7. c #5F4507",
+"8. c #FD8D08",
+"9. c #FFBD0C",
+"0. c #3D330C",
+"a. c #B8B5C6",
+"b. c #858297",
+"c. c #BD6464",
+"d. c #C24748",
+"e. c #CD5E5F",
+"f. c #D2ADAE",
+"g. c #DA8E91",
+"h. c #E73643",
+"i. c #5B3206",
+"j. c #EF8308",
+"k. c #EF7510",
+"l. c #3C2A0F",
+"m. c #ADAEC2",
+"n. c #8F8DA3",
+"o. c #8A88A1",
+"p. c #9490AB",
+"q. c #69687B",
+"r. c #C30C0C",
+"s. c #BD3C3F",
+"t. c #C6777A",
+"u. c #D6B7BA",
+"v. c #D9A2A7",
+"w. c #D9848C",
+"x. c #DBC5C3",
+"y. c #E49A37",
+"z. c #F59212",
+"A. c #482E17",
+"B. c #D5ABBA",
+"C. c #817A95",
+"D. c #A491A9",
+"E. c #8986A2",
+"F. c #8E89A7",
+"G. c #7B7795",
+"H. c #C55252",
+"I. c #C37072",
+"J. c #BE4951",
+"K. c #CA6A72",
+"L. c #CF4E5A",
+"M. c #D49098",
+"N. c #DDD2D6",
+"O. c #E6A489",
+"P. c #EB7313",
+"Q. c #452716",
+"R. c #FEC1CD",
+"S. c #E76B80",
+"T. c #F7BBC5",
+"U. c #CBACBA",
+"V. c #9292AD",
+"W. c #837FA0",
+"X. c #C62F2F",
+"Y. c #C73E3F",
+"Z. c #C53F4A",
+"`. c #B93344",
+" + c #CD3C4D",
+".+ c #D75767",
+"++ c #E17484",
+"@+ c #C46559",
+"#+ c #DA743E",
+"$+ c #53241E",
+"%+ c #ED546E",
+"&+ c #E66E82",
+"*+ c #F3BCC6",
+"=+ c #E19FAF",
+"-+ c #B81415",
+";+ c #BA4D4F",
+">+ c #AD7782",
+",+ c #AC4B5D",
+"'+ c #C09FA9",
+")+ c #BB3A52",
+"!+ c #C14F67",
+"~+ c #CD5858",
+"{+ c #E1AEAC",
+"]+ c #D9D0D0",
+"^+ c #D25169",
+"/+ c #DC7C8F",
+"(+ c #F0C9D1",
+"_+ c #E6A3B2",
+":+ c #A27791",
+"<+ c #AA98B1",
+". + @ ",
+"# $ % & * = - ; > , ' ) ",
+"! ~ { ] ^ / ( _ : < [ } | 1 ",
+"2 3 4 5 6 7 8 9 0 a b c d e ",
+"f g h i j k l m n o p q r ",
+"s t u v w x y z A B C D E ",
+"F G H I J K L M N O P Q R ",
+"S T U V W X Y Z ` ...+. ",
+"@.#.$.%.&.*.=.-.;.>.,. '. ",
+").!.~.{.].^./.(._.:.<.[.}.|. ",
+"1.2.3.4.5.6.7.8.9.0.a. b. ",
+"c.d.e.f.g.h.i.j.k.l.m.n.o. p.q.",
+"r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.",
+"H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.",
+"X.Y.Z.`. +.+++@+#+$+%+&+*+=+ ",
+"-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+"};