def __init__(self): ManipulatedFrame.__init__(self) # set of selected point self.selection = [] # axis for axial displacement self.axis = [ SelectionAxis(self, SelectionAxis.X, material=Material((250, 30, 30), 1)), SelectionAxis(self, SelectionAxis.Y, material=Material((30, 250, 30), 1)), SelectionAxis(self, SelectionAxis.Z, material=Material((250, 250, 250), 1)) ] # previous position to compute displacement self.previousPosition = Vec() self.manipulated = pyqtSignal( ) # AUTO SIGNAL TRANSLATION in class SelectionManipulator self.manipulated.connect( self.propagatePointTranslation ) # QObject.connect(self,SIGNAL("manipulated()"),self.propagatePointTranslation) for axis in self.axis: axis.translated.connect( self.propagateAxisTranslation ) # QObject.connect(axis,SIGNAL("translated(PyQt_PyObject)"),self.propagateAxisTranslation)
class MaterialPanelWidget(QWidget): valueChanged = pyqtSignal() def __init__(self, parent): QWidget.__init__(self, parent) self.setObjectName("materialPanelContents") self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("materialPanelLayout") self.materialpanel = QScrollArea(self) self.view = MaterialPanelView(self.materialpanel) self.materialpanel.setWidget(self.view) self.materialpanel.setWidgetResizable(True) self.materialpanel.setObjectName("materialPanelArea") self.verticalLayout.addWidget(self.materialpanel) self.view.valueChanged.connect(self.__updateStatus) def __updateStatus(self): self.valueChanged.emit() def setTurtle(self, turtle): self.view.setTurtle(turtle) def getTurtle(self): return self.view.getTurtle() def setAppearanceList(self, matlist): self.view.setAppearanceList(matlist) def getAppearanceList(self): return self.view.getAppearanceList()
class LpyTabBarNeighbor(QWidget): newDocumentRequest = pyqtSignal() def __init__(self,parent): QWidget.__init__(self,parent) def mouseDoubleClickEvent(self,event): self.newDocumentRequest.emit() QWidget.mouseDoubleClickEvent(self,event)
class ComputationTask(QThread): killed = pyqtSignal() def __init__(self, process=None, postprocess=None, preprocess=None, cleanupprocess=None): QThread.__init__(self) self.process = process self.postprocess = postprocess self.preprocess = preprocess self.cleanupprocess = cleanupprocess self.threaded = True self.exception = None def initialize(self): if self.preprocess: self.preprocess(self) def run(self): if self.process: if self.threaded: try: self.process(self) except: self.exception = ThreadTransferException( sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]) else: self.process(self) def finalize(self): if self.threaded and not self.exception is None: raise self.exception if self.postprocess: self.postprocess(self) def apply(self): self.threaded = False self.initialize() self.run() self.finalize() def kill(self): self.quit() if self.cleanupprocess: self.cleanupprocess() self.killed.emit() def __call__(self): self.start()
class ItemSlider(QSpinBox): valueChanged = pyqtSignal('PyQt_PyObject') def __init__(self,orientation, parent, item): QSpinBox.__init__(self, parent) self.item = item scalar = item.scalar self.setRange(scalar.minvalue,scalar.maxvalue) self.setValue(scalar.value) self.valueChanged.connect(self.updateItem) # QObject.connect(self,SIGNAL('valueChanged(int)'),self.updateItem) def updateItem(self,value): self.item.scalar.value = value self.item.setText(str(value)) self.valueChanged.emit(self.item.scalar) # self.emit(SIGNAL('valueChanged(PyQt_PyObject)'),self.item.scalar) # AUTO SIGNAL TRANSLATION
class MyItemModel(QStandardItemModel): moveRequest = pyqtSignal(int,int) def __init__(self,a,b,scalarmap): QStandardItemModel.__init__(self,a,b) self.scalarmap = scalarmap def dropMimeData(self,data,action,row,column,parent): encoded = data.data("application/x-qstandarditemmodeldatalist") stream = QDataStream(encoded, QIODevice.ReadOnly) r = stream.readInt() self.moveRequest.emit(r,parent.row()) # self.emit(SIGNAL("moveRequest(int,int)"),r,parent.row()) # AUTO SIGNAL TRANSLATION return True def supportedDropActions(self): return Qt.MoveAction
class MaterialPanelDock(QDockWidget): valueChanged = pyqtSignal() def __init__(self, parent, name=None): QDockWidget.__init__(self, parent) if name: self.setObjectName(name.replace(' ', '_')) self.setWindowTitle('Color Map') self.view = MaterialPanelWidget(self) self.setWidget(self.view) self.view.valueChanged.connect(self.__updateStatus) def setStatusBar(self, st): self.view.statusBar = st def showMessage(self, msg, timeout): if hasattr(self, 'statusBar'): self.statusBar.showMessage(msg, timeout) else: print(msg) def __updateStatus(self): self.valueChanged.emit()
class CtrlPoint(ManipulatedFrame): """the class CtrlPoint represent the control points of a NurbsPatch by PlantGL Spheres. The user can grab them and move them dynamically. Change of value is automatically propagate to the initial structure using the position_setter function """ #modified = pyqtSignal() #manipulated = pyqtSignal() translated = pyqtSignal("PyQt_PyObject", "PyQt_PyObject") valueChanged = pyqtSignal(int) def __init__(self, position, position_setter, color=(30, 30, 250), id=0): """Constructor :param position: initial position of the control point :param position_setter: makes it possible to propagate position change to initial structure """ ManipulatedFrame.__init__(self) self.id = id # current position and method to propagate displacement on original structure self.setPosition(Vec(position[0], position[1], position[2])) self.position_setter = position_setter # point selection or focus self.selected = False self.hasFocus = False self.color = color # previous position to compute displacement self.previousPosition = self.position() # Constraint the direction of translation self.constraint = WorldConstraint() self.constraint.setRotationConstraintType( AxisPlaneConstraint.FORBIDDEN) self.setConstraint(self.constraint) # SIGNAL to propagate displacement self.modified.connect( self.__propagate_position_change__ ) # QObject.connect(self,SIGNAL('modified()'),self.__propagate_position_change__) self.manipulated.connect( self.__emit_translation__ ) # QObject.connect(self,SIGNAL('manipulated()'),self.__emit_translation__) # manipulated call back self.callback = None def setCallBack(self, callback): self.callback = callback def __push_position__(self): """ push current position status as previous position """ self.previousPosition = self.position() def displacement(self): """ get displacement applied to the point """ delta = self.position() - self.previousPosition self.__push_position__() return delta def __propagate_position_change__(self): """ apply position change to initial structure """ self.position_setter(self.position()) def __emit_translation__(self): """ emit a SIGNAL when a translation has been applied to the point """ pos = self.position() tr = pos - self.previousPosition self.translated.emit( self, tr ) # self.emit(SIGNAL("translated(PyQt_PyObject,PyQt_PyObject)"),self,tr) # AUTO SIGNAL TRANSLATION self.valueChanged.emit( self.id ) # self.emit(SIGNAL("valueChanged(int)"),self.id) # AUTO SIGNAL TRANSLATION if self.callback: self.callback(self.id) def apply_translation(self, tr): """ apply a displacement. USefull when point is part of selection and a displacement occur on another point """ self.setPosition(self.position() + tr) self.__push_position__() def representation(self, pointsize=0.05): """ Compute representation of the control points as PlantGL Spheres""" p = self.position() #we change the color of the point if it is selected or has focus if self.hasFocus: m = Material((250, 30, 30), 1) # red elif self.selected: m = Material((250, 250, 30), 1) #yellow else: m = Material(self.color, 1) #the point is represented by a PlantGl Sphere primitive = pointsize if isinstance(pointsize, Geometry) else Sphere(pointsize) return Shape(Translated(Vector3(p.x, p.y, p.z), primitive), m, self.id) def getx(self): """ Get the x coordinate """ return self.position().x def gety(self): """ Get the y coordinate """ return self.position().y def getz(self): """ Get the z coordinate """ return self.position().z def setx(self, x): """ Set the x coordinate """ p = self.position() p.x = x self.setPosition(p) x = property(getx, """ The x coordinate property """) y = property(gety, """ The y coordinate property """) z = property(getz, """ The z coordinate property """)
class MaterialPanelView(QGLWidget): valueChanged = pyqtSignal() def __init__(self, parent): QGLWidget.__init__(self, parent) self.unitsize = 30 self.sphere = None self.spherelist = None self.darkcheck = [0.5, 0.5, 0.5] self.lightcheck = [0.9, 0.9, 0.9] self.checklist = None self.selectedBorderList = None self.turtle = PglTurtle() self.mousepos = None self.initpos = None self.menuselection = None self.selectionbegin = None self.selectionend = None self.cursorselection = None self.cutaction = False self.preview = None self.previewselection = None self.previewtrigger = QTimer(self) self.previewtrigger.setInterval(1000) self.previewtrigger.setSingleShot(True) self.clipboard = None self.previewtrigger.timeout.connect(self.displayPreview) self.valueChanged.connect(self.computeSize) self.lastTextureDir = '.' self.preview_start_show = False self.defaultmat = Material('default') self.setAcceptDrops(True) def setTurtle(self, turtle): self.turtle = turtle self.cutaction = False self.computeSize() if self.isVisible(): self.updateGL() def getTurtle(self): return self.turtle def getMaterial(self, index): return self.turtle.getMaterial(index) def setMaterial(self, index, value): self.turtle.setMaterial(index, value) def insertMaterial(self, index, value): self.turtle.insertMaterial(index, value) def delMaterial(self, index): self.turtle.removeColor(index) def nbMaterial(self): return self.turtle.getColorListSize() def swapMaterial(self, index1, index2): mat1, mat2 = self.getMaterial(index1), self.getMaterial(index2) mat1.name = 'Color_' + str(index2) mat2.name = 'Color_' + str(index1) self.setMaterial(index1, mat2), self.setMaterial(index2, mat1) def setAppearanceList(self, appList): # for i,mat in enumerate(materialList): # self.setMaterial(i,mat) # if self.nbMaterial() > len(materialList): # for i in xrange(self.nbMaterial()-1,len(materialList)-1,-1): # self.delMaterial(i) self.turtle.setColorList(appList) self.cutaction = False self.computeSize() if self.isVisible(): self.updateGL() def getAppearanceList(self): return self.turtle.getColorList() def resizeGL(self, w, h): if w == 0 or h == 0: return self.computeSize() def computeSize(self, w=None, h=None): if w is None: w = self.width() if h is None: h = self.height() if w == 0 or h == 0: return nbcitem, nbritem = self.getNbColRow(w, h) pcolor = self.positionColor(self.nbMaterial()) maxwidth = pcolor[0] + (2 * self.unitsize) self.setMinimumSize(maxwidth, 2 * self.unitsize) def initializeGL(self): glClearColor(1.0, 1.0, 1.0, 1.0) glShadeModel(GL_SMOOTH) glLightfv(GL_LIGHT0, GL_POSITION, [1000, 1000, 2000, 0]) glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [1.0, 1.0, 1.0, 1.0]) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glEnable(GL_DEPTH_TEST) glEnable(GL_BLEND) if self.sphere: gluDeleteQuadric(self.sphere) self.sphere = gluNewQuadric() gluQuadricDrawStyle(self.sphere, GLU_FILL) gluQuadricNormals(self.sphere, GLU_SMOOTH) self.spherelist = glGenLists(1) glNewList(self.spherelist, GL_COMPILE) gluSphere(self.sphere, self.unitsize * 0.8, 80, 80) glEndList() checkdepth = -self.unitsize self.checklist = glGenLists(1) glNewList(self.checklist, GL_COMPILE) glDisable(GL_LIGHTING) glBegin(GL_QUADS) glColor3fv(self.lightcheck) glVertex3f(-self.unitsize, self.unitsize, checkdepth) glVertex3f(0, self.unitsize, checkdepth) glVertex3f(0, 0, checkdepth) glVertex3f(-self.unitsize, 0, checkdepth) glVertex3f(0, 0, checkdepth) glVertex3f(self.unitsize, 0, checkdepth) glVertex3f(self.unitsize, -self.unitsize, checkdepth) glVertex3f(0, -self.unitsize, checkdepth) glColor3fv(self.darkcheck) glVertex3f(0, self.unitsize, checkdepth) glVertex3f(self.unitsize, self.unitsize, checkdepth) glVertex3f(self.unitsize, 0, checkdepth) glVertex3f(0, 0, checkdepth) glVertex3f(-self.unitsize, 0, checkdepth) glVertex3f(0, 0, checkdepth) glVertex3f(0, -self.unitsize, checkdepth) glVertex3f(-self.unitsize, -self.unitsize, checkdepth) glEnd() glEndList() self.texturedQuadList = glGenLists(1) glNewList(self.texturedQuadList, GL_COMPILE) glBegin(GL_QUADS) glTexCoord2d(0, 1) glVertex3f(-self.unitsize, -self.unitsize, checkdepth) glTexCoord2d(1, 1) glVertex3f(self.unitsize, -self.unitsize, checkdepth) glTexCoord2d(1, 0) glVertex3f(self.unitsize, self.unitsize, checkdepth) glTexCoord2d(0, 0) glVertex3f(-self.unitsize, self.unitsize, checkdepth) glEnd() glEndList() borderdepth = -self.unitsize + 1 self.selectedBorderList = glGenLists(1) glNewList(self.selectedBorderList, GL_COMPILE) glLineWidth(3) glColor3f(0.3, 0.3, 0.3) glBegin(GL_LINE_STRIP) glVertex3f(-self.unitsize, -self.unitsize, borderdepth) glVertex3f(self.unitsize, -self.unitsize, borderdepth) glVertex3f(self.unitsize, self.unitsize, borderdepth) glVertex3f(-self.unitsize, self.unitsize, borderdepth) glVertex3f(-self.unitsize, -self.unitsize, borderdepth) glEnd() glLineWidth(1) glColor3f(0.0, 0.0, 0.0) borderdepth += 1 glBegin(GL_LINE_STRIP) glVertex3f(-self.unitsize, -self.unitsize, borderdepth) glVertex3f(self.unitsize, -self.unitsize, borderdepth) glVertex3f(self.unitsize, self.unitsize, borderdepth) glVertex3f(-self.unitsize, self.unitsize, borderdepth) glVertex3f(-self.unitsize, -self.unitsize, borderdepth) glEnd() glEndList() def paintGL(self): cursorselection = -1 if not self.isVisible(): return w, h = self.width(), self.height() if w == 0 or h == 0: return if self.mousepos != None and self.geometry().contains(self.mousepos): cursorselection = self.selectedColor(self.mousepos.x(), self.mousepos.y()) scaling = self.window().devicePixelRatio() glViewport(0, 0, int(w * scaling), int(h * scaling)) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, w, h, 0, -3000, 1000) glMatrixMode(GL_MODELVIEW) glLoadIdentity() try: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) except: # The visible attribute is not correctly set on mac. # The widget is not really visible and the initialization failed. return glShadeModel(GL_SMOOTH) nbcitem, nbritem = self.getNbColRow(w, h) colindex = 0 d = Discretizer() glr = GLRenderer(d) selectionbegin = self.selectionbegin selectionend = self.selectionend for i in range(0, int(nbcitem)): for j in range(0, int(nbritem)): if colindex < self.nbMaterial(): curmat = self.getMaterial(colindex) else: curmat = self.defaultmat istexture = curmat.isTexture() inselection = self.isInSelection(colindex) glPushMatrix() glTranslate(self.unitsize + 2 * self.unitsize * i, self.unitsize + 2 * self.unitsize * j, 0) if inselection and not self.initpos is None: glCallList(self.checklist) decal = self.mousepos - self.initpos glTranslate(decal.x(), decal.y(), 2 * self.unitsize) if istexture: glCallList(self.checklist) glTranslate(0, 0, 1) glEnable(GL_LIGHTING) curmat.apply(glr) glCallList(self.texturedQuadList) glDisable(GL_TEXTURE_2D) if inselection: glCallList(self.selectedBorderList) else: glCallList(self.checklist) if inselection: glCallList(self.selectedBorderList) glEnable(GL_LIGHTING) curmat.apply(glr) if colindex == cursorselection: glScalef(1.3, 1.3, 1.3) elif inselection: glScalef(1.2, 1.2, 1.2) glCallList(self.spherelist) glPopMatrix() glColor3f(0, 0, 0) self.renderText(2 * self.unitsize * i, 2 * self.unitsize * (j + 1), 4 * self.unitsize, str(colindex)) colindex += 1 glPushMatrix() glTranslate(self.unitsize + 2 * self.unitsize * i, self.unitsize + 2 * self.unitsize * nbritem, 0) glCallList(self.checklist) glPopMatrix() def getNbColRow(self, w, h): nbcol = (w / (2 * self.unitsize)) + 1 nbrow, rest = divmod(h, 2 * self.unitsize) if rest > self.unitsize: nbrow += 1 if nbrow == 0: nbrow = 1 return nbcol, nbrow def isInSelection(self, i): if self.selectionend is None: return False if self.selectionbegin is None: return False if self.selectionbegin <= i <= self.selectionend: return True if self.selectionend <= i <= self.selectionbegin: return True return False def lenSelection(self): if self.selectionend is None: return 0 if self.selectionbegin is None: return 0 return abs(self.selectionend - self.selectionbegin) + 1 def setSelection(self, beg, end=None): self.selectionbegin = beg if end is None: self.selectionend = beg else: self.selectionend = end def getSelection(self): return range(self.selectionbegin, self.selectionend + 1) def selectedColor(self, x, y): w = self.width() h = self.height() nbcol, nbrow = self.getNbColRow(w, h) nbcolselection = x // (2 * self.unitsize) nbrowselection = y // (2 * self.unitsize) if nbcolselection >= nbcol or nbrowselection >= nbrow: return -1 id = nbcolselection * nbrow + nbrowselection return id def positionColor(self, id): w = self.width() h = self.height() nbcol, nbrow = self.getNbColRow(w, h) idcol, idrow = divmod(id, nbrow) return (idcol * (2 * self.unitsize), idrow * (2 * self.unitsize)) def mouseDoubleClickEvent(self, event): x, y = event.pos().x(), event.pos().y() self.edition(self.selectedColor(x, y)) self.initpos = None event.accept() self.updateGL() def mousePressEvent(self, event): if self.preview and self.preview.isVisible(): self.preview.hide() if event.button() == Qt.LeftButton: self.initpos = event.pos() self.mousepos = self.initpos x, y = event.pos().x(), event.pos().y() if event.modifiers() & Qt.ShiftModifier: self.selectionend = self.selectedColor(x, y) else: self.setSelection(self.selectedColor(x, y)) self.updateGL() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: x, y = event.pos().x(), event.pos().y() if event.modifiers() & Qt.ShiftModifier: self.selectionend = self.selectedColor(x, y) else: initialselection = self.selectionbegin self.setSelection(self.selectedColor(x, y)) if self.selectionbegin == self.selectionend: if initialselection != self.selectionbegin: self.showMessage( "Swap colors " + str(initialselection) + " and " + str(self.selectionbegin), 2000) self.swapMaterial(initialselection, self.selectionbegin) self.valueChanged.emit() else: self.showMessage( "Click on color " + str(self.selectionbegin), 2000) else: self.selectionbegin, self.selectionend = min( self.selectionbegin, self.selectionend), max(self.selectionbegin, self.selectionend) self.showMessage( "Selected colors from " + str(self.selectionbegin) + ' to ' + str(self.selectionend), 2000) self.initpos = None self.updateGL() def mouseMoveEvent(self, event): self.mousepos = event.pos() lastcursorselection = self.cursorselection self.cursorselection = self.selectedColor(self.mousepos.x(), self.mousepos.y()) if self.cursorselection < 0 or not self.geometry().contains( self.mousepos): self.cursorselection = lastcursorselection if event.buttons() == Qt.LeftButton: if self.previewtrigger.isActive(): self.previewtrigger.stop() if event.modifiers() & Qt.ShiftModifier: self.selectionend = self.cursorselection # else: # if self.cursorselection != lastcursorselection and not lastcursorselection is None: # p1 = QPoint(*self.positionColor(lastcursorselection)) # p2 = QPoint(*self.positionColor(self.cursorselection)) # self.initpos = self.initpos - p1 + p2 # self.setSelection(self.cursorselection) elif event.buttons( ) == Qt.NoButton and not self.cursorselection is None and self.cursorselection >= 0: cmat = self.getMaterial(self.cursorselection) if cmat.isTexture(): if lastcursorselection != self.cursorselection or ( self.preview is None and not self.previewtrigger.isActive()): self.setToolTip('Texture ' + str(self.cursorselection) + ' : "' + cmat.image.filename + '"') if not self.preview is None: self.preview.hide() self.previewselection = self.cursorselection self.previewtrigger.start() elif self.preview and self.preview.isVisible(): self.preview.move(event.globalPos() + QPoint(2, 2)) else: if lastcursorselection != self.cursorselection: self.setToolTip('Color ' + str(self.cursorselection)) if not self.preview is None and self.preview.isVisible(): self.preview.hide() if self.previewtrigger.isActive(): self.previewtrigger.stop() self.showMessage("Mouse on color " + str(self.cursorselection), 2000) self.updateGL() def displayPreview(self): self.preview = QSplashScreen( self, QPixmap(self.getMaterial(self.previewselection).image.filename)) self.preview.move(QCursor.pos() + QPoint(2, 2)) self.preview.show() def leaveEvent(self, event): if not self.geometry().contains(self.mapFromGlobal(QCursor.pos())): self.mousepos = None self.initpos = None self.setMouseTracking(False) self.updateGL() if self.preview and self.preview.isVisible(): self.preview.hide() if self.previewtrigger.isActive(): self.previewtrigger.stop() QGLWidget.leaveEvent(self, event) def enterEvent(self, event): self.mousepos = None self.setMouseTracking(True) QGLWidget.enterEvent(self, event) def showMessage(self, msg, timeout): if hasattr(self, 'statusBar'): self.statusBar.showMessage(msg, timeout) def edition(self, id): self.showMessage("Edit color = " + str(id), 2000) if id >= 0: color = self.getMaterial(id) if not color.isTexture(): try: res = editMaterialInDialog( color) # Warning Here! QLayout problem. if res is None or res == QDialog.Accepted: self.valueChanged.emit() except Exception as e: print(e) print( 'editMaterialInDialog not supported by your version of PlantGL' ) else: self.edittexture(color.image.filename) def contextMenuEvent(self, event): self.menuselection = self.selectedColor(event.x(), event.y()) if self.lenSelection() > 0: if not self.isInSelection(self.menuselection): self.setSelection(self.menuselection) self.updateGL() else: self.setSelection(self.menuselection) self.updateGL() menu = QMenu("Color Edit", self) menu.addAction("Copy", self.copymaterial) menu.addAction("Cut", self.cutmaterial) action = menu.addAction("Paste", self.pastematerial) if self.clipboard is None: action.setEnabled(False) action = menu.addAction("Load texture", self.loadtexture) if self.lenSelection() > 1: action.setEnabled(False) if self.lenSelection() == 1 and self.getMaterial( self.getSelection()[0]).isTexture(): menu.addAction("Open repository", self.opentexturerepository) menu.addAction("Remove", self.removematerial) action = menu.addAction("Interpolate", self.interpolatematerial) if self.lenSelection() < 3: action.setEnabled(False) menu.exec_(event.globalPos()) def opentexturerepository(self): import os, sys cmat = self.getMaterial(self.getSelection()[0]) fname = os.path.abspath(cmat.image.filename) mdir = os.path.dirname(fname) if sys.platform == 'win32': import subprocess subprocess.call('explorer /select,"' + fname + '"') elif sys.platform == 'linux2': os.system('xdg-open "' + mdir + '"') else: os.system('open "' + mdir + '"') def copymaterial(self): self.clipboard = [(i, self.getMaterial(i)) for i in self.getSelection()] self.cutaction = False self.menuselection = None def cutmaterial(self): self.clipboard = [(i, self.getMaterial(i)) for i in self.getSelection()] self.cutaction = True self.menuselection = None def pastematerial(self): if self.clipboard is None or not self.lenSelection() in [ 1, len(self.clipboard) ]: QMessageBox.warning( self, 'Copy error', 'Cannot copy materials ! Source and target are inconsistents') else: if self.lenSelection() == 1: sel = self.selectionbegin if self.cutaction == True: sourceindices = [i[0] for i in self.clipboard] if sel in sourceindices: sel = min(sourceindices) sourceindices.pop(sourceindices.index(sel)) for i in reversed(sourceindices): self.delMaterial(i) if i < sel: sel -= 1 print('Copy ', self.clipboard[0][0], 'to', sel) self.setMaterial(sel, self.clipboard[0][1].deepcopy()) for i, color in reversed(self.clipboard[1:]): self.insertMaterial(sel + 1, color.deepcopy()) self.setSelection(sel, sel + len(self.clipboard) - 1) else: for color, pos in zip(self.clipboard, self.getSelection()): self.setMaterial(pos, color[1].deepcopy()) if self.cutaction == True: iminus = 0 for pos, color in reversed(self.clipboard): if not self.isInSelection(pos): self.delMaterial(pos) if pos < self.selectionbegin: iminus -= 1 self.setSelection(self.selectionbegin - iminus, self.selectionend - iminus) self.cutaction = None self.clipboard = None self.valueChanged.emit() self.menuselection = None self.updateGL() def loadtexture(self): self.edittexture(self.menuselection) def edittexture(self, i, initialfile=None): fname, ffilter = QFileDialog.getOpenFileName( self, "Texture Selection", self.lastTextureDir if initialfile is None else initialfile, "All files (*.*)") if len(fname) == 0: return self.lastTextureDir = os.path.dirname(str(fname)) format = QImageReader.imageFormat(fname) if len(format) == 0: QMessageBox.warning(self, "Format error!", "Format not supported !") self.edittexture(i, initialfile) else: self.setMaterial(i, ImageTexture(str(fname))) self.valueChanged.emit() def removematerial(self): if not self.menuselection is None and self.nbMaterial( ) > self.menuselection: if not self.selectionend is None: for i in self.getSelection(): self.delMaterial(self.selectionbegin) self.selectionbegin = None self.selectionend = None else: self.delMaterial(self.menuselection) self.valueChanged.emit() def interpolatematerial(self): if not self.selectionend is None: beg = self.selectionbegin end = self.selectionend self.showMessage( "Interpolate colors from " + str(beg) + " to " + str(end), 2000) deltaselection = end - beg ratio = 1 / float(deltaselection) fmat = self.getMaterial(beg) lmat = self.getMaterial(end) iratio = 0 for i in range(beg + 1, end): iratio += ratio self.setMaterial(i, fmat.interpolate(lmat, iratio)) self.selectionbegin, self.selectionend = None, None self.valueChanged.emit() def dragEnterEvent(self, event): event.acceptProposedAction() def dropEvent(self, event): if event.mimeData().hasUrls(): self.fileDropEvent( self.selectedColor(event.pos().x(), event.pos().y()), str(event.mimeData().urls()[0].toLocalFile())) def fileDropEvent(self, pos, fname): from openalea.lpy.cpfgcompat.data_import import import_colormap, import_materialmap base, ext = os.path.splitext(fname) if ext == '.map' or ext == '.mat': if ext == '.map': newcolors = import_colormap(fname) else: newcolors = import_materialmap(fname) if len(newcolors) > 0: self.setMaterials(newcolors) else: QMessageBox.warning( self, 'Data Error', 'Read no data from file "' + os.path.basename(fname) + '"') else: format = QImageReader.imageFormat(fname) if len(format) != 0: self.setMaterial(pos, ImageTexture(str(fname))) self.valueChanged.emit()
class ItemSlider(QWidget): valueChanged = pyqtSignal('PyQt_PyObject') def __init__(self, orientation, parent, item): QWidget.__init__(self, parent) self.setFocusPolicy(Qt.StrongFocus) self.setMinimumHeight(20) horizontalLayout = QHBoxLayout(self) horizontalLayout.setContentsMargins(0, 0, 0, 0) self.label = QLabel(self) horizontalLayout.addWidget(self.label) self.slider = QSlider(orientation, self) horizontalLayout.addWidget(self.slider) self.item = item scalar = item.scalar self.isfloat = scalar.isFloat() if item.scalar.isFloat(): self.spinBox = QDoubleSpinBox(self) self.spinBox.setSingleStep(0.1**scalar.decimals) else: self.spinBox = QSpinBox(self) self.spinBox.setMinimumHeight(20) horizontalLayout.addWidget(self.spinBox) self.spinBox.hide() #self.slider.hide() self.chgButton = QPushButton('O', self) self.chgButton.setMaximumWidth(15) self.chgButton.setMinimumWidth(15) horizontalLayout.addWidget(self.chgButton) self.setRange(scalar.minvalue, scalar.maxvalue) self.label.setMinimumWidth(self.labelwidth) self.setValue(scalar.value) self.locked = False if self.isfloat: self.slider.valueChanged.connect( self.updateInt2FloatItem ) # QObject.connect(self.slider,SIGNAL('valueChanged(int)'),self.updateInt2FloatItem) self.spinBox.valueChanged.connect( self.updateItem ) # QObject.connect(self.spinBox,SIGNAL('valueChanged(double)'),self.updateItem) else: self.slider.valueChanged.connect( self.updateItem ) # QObject.connect(self.slider,SIGNAL('valueChanged(int)'),self.updateItem) self.spinBox.valueChanged.connect( self.updateItem ) # QObject.connect(self.spinBox,SIGNAL('valueChanged(int)'),self.updateItem) self.chgButton.pressed.connect( self.changeEditor ) # QObject.connect(self.chgButton,SIGNAL('pressed()'),self.changeEditor) def updateInt2FloatItem(self, value): a = 10.**self.item.scalar.decimals self.updateItem(value / a) def updateItem(self, value): if self.item.scalar.value != value and not self.locked: self.locked = True self.item.scalar.value = value self.setValue(value) self.item.setText(str(value)) self.label.setMinimumWidth(self.labelwidth) self.valueChanged.emit( self.item.scalar ) # self.emit(SIGNAL('valueChanged(PyQt_PyObject)'),self.item.scalar) # AUTO SIGNAL TRANSLATION self.locked = False def setRange(self, minv, maxv): if self.isfloat: a = 10**self.item.scalar.decimals self.labelwidth = self.fontMetrics().width(' ' + str(int(a * maxv)) + '. ') self.slider.setRange(int(minv * a), int(maxv * a)) else: self.slider.setRange(minv, maxv) self.labelwidth = self.fontMetrics().width(' ' + str(maxv) + ' ') self.spinBox.setRange(minv, maxv) self.label.setText(' ' * (2 + len(str(maxv)))) def setValue(self, value): if self.isfloat: a = 10**self.item.scalar.decimals nv = int(value * a) if self.slider.value() != nv: self.slider.setValue(value * a) else: if self.slider.value() != value: self.slider.setValue(value) if self.spinBox.value() != value: self.spinBox.setValue(value) def changeEditor(self): if self.spinBox.isHidden(): self.slider.hide() self.label.hide() self.spinBox.show() self.spinBox.move(0, 0) else: self.slider.show() self.label.show() self.spinBox.hide()
class ScalarEditor(QTreeView): valueChanged = pyqtSignal() itemValueChanged = pyqtSignal('PyQt_PyObject') def __init__(self, parent): QTreeView.__init__(self, parent) self.scalars = [] self.scalarmap = {} self.initTable() self.scalarDelegate = ScalarEditorDelegate(self) self.setItemDelegateForColumn(1, self.scalarDelegate) self.createContextMenu() self.metaIntEdit = ScalarDialog(self) self.metaFloatEdit = FloatScalarDialog(self) self.setItemsExpandable(False) self.setIndentation(0) def initTable(self): self.scalarModel = MyItemModel(0, 1, self.scalarmap) self.scalarModel.itemChanged.connect( self.internalItemChanged ) # QObject.connect(self.scalarModel,SIGNAL('itemChanged(QStandardItem*)'),self.internalItemChanged) self.scalarModel.moveRequest.connect( self.moveItem ) # QObject.connect(self.scalarModel,SIGNAL('moveRequest(int,int)'),self.moveItem) self.scalarModel.setHorizontalHeaderLabels(["Parameter", "Value"]) self.setModel(self.scalarModel) def contextMenuEvent(self, event): items = self.selection() self.deleteAction.setEnabled(len(items) > 0) self.editAction.setEnabled( len(items) == 1 and not (self.scalars[items[0]].isCategory() or self.scalars[items[0]].isBool())) self.menu.exec_(event.globalPos()) def createContextMenu(self): self.menu = QMenu("Scalar Edit", self) self.menu.addAction("New Integer", self.newScalar) self.menu.addAction("New Float", self.newFloatScalar) self.menu.addAction("New Boolean", self.newBoolScalar) self.menu.addSeparator() self.menu.addAction("New Category", self.newCategoryScalar) self.menu.addSeparator() self.deleteAction = self.menu.addAction("Delete", self.deleteScalars) self.editAction = self.menu.addAction("Edit", self.editMetaScalar) def selection(self): items = list(set([i.row() for i in self.selectedIndexes()])) items.sort(key=lambda x: -x) return items def deleteScalars(self): for i in self.selection(): self.scalarModel.removeRow(i) del self.scalars[i] self.valueChanged.emit() def editMetaScalar(self): item = self.selection()[0] v = self.scalars[item] sc = self.visualEditMetaScalar(v) if sc and v != sc: v.importValue(sc) v.si_name.setText(v.name) v.si_value.setText(str(v.value)) self.itemValueChanged.emit( v ) # self.emit(SIGNAL('itemValueChanged(PyQt_PyObject)'),v) # AUTO SIGNAL TRANSLATION self.valueChanged.emit() def visualEditMetaScalar(self, scalar): metaEdit = self.metaIntEdit if scalar.isFloat(): metaEdit = self.metaFloatEdit metaEdit.setScalar(scalar) res = metaEdit.exec_() if res: return metaEdit.getScalar() def getItems(self, scalar): si_name = QStandardItem(scalar.name) si_name.setEditable(True) #si_name.setData(scalar) si_name.scalar = scalar si_name.nameEditor = True if scalar.isCategory(): b = QBrush(QColor(255, 255, 255)) si_name.setForeground(b) b = QBrush(QColor(0, 0, 0)) si_name.setBackground(b) return [si_name] si_value = QStandardItem() si_value.setEditable(False) si_value.setBackground(b) elif scalar.isBool(): si_value = QStandardItem() si_value.setCheckable(True) si_value.setCheckState( Qt.Checked if scalar.value else Qt.Unchecked) si_value.stdEditor = True else: si_value = QStandardItem(str(scalar.value)) si_value.scalar = scalar scalar.si_name = si_name scalar.si_value = si_value self.scalarmap[scalar.name] = (scalar, si_name, si_value) return [si_name, si_value] def newScalar(self): s = self.visualEditMetaScalar(IntegerScalar('default_scalar')) if s: self.scalars.append(s) self.scalarModel.appendRow(self.getItems(s)) self.internalValueChanged(s) def newFloatScalar(self): s = self.visualEditMetaScalar(FloatScalar('default_scalar')) if s: self.scalars.append(s) self.scalarModel.appendRow(self.getItems(s)) self.internalValueChanged(s) def newBoolScalar(self): s = BoolScalar('default_bool', True) self.scalars.append(s) self.scalarModel.appendRow(self.getItems(s)) self.internalValueChanged(s) def newCategoryScalar(self): s = CategoryScalar('new category') self.scalars.append(s) ri = self.scalarModel.indexFromItem( self.scalarModel.invisibleRootItem()) self.scalarModel.appendRow(self.getItems(s)) self.setFirstColumnSpanned(len(self.scalars) - 1, ri, True) self.internalValueChanged(s) def setScalars(self, values): self.scalars = values self.replotScalars() def getScalars(self): return self.scalars def replotScalars(self): self.initTable() ri = self.scalarModel.indexFromItem( self.scalarModel.invisibleRootItem()) for i, sc in enumerate(self.scalars): self.scalarModel.appendRow(self.getItems(sc)) if sc.isCategory(): self.setFirstColumnSpanned(i, ri, True) def internalValueChanged(self, scalar): self.itemValueChanged.emit( scalar ) # self.emit(SIGNAL('itemValueChanged(PyQt_PyObject)'),scalar) # AUTO SIGNAL TRANSLATION self.valueChanged.emit() def internalItemChanged(self, item): if hasattr(item, 'nameEditor'): item.scalar.name = str(item.text()) self.valueChanged.emit() elif hasattr(item, 'stdEditor'): item.scalar.value = item.checkState() == Qt.Checked self.valueChanged.emit() def moveItem(self, r0, r1): item = self.scalars.pop(r0) if r1 == -1: self.scalars.append(item) else: self.scalars.insert(r1, item) self.replotScalars() self.valueChanged.emit()
class NurbsObjectEditor(QtWidgets.QWidget): valueChanged = pyqtSignal() manipulated = pyqtSignal() def __init__(self, parent, dimension=2): QtWidgets.QWidget.__init__(self, parent) self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setContentsMargins(2, 0, 11, 2) self.view = DimensionViewClass[dimension](self) self.view.valueChanged.connect(self.propagate_valuechanged) self.view.manipulated.connect(self.propagate_manipulated) self.stridenames = [ n for n in ['u', 'v', 'w'] if hasattr(self.view, 'set' + n.upper() + 'Stride') ] dim = len(self.stridenames) maxrow = 1 + 2 * dim self.gridLayout.addWidget(self.view, 0, 0, 2, maxrow - 1) self.view.selectionChanged.connect(self.selectionEvent) self.weightSpinBox = QtWidgets.QDoubleSpinBox(self) self.weightSpinBox.setMinimum(-100) self.weightSpinBox.setMaximum(100) self.weightSpinBox.valueChanged.connect(self.setWeigthToSelection) self.weightSpinBox.setEnabled(False) self.gridLayout.addWidget(self.weightSpinBox, 0, maxrow, 1, 1) self.weigthSlider = QtWidgets.QSlider(self) self.weigthSlider.setMinimum(-200) self.weigthSlider.setMaximum(200) self.weigthSlider.setOrientation(QtCore.Qt.Vertical) self.weigthSlider.setValue(0) self.weigthSlider.sliderPressed.connect(self.weigthSliderPressed) self.weigthSlider.sliderReleased.connect(self.weigthSliderReleased) self.weigthSlider.sliderMoved.connect(self.setWeigthRatioToSelection) self.weigthSlider.setEnabled(False) self.gridLayout.addWidget(self.weigthSlider, 1, maxrow, 1, 1) label = QtWidgets.QLabel(self) label.setText("Discretization") self.gridLayout.addWidget(label, 2, 0, 1, 1) self.sliders = [] for i, stridename in enumerate(self.stridenames): strideSlider = QtWidgets.QSlider(self) strideSlider.setMinimum(2) strideSlider.setMaximum(50) strideSlider.setOrientation(QtCore.Qt.Horizontal) strideSlider.setValue( getattr(self.view.nurbsObject, stridename + 'stride')) strideSlider.valueChanged.connect( getattr(self.view, 'set' + stridename.upper() + 'Stride')) self.sliders.append(strideSlider) self.gridLayout.addWidget(strideSlider, 2, 1 + 2 * i, 1, 1) spacerItem = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) self.gridLayout.addItem(spacerItem, 2, 1 + 2 * i + 1, 1, 1) def selectionEvent(self): active = (len(self.view.getSelection()) != 0) self.weightSpinBox.setEnabled(active) self.weigthSlider.setEnabled(active) if active: self.weightSpinBox.setValue( sum([ p.position_setter.weight for p in self.view.selectionManipulator.selection ]) / len(self.view.selectionManipulator.selection)) def weigthSliderPressed(self): if self.view.selectionManipulator: self.cweight = sum([ p.position_setter.weight for p in self.view.selectionManipulator.selection ]) / len(self.view.selectionManipulator.selection) else: self.cweight = 1 def weigthSliderReleased(self): self.weigthSlider.setValue(0) def setWeigthRatioToSelection(self, value): ratio = 1 + (value / 100.) self.weightSpinBox.setValue(self.cweight * ratio) self.view.setWeigthToSelection(self.cweight * ratio) def setWeigthToSelection(self, value): self.view.setWeigthToSelection(value) def setNurbsObject(self, nurbsObject): self.view.setNurbsObject(nurbsObject) for slider, stridename in zip(self.sliders, self.stridenames): slider.setValue( getattr(self.view.nurbsObject, stridename + 'stride')) def getNurbsObject(self): return self.view.getNurbsObject() def propagate_valuechanged(self): self.valueChanged.emit() def propagate_manipulated(self): self.manipulated.emit() def rescaleObject(self): """ return a default nurbs patch """ Dialog = QtWidgets.QDialog(self) gridLayout = QtWidgets.QGridLayout(Dialog) Dialog.values = [] for i, axis in enumerate(['X', 'Y', 'Z']): sectionLabel = QtWidgets.QLabel(Dialog) sectionLabel.setText(axis) gridLayout.addWidget(sectionLabel, i, 0) spacer = QtWidgets.QSpacerItem(1, 10) gridLayout.addItem(spacer, i, 1) valuebox = QtWidgets.QDoubleSpinBox(Dialog) valuebox.setDecimals(3) valuebox.setMinimum(0.001) valuebox.setMaximum(9999999999) valuebox.setValue(1) Dialog.values.append(valuebox) gridLayout.addWidget(valuebox, i, 2) buttonBox = QtWidgets.QDialogButtonBox(Dialog) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) buttonBox.accepted.connect(Dialog.accept) buttonBox.rejected.connect(Dialog.reject) gridLayout.addWidget(buttonBox, 3, 0, 1, 2) if Dialog.exec_(): scale = Vector3([v.value() for v in Dialog.values]) self.view.applyScaling(scale) def createCurstomDefaultObject(self): """ return a default nurbs patch """ import inspect Dialog = QtWidgets.QDialog(self) gridLayout = QtWidgets.QGridLayout(Dialog) Dialog.values = [] pname = inspect.getfullargspec(self.view.newDefaultNurbsObject).args for i, axis in enumerate(pname): sectionLabel = QtWidgets.QLabel(Dialog) sectionLabel.setText(axis) gridLayout.addWidget(sectionLabel, i, 0) spacer = QtWidgets.QSpacerItem(1, 10) gridLayout.addItem(spacer, i, 1) valuebox = QtWidgets.QSpinBox(Dialog) valuebox.setMinimum(2) valuebox.setMaximum(1000) valuebox.setValue(4) Dialog.values.append(valuebox) gridLayout.addWidget(valuebox, i, 2) buttonBox = QtWidgets.QDialogButtonBox(Dialog) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) buttonBox.accepted.connect(Dialog.accept) buttonBox.rejected.connect(Dialog.reject) gridLayout.addWidget(buttonBox, 3, 0, 1, 2) if Dialog.exec_(): nbpoints = [v.value() for v in Dialog.values] self.setNurbsObject(self.view.newDefaultNurbsObject(*nbpoints)) def createDefaultObject(self, *dims): self.setNurbsObject(self.view.newDefaultNurbsObject(*dims))
class AbstractNurbsObjectEditorView(QGLViewer): """ The class NurbsPatchEditor is the viewer of the scene, it contains all the informations about the NurbsPatch, that's why a getter and a setter have been created, the NubrsPatch is defined by a 2 dimensional Array of 3d Vectors""" Edit, Rotate = list(range(2)) valueChanged = pyqtSignal() manipulated = pyqtSignal() selectionChanged = pyqtSignal() def __init__(self, parent): """ Constructor :param parent: the parent widget """ QGLViewer.__init__(self, parent) self.setStateFileName('.nurbsobjecteditor.xml') self.mode = AbstractNurbsObjectEditorView.Edit # the nurbs patch self.nurbsObject = None # matrix of ctrl point manipulator self.ctrlPointManipulators = {} # shape and material to display the patch self.nurbsShape = Shape() self.defaultMaterial = Material((30, 200, 30), 1, transparency=0.5) self.nurbsShape.appearance = self.defaultMaterial # plantgl basic object to display nurbs self.discretizer = Discretizer() self.glrenderer = GLRenderer(self.discretizer) self.glrenderer.renderingMode = GLRenderer.Dynamic self.ctrlrenderer = GLCtrlPointRenderer(self.discretizer) self.bboxcomputer = BBoxComputer(self.discretizer) self.ctrlPointDisplay = None # Current selection self.selectionManipulator = None self.focus = None # rectangular selection self.selectionRect = False # for rectangular selection (with control) self.__rectangle = QRect() self.__rectangleInit = None self._drawNurbsObject = True self._drawGrid = True #creation of a default NurbsPatch self.setNurbsObject(self.newDefaultNurbsObject()) def getSelection(self): if self.selectionManipulator: return self.selectionManipulator.selection else: return [] def clear(self): """ clear current edition """ self.ctrlPointManipulators = {} self.clearSelectionManipulator() self.resetDisplay() def getCtrlPoints(self): return self.nurbsObject.ctrlPointMatrix def getCtrlPointsDim(self): return self.getCtrlPoints().sizes() def getCtrlPointManipulators(self): return self.ctrlPointManipulators def getBounds(self): """ Get the Bounding Box of the scene: return the minpos and maxpos that can be used by setSceneBoundingBox function""" self.nurbsObject.apply(self.bboxcomputer) res = self.bboxcomputer.result return Vec(*res.lowerLeftCorner), Vec(*res.upperRightCorner) def __propagate_valuechanged__(self, pid=None): """ emit a SIGNAL every time a value changed """ self.setSceneBoundingBox(*self.getBounds()) self.valueChanged.emit() self.resetDisplay() def resetDisplay(self, pid=None): self.ctrlPointDisplay = None def init(self): """ init function """ self.setSceneBoundingBox(*self.getBounds()) self.restoreStateFromFile() # init mouse interaction self.mode = AbstractNurbsObjectEditorView.Edit self.setInteractionMode(False) self.setMouseBindingDescription(Qt.ShiftModifier, Qt.LeftButton, "Rectangular selection") self.setMouseBindingDescription(Qt.NoModifier, Qt.LeftButton, "Camera/Control Points manipulation") self.setMouseBindingDescription( Qt.NoModifier, Qt.LeftButton, "When double clicking on a line, create a new line", True) self.setKeyDescription(Qt.Key_K, "Toggles control point wire display") self.setKeyDescription(Qt.Key_G, "Toggles patch display") def setFocus(self, point): """ Set focus to given control point """ if self.focus: self.focus.hasFocus = False self.focus = point if self.focus: point.hasFocus = True def clearSelectionManipulator(self): """ clear the selection manipulator """ if not self.selectionManipulator is None: self.selectionManipulator.clear() self.selectionManipulator.valueChanged.disconnect( self.__propagate_valuechanged__ ) # QObject.disconnect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) self.selectionManipulator = None self.resetDisplay() def createSelectionManipulator(self): """ ensure that the selection manipulator is existing and valid """ if not self.selectionManipulator: self.selectionManipulator = SelectionManipulator() self.selectionManipulator.valueChanged.connect( self.__propagate_valuechanged__ ) # QObject.connect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) self.resetDisplay() def mousePressEvent(self, event): """ Check for eventual operations the user asks: shift start rectangular selection else check for which point is selected """ #Rectangular selection if event.modifiers() == Qt.ShiftModifier: if event.button() == Qt.LeftButton: self.__rectangle = QRect(event.pos(), event.pos()) self.__rectangleInit = event.pos() self.selectionRect = True elif event.button() == Qt.RightButton: self.clearSelectionManipulator() self.selectionChanged.emit() self.updateGL() else: pointSelection = False if not self.selectionManipulator is None: pointSelection = self.selectionManipulator.checkIfAxisGrabsMouse( event, self) if not pointSelection: for cCtrlPoint in self.getCtrlPointManipulators().values(): cCtrlPoint.checkIfGrabsMouse(event.x(), event.y(), self.camera()) if cCtrlPoint.grabsMouse(): pointSelection = True self.setFocus(cCtrlPoint) if self.selectionManipulator and cCtrlPoint.selected: self.setManipulatedFrame(self.selectionManipulator) else: self.setManipulatedFrame(cCtrlPoint) break self.setInteractionMode(pointSelection) self.updateGL() QGLViewer.mousePressEvent(self, event) def setInteractionMode(self, frame=True): if frame: if self.mode != AbstractNurbsObjectEditorView.Edit: self.mode = AbstractNurbsObjectEditorView.Edit self.setMouseBinding(Qt.NoModifier, Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.NoModifier, Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.ControlModifier, Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.ROTATE) self.setMouseBinding(Qt.ControlModifier, Qt.RightButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier, Qt.MiddleButton, QGLViewer.CAMERA, QGLViewer.ZOOM) else: if self.mode != AbstractNurbsObjectEditorView.Rotate: self.mode = AbstractNurbsObjectEditorView.Rotate self.setMouseBinding(Qt.ControlModifier, Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier, Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.NoModifier, Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.ROTATE) self.setMouseBinding(Qt.NoModifier, Qt.RightButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.NoModifier, Qt.MiddleButton, QGLViewer.CAMERA, QGLViewer.ZOOM) def mouseDoubleClickEvent(self, event): """ mouseDoubleClickEvent: add a control point to the selection double clicking on it, empty the selection double clicking elsewhere """ if event.modifiers() & Qt.ShiftModifier: selection = False #Doubleclick on a control point add/delete it from the selection array# for cCtrlPoint in self.getCtrlPointManipulators().values(): cCtrlPoint.checkIfGrabsMouse(event.x(), event.y(), self.camera()) if cCtrlPoint.grabsMouse(): cCtrlPoint.selected = not cCtrlPoint.selected selection = True self.createSelectionManipulator() self.selectionManipulator.toogleSelection(cCtrlPoint) #Double click with shift anywhere but on a control point -> empty selection # if not selection: self.clearSelectionManipulator() self.selectionChanged.emit() else: ## Adding a column on the NurbsPatch# """ when double click on a line, the line is doubled """ self.select( event.pos() ) # select call drwWithName that render line of control points with ids selection = self.selectedName() if selection >= 0: selectedPoints = self.addElement(selection) if selectedPoints: self.createSelectionManipulator() for index in selectedPoints: self.selectionManipulator.add( self.ctrlPointManipulators[index]) else: self.clearSelectionManipulator() else: self.clearSelectionManipulator() self.selectionChanged.emit() def mouseMoveEvent(self, event): """ mouseMoveEvent """ #rectangular selection if self.selectionRect: self.__rectangle = QRect(self.__rectangleInit, event.pos()).normalized() self.updateGL() # by default camera or manipulated frame is manipulated QGLViewer.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): """On mouse release, we release every grabbed objects""" QGLViewer.mouseReleaseEvent(self, event) # clear manipulated object self.setManipulatedFrame(None) self.setFocus(None) if self.selectionManipulator: self.selectionManipulator.clearFocus() # end of rectangular selection : if self.selectionRect: self.selectionRect = False # Possibly swap left/right and top/bottom to make rectangle_ valid. self.__rectangle = QRect(self.__rectangleInit, event.pos()).normalized() # make rectangle with a minimal size if self.__rectangle.width() < 10 or self.__rectangle.height() < 10: self.__rectangle = QRect(event.pos().x() - 5, event.pos().y() - 5, 10, 10) self.selectionFromRect(self.__rectangle) self.setInteractionMode(False) self.updateGL() def selectionFromRect(self, rect): """ check if control point projections are in the given rectangle. If yes, put point in selection """ selectionOccur = False for cCtrlPoint in self.getCtrlPointManipulators().values(): point = self.camera().projectedCoordinatesOf(cCtrlPoint.position()) if (self.__rectangle.contains(int(point.x), int(point.y))): selectionOccur = True if not self.selectionManipulator: self.selectionManipulator = SelectionManipulator() self.selectionManipulator.valueChanged.connect( self.__propagate_valuechanged__ ) # QObject.connect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) self.selectionManipulator.toogleSelection(cCtrlPoint) if self.selectionManipulator and self.selectionManipulator.empty(): self.clearSelectionManipulator() self.ctrlPointDisplay = None if selectionOccur: self.selectionChanged.emit() def keyPressEvent(self, e): modifiers = e.modifiers() # A simple switch on e->key() is not sufficient if we want to take state key into account. # With a switch, it would have been impossible to separate 'F' from 'CTRL+F'. # That's why we use imbricated if...else and a "handled" boolean. if ((e.key() == Qt.Key_K) and (modifiers == Qt.NoModifier)): self._drawGrid = not self._drawGrid self.updateGL() elif (e.key() == Qt.Key_G) and (modifiers == Qt.NoModifier): self._drawNurbsObject = not self._drawNurbsObject self.updateGL() else: QGLViewer.keyPressEvent(self, e) def addElement(self, selection): nbcolumn = self.getCtrlPoints().getColumnNb() minp, maxp = self.getBounds() diff = norm(maxp - minp) / 20 if selection >= nbcolumn: row = selection - nbcolumn newpatch = addRowToPatch(self.nurbsObject, row, Vector4(diff, 0)) selectedPoints = [(row, i) for i in range(nbcolumn)] else: newpatch = addColumnToPatch(self.nurbsObject, selection, Vector4(diff, 0)) selectedPoints = [(i, selection) for i in range(self.getCtrlPoints().getRowNb())] self.setNurbsObject(newpatch) return selectedPoints def draw(self): """ Draw function : Control points of the nurbsPatch are represented by spheres. If some of them are selected, a medium axis will be drawn by lines and little spheres, allowing us to grab one of them to perform a constrained move """ ogl.glDisable(ogl.GL_LIGHTING) ogl.glPolygonMode(ogl.GL_FRONT_AND_BACK, ogl.GL_LINE) # draw the wire of the patch ogl.glLineWidth(1) if self._drawNurbsObject: self.drawNurbsObjectWire() if self._drawGrid: self.drawCtrlWire() # patch and control point shpere rendering ogl.glPolygonMode(ogl.GL_FRONT_AND_BACK, ogl.GL_FILL) ogl.glEnable(ogl.GL_LIGHTING) ogl.glLightModeli(ogl.GL_LIGHT_MODEL_TWO_SIDE, ogl.GL_TRUE) ogl.glEnable(ogl.GL_BLEND) ogl.glBlendFunc(ogl.GL_SRC_ALPHA, ogl.GL_ONE_MINUS_SRC_ALPHA) if self._drawNurbsObject: self.drawNurbsObject() self.drawCtrlPoints() ogl.glDisable(ogl.GL_BLEND) if self.selectionRect: # Draw rectangular selection self.startScreenCoordinatesSystem() # use screen coordinates r = self.__rectangle rect = Shape( Polyline2D([(r.x(), r.y()), (r.x() + r.width(), r.y()), (r.x() + r.width(), r.y() + r.height()), (r.x(), r.y() + r.height()), (r.x(), r.y())]), Material((200, 200, 200), 1)) ogl.glEnable(ogl.GL_LINE_STIPPLE) ogl.glLineStipple(1, 0x0FFF) ogl.glLineWidth(2) rect.apply(self.glrenderer) ogl.glDisable(ogl.GL_LINE_STIPPLE) self.stopScreenCoordinatesSystem() ogl.glLineWidth(1) def drawCtrlPoints(self): if self.ctrlPointDisplay is None: scradius = self.sceneRadius() pointsize = scradius / 30. shapes = [] #define the nurbsShape and the control points geometry for ctrlPoint in self.getCtrlPointManipulators().values(): shapes.append(ctrlPoint.representation(pointsize)) # draw the frame of multiple selection if self.selectionManipulator: shapes += self.selectionManipulator.representation(pointsize * 1.2) self.ctrlPointDisplay = Scene(shapes) # control point shpere rendering self.ctrlPointDisplay.apply(self.glrenderer) def drawCtrlWire(self): # ctrl line rendering Material((255, 255, 255), 1).apply(self.glrenderer) self.nurbsObject.apply(self.ctrlrenderer) def drawNurbsObject(self): sc = Scene([self.nurbsShape]) # patch rendering sc.apply(self.glrenderer) def drawNurbsObjectWire(self): shwire = Shape(self.nurbsObject, Material((120, 255, 0), 0.8)) shwire.apply(self.glrenderer) def drawWithNames(self): """ draw control lines with names """ pointmatrix = self.getCtrlPoints() lines = [] m = Material() maxcolumn = pointmatrix.getColumnNb() for colid in range(maxcolumn): lines.append( Shape( Polyline(Point4Array( pointmatrix.getColumn(colid)).project(), width=3), m, colid)) for rowid in range(pointmatrix.getRowNb()): lines.append( Shape( Polyline(Point4Array(pointmatrix.getRow(rowid)).project(), width=3), m, maxcolumn + rowid)) for sh in lines: ogl.glPushName(sh.id) sh.apply(self.glrenderer) ogl.glPopName() def getNurbsObject(self): """ return the edited nurbs patch """ return self.nurbsObject def setNurbsObject(self, nurbsObject): """ set the nurbs patch to edit """ self.clear() self.nurbsObject = nurbsObject self.setNurbsObjectView() self.setControlPointManipulators() def setControlPointManipulators(self): import itertools dims = list(map(float, self.getCtrlPointsDim())) def color(idx): try: r = 30 + int(220 * idx[0] / dims[0]) g = 30 + int(220 * idx[1] / dims[1]) if len(dims) > 1 else 250 b = 30 + int(220 * idx[2] / dims[2]) if len(dims) > 2 else 250 except: r = 250 g = 30 + int(220 * idx / dims[0]) b = 250 return (r, g, b) ctrlpoints = self.getCtrlPoints() self.ctrlPointManipulators = {} dims = self.getCtrlPointsDim() if len(dims) == 1: idxiter = list(range(dims[0])) else: idxiter = itertools.product(*[list(range(d)) for d in dims]) for pid, index in enumerate(idxiter): ctrlPoint = CtrlPoint(ctrlpoints[index], Pos4Setter(self.getCtrlPoints(), index, ctrlpoints[index].w), color=color(index), id=pid) ctrlPoint.setCallBack(self.__propagate_valuechanged__) self.ctrlPointManipulators[index] = ctrlPoint self.setSceneBoundingBox(*self.getBounds()) def setNurbsObjectView(self): self.nurbsShape.geometry = self.nurbsObject def helpString(self): return helpstr def closeEvent(self, event): """close the windows properly""" helpwidget = self.helpWidget() if helpwidget and helpwidget.isVisible(): helpwidget.hide() QGLViewer.closeEvent(self, event) def setUStride(self, value): self.nurbsObject.ustride = value self.valueChanged.emit() self.updateGL() def setWeigthToSelection(self, value): if self.selectionManipulator: try: if len(value) > 0: for v, p in zip(value, self.selectionManipulator.selection): p.position_setter.weight = v p.__propagate_position_change__() except: if abs(value) < 1e-3: return for p in self.selectionManipulator.selection: p.position_setter.weight = value p.__propagate_position_change__() self.updateGL()
class LpyTabBar(QTabBar): switchDocument = pyqtSignal(int,int) newDocumentRequest = pyqtSignal() def __init__(self,parent): QTabBar.__init__(self,parent) #self.setDrawBase(False) self.selection = None self.lpystudio = None self.initialtab = None self.inserted = set() def connectTo(self,lpystudio): self.lpystudio = lpystudio self.switchDocument.connect(lpystudio.switchDocuments) self.currentChanged.connect(lpystudio.changeDocument) self.newDocumentRequest.connect(lpystudio.newfile) def mousePressEvent(self,event): if event.button() == Qt.LeftButton: self.initialtab = self.tabAt(event.pos()) QTabBar.mousePressEvent(self,event) def mouseReleaseEvent(self,event): if event.button() == Qt.LeftButton: tabselect = self.tabAt(event.pos()) if tabselect != -1 and not self.initialtab is None: if tabselect != self.initialtab: self.switchDocument.emit(tabselect,self.initialtab) self.initialtab = None QTabBar.mousePressEvent(self,event) # def mouseMoveEvent(self,event): # tabselect = self.tabAt(event.pos()) # if tabselect != -1 and self.initialtab != None: # if tabselect != originaltab: # pass # #self.emit(SIGNAL("switchDocument"),tabselect,originaltab) # QTabBar.mouseMoveEvent(self,event) # def mouseDoubleClickEvent(self,event): # tabselect = self.tabAt(event.pos()) # if tabselect != -1 : # self.emit(SIGNAL("newDocumentRequest")) # QTabBar.mouseDoubleClickEvent(self,event) def contextMenuEvent(self,event): self.selection = self.tabAt(event.pos()) if self.selection != -1: menu = QMenu(self) action = menu.addAction('Close') action.triggered.connect(self.close) action = menu.addAction('Close all except this ') action.triggered.connect(self.closeAllExcept) menu.addSeparator() if self.lpystudio.simulations[self.selection].readonly: action = menu.addAction('Remove Readonly ') action.triggered.connect(self.removeReadOnly) else: action = menu.addAction('Set Readonly ') action.triggered.connect(self.setReadOnly) menu.addSeparator() action = menu.addAction('Copy filename ') action.triggered.connect(self.copyFilename) action = menu.addAction('Open folder') action.triggered.connect(self.openFolder) action = menu.addAction('Open terminal') action.triggered.connect(self.openTerminalAtFolder) fname = self.lpystudio.simulations[self.selection].fname if fname and svnmanip.hasSvnSupport() : if svnmanip.isSvnFile(fname): menu.addSeparator() status = svnmanip.svnFileTextStatus(fname) if status != svnmanip.added: action = menu.addAction('SVN Update') action.triggered.connect(self.svnUpdate) if status in [svnmanip.added,svnmanip.modified]: action = menu.addAction('SVN Commit') action.triggered.connect(self.svnCommit) if status != svnmanip.normal: action = menu.addAction('SVN Revert') action.triggered.connect(self.svnRevert) if status != svnmanip.added: menu.addSeparator() action = menu.addAction('Is Up-to-date ?') action.triggered.connect(self.svnIsUpToDate) elif svnmanip.isSvnFile(os.path.dirname(fname)): menu.addSeparator() action = menu.addAction('SVN Add') action.triggered.connect(self.svnAdd) menu.exec_(event.globalPos()) def openFolder(self): import os, sys fname = os.path.abspath(self.lpystudio.simulations[self.selection].fname) mdir = os.path.dirname(fname) if sys.platform == 'win32': import subprocess subprocess.call('explorer /select,"'+fname+'"') elif sys.platform == 'linux2': os.system('xdg-open "'+mdir+'"') else: os.system('open "'+mdir+'"') def openTerminalAtFolder(self): import os, sys fname = os.path.abspath(self.lpystudio.simulations[self.selection].fname) mdir = os.path.dirname(fname) if sys.platform == 'win32': import subprocess subprocess.call('CMD /K CD "'+mdir+'"') elif sys.platform == 'linux2': os.system('gnome-terminal --working-directory "'+mdir+'"') elif sys.platform == 'darwin': os.system('open -a"Terminal" "'+mdir+'"') def close(self): self.lpystudio.closeDocument(self.selection) def closeAllExcept(self): self.lpystudio.closeAllExcept(self.selection) def copyFilename(self): QApplication.clipboard().setText(self.lpystudio.simulations[self.selection].fname) def removeReadOnly(self): self.lpystudio.simulations[self.selection].removeReadOnly() def setReadOnly(self): self.lpystudio.simulations[self.selection].setReadOnly() def svnUpdate(self): self.lpystudio.simulations[self.selection].svnUpdate() def svnIsUpToDate(self): self.lpystudio.simulations[self.selection].svnIsUpToDate() def svnAdd(self): self.lpystudio.simulations[self.selection].svnAdd() def svnRevert(self): self.lpystudio.simulations[self.selection].svnRevert() def svnCommit(self): self.lpystudio.simulations[self.selection].svnCommit() def insertTab(self, index, val1, val2 = None): self.inserted.add(index) if val2 : QTabBar.insertTab(self, index, val1, val2) else : QTabBar.insertTab(self, index, val1)
class ComputationTaskManager(QObject): endTask = pyqtSignal('PyQt_PyObject') killedTask = pyqtSignal('PyQt_PyObject') def __init__(self): self.computationThread = None self.computationMutex = QMutex() self.with_thread = False def toggleUseThread(self): self.with_thread = not self.with_thread if not self.with_thread: self.computationMutex.unlock() def finalizeTask(self): ct = self.computationThread if not self.computationThread is None: try: self.computationThread.finalize() except ThreadTransferException as e: self.graberror((e.exc_type, e.exc_value, e.exc_traceback)) except: self.graberror() self.releaseCR() self.computationThread = None else: self.releaseCR() self.endTask.emit(ct) def abortTask(self): ct = self.computationThread self.computationThread = None self.releaseCR() self.endTask.emit(ct) self.clear() def killTask(self): ct = self.computationThread ct.kill() self.computationThread = None self.releaseCR() self.killedTask.emit(ct) self.clear() def registerTask(self, task): if self.computationThread is None: if self.with_thread: task.finished.connect(self.finalizeTask) if QT_VERSION <= 4: task.terminated.connect(self.abortTask) self.computationThread = task task.initialize() task.start() else: try: self.computationThread = task task.apply() except: self.graberror() self.computationThread = None self.releaseCR() self.endTask.emit(task) def acquireCR(self): """ acquire computation ressources """ if not self.computationMutex.tryLock(): self.taskRunningEvent() else: self.acquireEvent() self.isRunning() def taskRunningEvent(self): raise Exception('A task is already running') pass def isRunning(self): if self.computationMutex.tryLock(): self.computationMutex.unlock() return False else: return True def releaseCR(self): """ release computation ressources """ self.computationMutex.tryLock() self.computationMutex.unlock() self.releaseEvent() def acquireEvent(self): pass def releaseEvent(self): pass def graberror(self, exc_info=None, displayDialog=True): """ grab error """ if exc_info is None: exc_info = sys.exc_info() tb.print_exception(*exc_info) self.lastexception = exc_info[1] errmsg = self.getErrorMessage(exc_info) self.errorEvent(exc_info, errmsg, displayDialog) self.endErrorEvent() #if displayDialog: # self.endErrorEvent(self.errorMessage(errmsg)) #else: # self.endErrorEvent(None) def getErrorMessage(self, exc_info): exception = exc_info[1] msg = str(exc_info[1]) if exc_info[0] == SyntaxError and len(msg) == 0: msg = exc_info[1].msg return str(exc_info[0].__name__) + ':' + str(msg) def errorMessage(self, msg): return QMessageBox.warning(self, "Exception", msg, QMessageBox.Ok) def errorEvent(self, exc_info, errmsg, displayDialog): pass def endErrorEvent(self): pass
class Curve2DEditor(QGLViewer): BLACK_THEME = { 'Curve': (255, 255, 255), 'BackGround': (51, 51, 51), 'Text': (255, 255, 255), 'CtrlCurve': (122, 122, 0), 'GridStrong': (102, 102, 102), 'GridFade': (51, 51, 51), 'Points': (250, 30, 30), 'FirstPoint': (250, 30, 250), 'SelectedPoint': (30, 250, 30), 'DisabledBackGround': (150, 150, 150) } WHITE_THEME = { 'Curve': (255, 0, 0), 'BackGround': (255, 255, 255), 'Text': (0, 0, 0), 'CtrlCurve': (25, 0, 25), 'GridStrong': (102, 102, 102), 'GridFade': (153, 153, 153), 'Points': (30, 250, 30), 'FirstPoint': (250, 30, 250), 'SelectedPoint': (30, 250, 30), 'DisabledBackGround': (150, 150, 150) } valueChanged = pyqtSignal() def __init__(self, parent, constraints=Curve2DConstraint()): QGLViewer.__init__(self, parent) self.selection = -1 self.setStateFileName('.curveeditor.xml') self.sphere = Sphere(radius=0.02) self.curveshape = Shape() self.setTheme() #Color theme self.pointsConstraints = constraints self.accessorType = { NurbsCurve2D: Nurbs2DAccessor, BezierCurve2D: Bezier2DAccessor, Polyline2D: Polyline2DAccessor, } self.setCurve(self.newDefaultCurve()) self.discretizer = Discretizer() self.renderer = GLRenderer(self.discretizer) self.renderer.renderingMode = GLRenderer.Dynamic self.ctrlrenderer = GLCtrlPointRenderer(self.discretizer) self.bgimage = False self.font = QFont() if not hasattr(self, 'updateGL'): self.updateGL = self.update def setTheme(self, theme=BLACK_THEME): self.curveMaterial = Material(theme['Curve'], 1) self.defaultColor = QColor(*theme['BackGround']) self.disabledBGColor = QColor(*theme['DisabledBackGround']) self.textColor = [v / 255. for v in theme['Text']] + [1.0] self.ctrlCurveColor = [v / 255. for v in theme['CtrlCurve']] + [0.0] self.gridColor = [v / 255. for v in theme['GridStrong']] + [0.0] self.gridColor2 = [v / 255. for v in theme['GridFade']] + [0.0] self.pointColor = Material(theme['Points'], 1) self.firstPointColor = Material(theme['FirstPoint'], 1) self.selectedPointColor = Material(theme['SelectedPoint'], 1) self.curveshape.appearance = self.curveMaterial def applyTheme(self, theme=BLACK_THEME): self.setTheme(theme) if self.isVisible(): self.createControlPointsRep() self.updateGL() def newDefaultCurve(self): return self.pointsConstraints.defaultCurve() def init(self): self.updateSceneDimension() #self.setHandlerKeyboardModifiers(QGLViewer.CAMERA, Qt.AltModifier) #self.setHandlerKeyboardModifiers(QGLViewer.FRAME, Qt.NoModifier) #self.setHandlerKeyboardModifiers(QGLViewer.CAMERA, Qt.ControlModifier) self.setMouseBinding(Qt.ControlModifier, Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier, Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.NoModifier, Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.NoModifier, Qt.RightButton, QGLViewer.CAMERA, QGLViewer.NO_MOUSE_ACTION) self.camera().setUpVector(Vec(0, 1, 0)) self.camera().setType(Camera.ORTHOGRAPHIC) self.camConstraint = WorldConstraint() self.camConstraint.setRotationConstraintType( AxisPlaneConstraint.FORBIDDEN) self.camera().frame().setConstraint(self.camConstraint) self.showEntireScene() self.setSelectRegionWidth(5) self.setSelectRegionHeight(5) self.manipulator = ManipulatedFrame() self.setManipulatedFrame(self.manipulator) self.manipulator.manipulated.connect( self.updatePoints ) # QObject.connect(self.manipulator,SIGNAL('manipulated()'),self.updatePoints) #self.setBackgroundImage("test.png") #self.openImage() # QQQ def getCurve(self): """ Get the edited curve """ return self.curveshape.geometry def updateSceneDimension(self): minp, maxp = self.curveAccessor.bounds() minp, maxp = Vector3(minp, 0), Vector3(maxp, 0) self.setSceneBoundingBox(Vec(*minp), Vec(*maxp)) def setCurve(self, curve): """ Set the edited curve """ self.curveshape.geometry = curve curve.width = 2 self.curveAccessor = self.accessorType[type(curve)](curve) self.pointsConstraints.checkInitialCurve(self.curveAccessor) self.createControlPointsRep() self.updateSceneDimension() if self.isVisible(): self.showEntireScene() def openImage(self): name, selection = QFileDialog.getOpenFileName( self, "Select an image", ".", "Images (*.png *.xpm *.jpg)") # In case of Cancel if not name: return self.setBackgroundImage(name) return def closeImage(self): self.bgimage = False def setBackgroundImage(self, imagefilename): # code taken from the backgroundImage.cpp file that is part of the QGLViewer library self.u_max = 1.0 self.v_max = 1.0 # load image img = QImage(imagefilename) if img.isNull(): qWarning("Unable to load file, unsupported file format") return # qWarning("Loading " + imagefilename + " " + str(img.width()) + "x" + str(img.height()) +" pixels") self.imageWidth = float(img.width()) self.imageHeight = float(img.height()) # QQQ qWarning(str(self.imageWidth) + " " + str(self.imageHeight)) # 1E-3 needed. Just try with width=128 and see ! newWidth = 1 << (int)(1 + log(img.width() - 1 + 1E-3) / log(2.0)) newHeight = 1 << (int)(1 + log(img.height() - 1 + 1E-3) / log(2.0)) self.u_max = img.width() / float(newWidth) self.v_max = img.height() / float(newHeight) if ((img.width() != newWidth) or (img.height() != newHeight)): #qWarning("Image size set to " + str(newWidth) + "x" + str(newHeight) + " pixels") img = img.copy(0, 0, newWidth, newHeight) glImg = QImage(QGLWidget.convertToGLFormat(img)) # flipped 32bit RGBA # Bind the img texture... glTexImage2D(GL_TEXTURE_2D, 0, 4, glImg.width(), glImg.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, glImg.bits().asstring(glImg.sizeInBytes())) # Another way to bind img texture: # if self.textureId: # self.deleteTexture(self.textureId) # self.textureId = None # self.textureId = self.bindTexture(img,GL_TEXTURE_2D,GL_RGBA) # if self.textureId : # glBindTexture(GL_TEXTURE_2D, self.textureId) self.bgimage = True def drawBackground(self): # code taken from the backgroundImage.cpp file that is part of the QGLViewer library # set texturing for background image glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) # Nice texture coordinate interpolation glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glDisable(GL_LIGHTING) glEnable(GL_TEXTURE_2D) glColor3f(1, 1, 1) # to draw texture in entire window: # self.startScreenCoordinatesSystem(True) # # Draws the background quad # glNormal3f(0.0, 0.0, 1.0) # glBegin(GL_QUADS) # glTexCoord2f(0.0, 1.0-self.v_max) # glVertex2i(0,0) # glTexCoord2f(0.0, 1.0) # glVertex2i(0,self.height()) # glTexCoord2f(self.u_max, 1.0) # glVertex2i(self.width(),self.height()) # glTexCoord2f(self.u_max, 1.0-self.v_max) # glVertex2i(self.width(),0) # glEnd() # self.stopScreenCoordinatesSystem() # Draws the quad in (0,0) to (1.0,imageHeight/imageWidth) self.startScreenCoordinatesSystem(True) centre_x = self.width() * 0.5 - self.imageWidth * 0.5 centre_y = self.height() * 0.5 - self.imageHeight * 0.5 glNormal3f(0.0, 0.0, 1.0) glBegin(GL_QUADS) glTexCoord2f(0.0, 1.0 - self.v_max) glVertex2f(centre_x, centre_y) glTexCoord2f(0.0, 1.0) glVertex2f(centre_x, centre_y + self.imageHeight) glTexCoord2f(self.u_max, 1.0) glVertex2f(centre_x + self.imageWidth, centre_y + self.imageHeight) glTexCoord2f(self.u_max, 1.0 - self.v_max) glVertex2f(centre_x + self.imageWidth, centre_y) glEnd() self.stopScreenCoordinatesSystem() # Depth clear is not absolutely needed. An other option would have been to draw the # QUAD with a 0.999 z value (z ranges in [0, 1[ with startScreenCoordinatesSystem()). glClear(GL_DEPTH_BUFFER_BIT) glDisable(GL_TEXTURE_2D) glEnable(GL_LIGHTING) def draw(self): if self.isEnabled(): if self.bgimage == False: self.setBackgroundColor(self.defaultColor) else: self.drawBackground() else: self.setBackgroundColor(self.disabledBGColor) self.start = self.pointOnEditionPlane(QPoint(0, self.height() - 1)) self.end = self.pointOnEditionPlane(QPoint(self.width() - 1, 0)) self.sphere.radius = (self.end[0] - self.start[0]) / 80 self.discretizer.clear() self.curveshape.apply(self.renderer) try: glColor4fv(self.ctrlCurveColor) except: pass self.curveshape.apply(self.ctrlrenderer) self.ctrlpts.apply(self.renderer) self.drawGrid() def mRenderText(self, x, y, text): #error = glGetError() if self.font is None: self.font = QFont() self.renderText(x, y, 0, text) #self.drawText(x,y, text) #error = glGetError() #if error : print gluErrorString(error) def drawVLine(self, x, y1, y2): glVertex3f(x, y1, 0) glVertex3f(x, y2, 0) def drawHLine(self, x1, x2, y): glVertex3f(x1, y, 0) glVertex3f(x2, y, 0) def drawGrid(self): xr = self.end[0] - self.start[0] xy = self.end[1] - self.start[1] if xr <= 0 or xy <= 0: return nbdigit = max(int(round(log(xr, 10))), int(round(log(xy, 10)))) xdelta = pow(10, nbdigit) / 10. fxval = round(self.start[0] / xdelta) lxval = round(self.end[0] / xdelta) cxval = fxval * xdelta nbiter = int((lxval - fxval)) glColor4fv(self.gridColor2) glLineWidth(1) glBegin(GL_LINES) for i in range(nbiter + 1): self.drawVLine(cxval, self.start[1], self.end[1]) cxval += xdelta glEnd() cxval = fxval * xdelta glColor4fv(self.textColor) for i in range(nbiter + 1): self.mRenderText(cxval, self.start[1], '%.1f' % cxval) cxval += xdelta glLineWidth(2) glColor4fv(self.gridColor) cxval = round(self.start[0] / (10 * xdelta)) * (10 * xdelta) glBegin(GL_LINES) for i in range(int(nbiter / 10) + 1): self.drawVLine(cxval, self.start[1], self.end[1]) cxval += (10 * xdelta) glEnd() fyval = round(self.start[1] / xdelta) lyval = round(self.end[1] / xdelta) firstcyval = fyval * xdelta cyval = firstcyval nbiter = int((lyval - fyval)) glColor4fv(self.gridColor2) glLineWidth(1) glBegin(GL_LINES) for i in range(nbiter + 1): self.drawHLine(self.start[0], self.end[0], cyval) cyval += xdelta glEnd() cyval = firstcyval glColor4fv(self.textColor) for i in range(nbiter + 1): glVertex3f(self.end[0], cyval, 0) self.mRenderText(self.start[0], cyval, '%.1f' % cyval) cyval += xdelta glLineWidth(2) glColor4fv(self.gridColor) cyval = round(self.start[1] / (10 * xdelta)) * (10 * xdelta) glBegin(GL_LINES) for i in range(int(nbiter / 10) + 1): self.drawHLine(self.start[0], self.end[0], cyval) cyval += (10 * xdelta) glEnd() def drawWithNames(self): self.renderer.clear() for sh in self.ctrlpts: glPushName(sh.id) sh.apply(self.renderer) glPopName() # self.renderer.renderingMode = GLRenderer.Selection # self.renderer.selectionMode = GLRenderer.ShapeId # self.renderer.beginProcess() # self.ctrlpts.apply(self.renderer) # self.renderer.endProcess() # self.renderer.renderingMode = GLRenderer.Dynamic def pointOnEditionPlane(self, pos): orig, direction = self.camera().convertClickToLine(pos) npoint = orig + direction * (orig[2] / (-direction[2])) return (npoint[0], npoint[1]) def setInteractionMode(self, frame=True): if frame: self.setMouseBinding(Qt.NoModifier, Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.NoModifier, Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.ControlModifier, Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier, Qt.RightButton, QGLViewer.CAMERA, QGLViewer.NO_MOUSE_ACTION) else: self.setMouseBinding(Qt.ControlModifier, Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier, Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.NoModifier, Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.NoModifier, Qt.RightButton, QGLViewer.CAMERA, QGLViewer.NO_MOUSE_ACTION) def mousePressEvent(self, event): if event.modifiers() != Qt.ControlModifier: self.select(event.pos()) self.selection = self.selectedName() if self.selection != -1: p = self.curveAccessor.getPoint(self.selection) self.manipulator.setPosition(Vec(p[0], p[1], 0)) self.setManipulatedFrame(self.manipulator) self.createControlPointsRep() self.setInteractionMode(True) self.updateGL() QGLViewer.mousePressEvent(self, event) else: QGLViewer.mousePressEvent(self, event) else: return QGLViewer.mousePressEvent(self, event) def mouseDoubleClickEvent(self, event): if event.modifiers() == Qt.NoModifier: if event.button() == Qt.LeftButton: self.select(event.pos()) selection = self.selectedName() if selection == -1: npoint = self.pointOnEditionPlane(event.pos()) res = self.pointsConstraints.addPointEvent( npoint, self.curveAccessor) if res: index, npoint = res self.curveAccessor.insertPoint(index, npoint) self.valueChanged.emit() elif event.button() == Qt.RightButton: self.select(event.pos()) selection = self.selectedName() if selection != -1: self.curveAccessor.delPoint(selection) self.valueChanged.emit() self.createControlPointsRep() self.updateGL() elif event.modifiers() == Qt.ShiftModifier: self.select(event.pos()) selection = self.selectedName() if selection != -1: self.curveAccessor.delPoint(selection) self.valueChanged.emit() self.createControlPointsRep() self.updateGL() else: QGLViewer.mouseDoubleClickEvent(self, event) def mouseReleaseEvent(self, event): if self.selection != -1: self.selection = -1 self.setInteractionMode(False) self.createControlPointsRep() self.updateSceneDimension() self.updateGL() QGLViewer.mouseReleaseEvent(self, event) def changeEvent(self, event): if event.type() == QEvent.EnabledChange: if self.isEnabled(): self.pointColor = Material((250, 30, 30), 1) else: self.pointColor = Material((30, 30, 30), 1) self.createControlPointsRep() QGLViewer.changeEvent(self, event) def updatePoints(self): sel = self.selection if sel != -1: p = self.manipulator.getPosition() p = (p[0], p[1]) p = self.pointsConstraints.movePointEvent(p, self.selection, self.curveAccessor) self.curveAccessor.setPoint(self.selection, p) self.createControlPointsRep() self.updateGL() self.valueChanged.emit() def createControlPointsRep(self): self.ctrlpts = Scene([ Shape(Translated(Vector3(p[0], p[1], 0), self.sphere), self.pointColor, id=i) for i, p in enumerate(self.curveAccessor.points()) ]) if self.selection != -1: self.ctrlpts[self.selection].appearance = self.selectedPointColor if self.selection != 0: self.ctrlpts[0].appearance = self.firstPointColor
class SelectionManipulator(ManipulatedFrame): valueChanged = pyqtSignal() def __init__(self): ManipulatedFrame.__init__(self) # set of selected point self.selection = [] # axis for axial displacement self.axis = [ SelectionAxis(self, SelectionAxis.X, material=Material((250, 30, 30), 1)), SelectionAxis(self, SelectionAxis.Y, material=Material((30, 250, 30), 1)), SelectionAxis(self, SelectionAxis.Z, material=Material((250, 250, 250), 1)) ] # previous position to compute displacement self.previousPosition = Vec() self.manipulated = pyqtSignal( ) # AUTO SIGNAL TRANSLATION in class SelectionManipulator self.manipulated.connect( self.propagatePointTranslation ) # QObject.connect(self,SIGNAL("manipulated()"),self.propagatePointTranslation) for axis in self.axis: axis.translated.connect( self.propagateAxisTranslation ) # QObject.connect(axis,SIGNAL("translated(PyQt_PyObject)"),self.propagateAxisTranslation) def __push_position__(self): """ push current position status as previous position """ self.previousPosition = self.position() def displacement(self): """ Gives current displacement of the frame """ delta = self.position() - self.previousPosition self.__push_position__() return delta def propagateAxisTranslation(self, displacement): """ Apply the displacement of the axis on the set of selected control points """ self.setPosition(self.position() + displacement) self.__push_position__() for point in self.selection: point.apply_translation(displacement) self.valueChanged.emit( ) # self.emit(SIGNAL("valueChanged()")) # AUTO SIGNAL TRANSLATION def propagatePointTranslation(self): """ Apply the displacement of self on the set of selected control points """ displacement = self.displacement() for point in self.selection: point.apply_translation(displacement) self.valueChanged.emit( ) # self.emit(SIGNAL("valueChanged()")) # AUTO SIGNAL TRANSLATION def clear(self): """ remove all control points """ self.manipulated.disconnect(self.propagatePointTranslation) for ctrlpoint in self.selection: ctrlpoint.selected = False ctrlpoint.translated.disconnect(self.propagatePointTranslation) #QObject.disconnect(ctrlpoint,SIGNAL("translated(PyQt_PyObject,PyQt_PyObject)"),self.propagatePointTranslation) self.selection = None def add(self, ctrlpoint): """ add a ctrl point to the selection """ self.selection.append(ctrlpoint) ctrlpoint.selected = True #ctrlpoint.translated.connect(self.propagatePointTranslation) # QObject.connect(ctrlpoint,SIGNAL("translated(PyQt_PyObject,PyQt_PyObject)"),self.propagatePointTranslation) self.__update__() def remove(self, ctrlpoint): """ remove a ctrl point to the selection """ ctrlpoint.selected = False #ctrlpoint.translated.connect(self.propagatePointTranslation) # QObject.connect(ctrlpoint,SIGNAL("translated(PyQt_PyObject,PyQt_PyObject)"),self.propagatePointTranslation) del self.selection[self.selection.index(ctrlpoint)] self.__update__() def toogleSelection(self, ctrlpoint): """ add a ctrl point to the selection or remove it if already in it""" if ctrlpoint in self.selection: self.remove(ctrlpoint) else: self.add(ctrlpoint) def __update__(self): """ update the central point position when selection has changed """ self.centralPoint = sum([i.position() for i in self.selection], Vec()) / len(self.selection) self.setPosition(self.centralPoint) self.__push_position__() def representation(self, pointsize=0.05): """ define the representation of this manipulator """ shapes = [] shapes += self.axis[0].representation(pointsize) shapes += self.axis[1].representation(pointsize) shapes += self.axis[2].representation(pointsize) return shapes def empty(self): """ Tell whether the selection set is empty """ return len(self.selection) == 0 def checkIfAxisGrabsMouse(self, event, viewer): """ check whether an axis has been selected """ for axis in self.axis: axis.checkIfGrabsMouse(event.x(), event.y(), viewer.camera()) if axis.grabsMouse(): viewer.setManipulatedFrame(axis) axis.hasFocus = True return True return False def clearFocus(self): """ clear focus """ for axis in self.axis: axis.hasFocus = False
class SelectionAxis(ManipulatedFrame): """ The selection Axis class. We consider here that x, y and z axis are represented. These axis are represented by plantGL polylines and spheres and must be grabbable for constrained displacements """ O, X, Y, Z = (Vec(0, 0, 0), Vec(1, 0, 0), Vec(0, 1, 0), Vec(0, 0, 1) ) # predefined axis direction AxisLength = 0.2 # length of axis representation manipulated = pyqtSignal() translated = pyqtSignal('PyQt_PyObject') def __init__(self, parent, direction=Vec(), material=Material(), selectionMaterial=Material((0, 0, 0), 1)): """Constructor :param parent : the reference frame on which it is defined. In practice, it is a SelectionManipulator. :param direction: the direction of the axis. :param material: color of the representation of this axis :param selectionMaterial: color of the representation of this axis when it is selected """ ManipulatedFrame.__init__(self) self.setReferenceFrame(parent) # selection self.hasFocus = False # direction and position self.direction = direction self.initpos = direction * SelectionAxis.AxisLength self.setTranslation(self.initpos) # display option self.material = material self.selectionMaterial = selectionMaterial #displacement computation self.previousPosition = Vec() # Constraint the direction of translation self.constraint = WorldConstraint() self.constraint.setRotationConstraintType( AxisPlaneConstraint.FORBIDDEN) self.constraint.setTranslationConstraint(AxisPlaneConstraint.AXIS, self.direction) self.setConstraint(self.constraint) self.manipulated.connect( self.__emit_translation__ ) # QObject.connect(self,SIGNAL("manipulated()"),self.__emit_translation__) def representation(self, pointsize=0.05): """ Compute representation of the SelectionAxis as line and sphere. """ p = self.position() p2 = p - self.direction * SelectionAxis.AxisLength if self.hasFocus: material = self.selectionMaterial else: material = self.material sh = [Shape(Polyline([Vector3(*p2), Vector3(*p)]), material)] sh += [Shape(Translated(Vector3(*p), Sphere(pointsize)), material)] return sh def __emit_translation__(self): """ emit a SIGNAL when a translation occur on this axis """ tr = self.translation() self.setTranslation(self.initpos) self.translated.emit( tr - self.initpos ) # self.emit(SIGNAL("translated(PyQt_PyObject)"),tr-self.initpos) # AUTO SIGNAL TRANSLATION
class ObjectDialog(QDialog): """the class that will create dialog between the panel and the editor window""" valueChanged = pyqtSignal() hidden = pyqtSignal() AutomaticUpdate = pyqtSignal(bool) def __init__(self, *args): """during the init of the dialog we have to know the editor we want to open, the typ variable will allow us to know that""" QDialog.__init__(self,*args) self.hasChanged = False self.automaticUpdate = False #self.setModal(True) def setupUi(self,editor): self.setObjectName("ObjectDialog") self.resize(389, 282) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setSpacing(2) self.verticalLayout.setContentsMargins(2, 2, 2, 2) self.verticalLayout.setObjectName("verticalLayout") self._menu = QMenuBar(self) try: self._menu.setNativeMenuBar(False) except: pass self.verticalLayout.addWidget(self._menu) self.objectView = editor sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(5) #sizePolicy.setHeightForWidth(self.objectView.sizePolicy().hasHeightForWidth()) self.objectView.setSizePolicy(sizePolicy) self.objectView.setObjectName("objectView") self.verticalLayout.addWidget(self.objectView) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setSizeConstraint(QLayout.SetFixedSize) self.horizontalLayout.setObjectName("horizontalLayout") self.autoUpdateCheckBox = QCheckBox(self) self.autoUpdateCheckBox.setObjectName("autoUpdateCheckBox") self.horizontalLayout.addWidget(self.autoUpdateCheckBox) spacerItem = QSpacerItem(48, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.okButton = QPushButton(self) self.okButton.setObjectName("okButton") self.horizontalLayout.addWidget(self.okButton) self.applyButton = QPushButton(self) self.applyButton.setObjectName("applyButton") self.horizontalLayout.addWidget(self.applyButton) self.cancelButton = QPushButton(self) self.cancelButton.setObjectName("cancelButton") self.horizontalLayout.addWidget(self.cancelButton) self.verticalLayout.addLayout(self.horizontalLayout) self.setWindowTitle("ObjectDialog") self.autoUpdateCheckBox.setText("Auto update") self.okButton.setText("Ok") self.applyButton.setText("Apply") self.cancelButton.setText("Cancel") self.cancelButton.pressed.connect(self.reject) self.okButton.pressed.connect(self.__ok) self.applyButton.pressed.connect(self.__apply) self.autoUpdateCheckBox.toggled.connect(self.setAutomaticUpdate) self.objectView.valueChanged.connect(self.__valueChanged) def menu(self): return self._menu def __valueChanged(self): if self.automaticUpdate: self.valueChanged.emit() else: self.hasChanged = True def __apply(self): self.valueChanged.emit() self.hasChanged = False def __ok(self): self.valueChanged.emit() self.hasChanged = False self.accept() self.close() def hideEvent(self,event): self.hidden.emit() def setAutomaticUpdate(self,value): """setAutomaticUpdate: checking the autoupdate box will make the QDialog send a 'valueChanged()' signal each time it recieve the same Signal from the objectView""" if self.automaticUpdate != value: self.automaticUpdate = value self.applyButton.setEnabled(not self.automaticUpdate) self.AutomaticUpdate.emit(value) if self.automaticUpdate and self.hasChanged : self.__apply() def closeEvent(self,event): QDialog.closeEvent(self,event)
class LPyWindow(QMainWindow, lsmw.Ui_MainWindow, ComputationTaskManager): endTask = pyqtSignal('PyQt_PyObject') killedTask = pyqtSignal('PyQt_PyObject') instances = [] def __init__(self, parent=None, withinterpreter=True): """ :param parent : parent window """ QMainWindow.__init__(self, parent) ComputationTaskManager.__init__(self) lsmw.Ui_MainWindow.__init__(self) self.setObjectName('LPYMainWindow') self.setWindowIcon(QIcon(":/images/icons/mango.png")) import weakref LPyWindow.instances.append(weakref.ref(self)) self.withinterpreter = withinterpreter self.setupUi(self) self.editToolBar.hide() lpydock.initDocks(self) QTimer.singleShot(1000, lambda: lpydock.initShell(self)) self.preferences = lpypreferences.LpyPreferences(self) icon = QIcon() icon.addPixmap(QPixmap(":/images/icons/history.png"), QIcon.Normal, QIcon.Off) self.menuRecents.setIcon(icon) self.simulations = [] self.currentSimulationId = None self.history = [] self.historymaxsize = 50 self.fileBackupEnabled = True self.codeBackupEnabled = True self.fitAnimationView = True self.fitRunView = True self.with_thread = False self.showPyCode = False self.displayMetaInfo = False self.reloadAtStartup = True self.fileMonitoring = True self.exitWithoutPrompt = False self.cCompilerPath = '' self.profilingMode = ProfilingWithFinalPlot self.desc_items = { '__authors__': self.authorsEdit, '__institutes__': self.intitutesEdit, '__copyright__': self.copyrightEdit, '__description__': self.descriptionEdit, '__references__': self.referenceEdit } self.com_mutex = QMutex() self.com_waitcondition = QWaitCondition() self.killsimudialog = KillSimulationDialog(self) self.plotter = LpyPlotter(self) self.use_own_view3D = False self.viewer = Viewer registerPlotter(self.plotter) class ViewerFuncAborter: def __init__(self): self.__shouldAbort = False self.__registered = False def shouldAbort(self): self.__shouldAbort = True def reset(self): self.__shouldAbort = False if not self.__registered: self.__registered = True Viewer.setDialogAbortFunc(self) def __call__(self): if self.__shouldAbort: self.__shouldAbort = False return True else: return False self.viewAbortFunc = ViewerFuncAborter() self.frameFind.hide() self.frameReplace.hide() self.frameGoto.hide() self.codeeditor.initWithEditor(self) self.debugMode = False self.debugger = LpyVisualDebugger(self) st = self.statusBar() self.materialed.statusBar = st self.panelmanager = ObjectPanelManager(self) #self.documentNames.setShape(QTabBar.TriangularNorth) #self.documentNames.setTabsClosable(True) self.newfile() self.textEditionWatch = False self.documentNames.connectTo(self) self.endTask.connect(self.endTaskCheck) # self.documentNamesMore.newDocumentRequest = pyqtSignal() # AUTO SIGNAL TRANSLATION in class LPyWindow self.documentNamesMore.newDocumentRequest.connect(self.newfile) # self.documentNamesMore2.newDocumentRequest = pyqtSignal() # AUTO SIGNAL TRANSLATION in class LPyWindow self.documentNamesMore2.newDocumentRequest.connect(self.newfile) self.actionNew.triggered.connect(self.newfile) self.actionOpen.triggered.connect(lambda: self.openfile()) self.actionSave.triggered.connect(lambda: self.savefile()) self.actionSaveAll.triggered.connect(lambda: self.saveallfiles()) self.actionSaveAs.triggered.connect(self.saveas) self.actionClose.triggered.connect(self.closeDoc) self.actionImportCpfgProject.triggered.connect( lambda: self.importcpfgproject()) self.actionImportCpfgFile.triggered.connect( lambda: self.importcpfgfile()) self.actionClear.triggered.connect(self.clearHistory) self.actionSaveSession.triggered.connect(self.saveSession) self.actionRun.triggered.connect(self.run) self.actionAnimate.triggered.connect(self.animate) self.actionStep.triggered.connect(self.step) self.actionRewind.triggered.connect(self.rewind) self.actionStepInterpretation.triggered.connect( self.stepInterpretation) self.actionIterateTo.triggered.connect(self.iterateTo) self.actionNextIterate.triggered.connect(self.nextIterate) self.actionAutoRun.triggered.connect(self.projectAutoRun) self.actionDebug.triggered.connect(self.debug) self.actionProfile.triggered.connect(self.profile) self.actionRecord.triggered.connect(self.record) self.actionStop.triggered.connect(self.cancelTask) self.actionStop.triggered.connect(self.abortViewer) self.actionExecute.triggered.connect(self.executeCode) self.actionComment.triggered.connect(self.codeeditor.comment) self.actionUncomment.triggered.connect(self.codeeditor.uncomment) self.actionInsertTab.triggered.connect(self.codeeditor.tab) self.actionRemoveTab.triggered.connect(self.codeeditor.untab) self.actionSyntax.triggered.connect(self.setSyntaxHighLightActivation) self.actionTabHightlight.triggered.connect( self.setTabHighLightActivation) self.actionPreferences.triggered.connect(self.preferences.show) self.animtimestep.valueChanged.connect(self.setTimeStep) self.animtimeSpinBox.valueChanged.connect(self.setTimeStep) self.codeeditor.textChanged.connect(self.textEdited) self.descriptionEdit.textChanged.connect(self.projectEdited) self.referenceEdit.textChanged.connect(self.projectEdited) self.authorsEdit.textChanged.connect(self.projectEdited) self.intitutesEdit.textChanged.connect(self.projectEdited) self.copyrightEdit.textChanged.connect(self.projectEdited) self.materialed.valueChanged.connect(self.projectEdited) self.scalarEditor.valueChanged.connect(self.projectEdited) self.scalarEditor.valueChanged.connect(self.projectParameterEdited) self.actionPrint.triggered.connect(self.printCode) self.actionView3D.setEnabled(self.use_own_view3D) self.actionView3D.triggered.connect(self.switchCentralView) self.aboutLpy = lambda x: doc.aboutLpy(self) self.actionAbout.triggered.connect(self.aboutLpy) self.actionAboutQt.triggered.connect(QApplication.aboutQt) self.aboutVPlants = lambda x: doc.aboutVPlants(self) self.helpDisplay.setText(doc.getSpecification()) self.actionOnlineHelp.triggered.connect(self.onlinehelp) self.actionSubmitBug.triggered.connect(self.submitBug) self.actionCheckUpdate.triggered.connect(self.check_lpy_update) self.actionUseThread.triggered.connect(self.toggleUseThread) self.actionFitAnimationView.triggered.connect( self.toggleFitAnimationView) self.menuRecents.triggered.connect(self.recentMenuAction) self.initSVNMenu() self.printTitle() self.centralViewIsGL = False self.svnLastRevisionChecked = 0 self.svnLastDateChecked = 0.0 self.stackedWidget.setCurrentIndex(0) self.setAnimated(False) settings.restoreState(self) self.createRecentMenu() #if not py2exe_release: try: self.createTutorialMenu() except: pass self.textEditionWatch = True self._initialized = False try: self.lpy_update_enabled = self.check_lpy_update_available() except: pass def init(self): self.textEditionWatch = False self.recoverPreviousFiles() self.textEditionWatch = True if True: #self.lpy_update_enabled: self.check_lpy_update(True) self.documentNames.show() self.currentSimulation().updateTabName() def check_lpy_update_available(self): available = True if not available: self.actionCheckUpdate.setEnabled(False) return available def retrieve_official_lpy_version(self, channel='openalea'): import urllib.request, urllib.error, urllib.parse versionurl = 'https://raw.githubusercontent.com/' + channel + '/lpy/master/src/openalea/lpy/__version__.py' try: response = urllib.request.urlopen(versionurl) except urllib.error.URLError as ue: import openalea.lpy.__version__ as lv return get_version_majorminor( lv.__version_number__), lv.LPY_VERSION_STR else: pyversioncode = response.read() lvofficial = {} exec(pyversioncode, lvofficial) return get_version_majorminor(lvofficial['__version_number__'] ), lvofficial['LPY_VERSION_STR'] def check_lpy_update(self, silent=False): import openalea.lpy.__version__ as lv import os, time if not silent or ( (self.svnLastDateChecked + 7 * 24 * 60 * 60) < time.time()): self.svnLastDateChecked = time.time() officialversion, offverstring = self.retrieve_official_lpy_version( ) officialdevversion, offverdevstring = self.retrieve_official_lpy_version( 'fredboudon') if get_version_majorminor(lv.__version_number__) < officialversion: QMessageBox.information( self, "Lpy Update", "Your version is " + lv.LPY_VERSION_STR + ".\nA new release version of lpy seems available on github :" + offverstring + "\n.") elif get_version_majorminor( lv.__version_number__) < officialdevversion: QMessageBox.information( self, "Lpy Update", "Your version is " + lv.LPY_VERSION_STR + ".\nA new develop version of lpy seems available on github :" + offverdevstring + "\n.") elif not silent: QMessageBox.information( self, "Lpy Update", "Your version is " + lv.LPY_VERSION_STR + ".\nYou are up-to-date!") else: self.statusBar().showMessage("L-Py " + lv.LPY_VERSION_STR + " is up-to-date.") def switchCentralView(self): if not self.centralViewIsGL: self.stackedWidget.setCurrentIndex(1) else: self.stackedWidget.setCurrentIndex(0) self.centralViewIsGL = not self.centralViewIsGL self.actionView3D.setChecked(self.centralViewIsGL) def setView3DCentral(self, enabled=True): if self.centralViewIsGL != enabled: self.switchCentralView() def setIntegratedView3D(self, enabled): if self.use_own_view3D != enabled: if PyQGLViewer: self.use_own_view3D = enabled if not enabled and self.centralViewIsGL: self.switchCentralView() if not enabled: self.viewer = Viewer Viewer.display = self.previousplotfunction else: if not hasattr(self, 'previousplotfunction'): self.previousplotfunction = Viewer.display self.viewer = self.view3D def myplot(sc): self.plotScene(sc) Viewer.display = staticmethod(myplot) self.actionView3D.setEnabled(enabled) def getObjectPanels(self): return self.panelmanager.getObjectPanels() def getMaxObjectPanelNb(self): return self.panelmanager.getMaxObjectPanelNb() def setObjectPanelNb(self, nb, new_visible=True): self.panelmanager.setObjectPanelNb(nb, new_visible) #def createNewPanel(self,above): # self.panelmanager.createNewPanel(above) def abortViewer(self): self.viewAbortFunc.shouldAbort() def currentSimulation(self): return self.simulations[self.currentSimulationId] def changeDocument(self, id): if self.currentSimulationId != id and id >= 0: if not self.currentSimulationId is None: self.currentSimulation().saveState() self.currentSimulationId = id self.currentSimulation().monitorfile() self.currentSimulation().restoreState() if self.documentNames.currentIndex() != id: self.documentNames.setCurrentIndex(id) def findDocumentId(self, fname): for i, s in enumerate(self.simulations): if s.fname == fname: return s.index def showDocumentAt(self, fname, line): if self.currentSimulation().fname == fname: self.codeeditor.gotoLine(line) else: id = None for i, s in enumerate(self.simulations): if s.fname == fname: id = s.index break if not id is None: self.changeDocument(id) self.codeeditor.gotoLine(line) def switchDocuments(self, id1, id2): self.simulations[id1], self.simulations[id2] = self.simulations[ id2], self.simulations[id1] self.simulations[id1].index = id1 self.simulations[id2].index = id2 self.simulations[id1].updateTabName(True) self.simulations[id2].updateTabName(True) self.documentNames.currentChanged.disconnect(self.changeDocument) self.documentNames.setCurrentIndex(id1) self.documentNames.currentChanged.connect(self.changeDocument) def focusInEvent(self, event): self.currentSimulation().monitorfile() return QMainWindow.focusInEvent(self, event) def closeDoc(self): self.closeDocument() def closeDocument(self, id=None): if id is None: id = self.currentSimulationId if self.simulations[id].close(): self.documentNames.removeTab(id) for i in range(id + 1, len(self.simulations)): self.simulations[i].index = i - 1 self.textEditionWatch = False defaultdoc = self.codeeditor.defaultdoc self.codeeditor.setLpyDocument(defaultdoc) self.simulations.pop(id) self.textEditionWatch = True if len(self.simulations) == 0: self.currentSimulationId = None self.newfile() else: self.currentSimulation().restoreState() def end(self, force=False): if force: self.exitWithoutPrompt = force self.close() def closeEvent(self, e): self.debugger.endDebug() self.viewer.stop() settings.saveState(self) prompt = False if not self.exitWithoutPrompt: for simu in self.simulations: prompt = (prompt or simu.isEdited()) if prompt: break if not prompt: answer = QMessageBox.warning(self, "Quitting", "Are you sure ?", QMessageBox.Ok, QMessageBox.Cancel) if answer == QMessageBox.Cancel: e.ignore() else: e.accept() else: for simu in reversed(self.simulations): if not simu.close(): e.ignore() return e.accept() else: e.accept() if e.isAccepted(): self.interpreter.locals.clear() def saveSession(self): settings.saveState(self) def showEvent(self, event): if not self._initialized: self.init() self._initialized = True def taskRunningEvent(self): self.statusBar().showMessage('A task is already running !', 5000) raise Exception('A task is already running') def acquireEvent(self): self.enableButtons(False) def releaseEvent(self): self.enableButtons(True) def enableButtons(self, enabled): self.actionRun.setEnabled(enabled) self.actionAnimate.setEnabled(enabled) self.actionStep.setEnabled(enabled) self.actionRewind.setEnabled(enabled) self.actionClear.setEnabled(enabled) self.actionClose.setEnabled(enabled) self.actionProfile.setEnabled(enabled) if self.debugMode == True: self.actionDebug.setEnabled(True) else: self.actionDebug.setEnabled(enabled) self.actionStop.setEnabled(not enabled) self.documentNames.setEnabled(enabled) def projectAutoRun(self, value=True): self.currentSimulation().autorun = value def viewer_plot(self, scene): if self.use_own_view3D: self.setView3DCentral() self.viewer.display(scene) def plotScene(self, scene): if self.thread() != QThread.currentThread(): #Viewer.display(scene) self.com_mutex.lock() e = QEvent(QEvent.Type(QEvent.User + 1)) e.scene = scene QApplication.postEvent(self, e) self.com_waitcondition.wait(self.com_mutex) self.com_mutex.unlock() else: self.viewer_plot(scene) if not self.currentSimulation().autorun: QCoreApplication.instance().processEvents() def cancelTask(self): if self.debugMode: self.debugger.stop() if not self.debugger.running: self.debugMode = False self.releaseCR() else: if not self.computationThread is None: self.currentSimulation().cancel() self.killsimudialog.run(self.isRunning, self.killTask) else: if self.isRunning(): print("Force release") self.releaseCR() def customEvent(self, event): self.viewer_plot(event.scene) self.com_mutex.lock() self.com_mutex.unlock() self.com_waitcondition.wakeAll() def errorEvent(self, exc_info, errmsg, displayDialog): if self.withinterpreter: self.interpreterDock.show() t, v, trb = exc_info stacksummary = list(reversed(tb.extract_tb(trb))) print(len(stacksummary)) for fid, frame in enumerate(stacksummary): print(frame.filename) if 'openalea/lpy' in frame.filename: stacksummary = stacksummary[:fid] break print(len(stacksummary)) self.lastexception = v if t == SyntaxError: errorfile = v.filename lineno = v.lineno else: if len(stacksummary) > 0: st = stacksummary[0] errorfile = st.filename lineno = st.lineno else: errorfile = None lineno = None fnames = ['<string>', self.currentSimulation().getBaseName()] if errorfile in fnames: self.codeeditor.hightlightError(lineno) def showErrorOnFile(): docid = self.findDocumentId(errorfile) if docid: self.showDocumentAt(errorfile, lineno) else: self.showfilecontent(errorfile) self.codeeditor.hightlightError(lineno) if displayDialog: dialog = QMessageBox( QMessageBox.Warning, "Exception", (os.path.basename(errorfile) + ':' if errorfile else '') + (str(lineno) + ':' if lineno else '') + errmsg, QMessageBox.Ok, self) if errorfile and (not errorfile in fnames): showbutton = dialog.addButton("Show file", QMessageBox.ApplyRole) showbutton.clicked.connect(showErrorOnFile) if len(stacksummary) > 0: dialog.setDetailedText(errmsg + '\n\n' + '\n'.join(tb.format_list(stacksummary))) dialog.exec_() #showError = QMessageBox.warning(self,"Exception", msg, QMessageBox.Ok) def endErrorEvent(self): if self.debugger.running: self.debugger.stopDebugger() self.debugMode = False def setToolBarApp(self, value): if type(value) == int: # hack since pyqtSlot does not seems to work value = ['Icons', 'Texts', 'Icons and texts', 'Texts below icons'][value] for bar in [self.FileBar, self.LsytemBar, self.editToolBar]: bar.setToolButtonStyle({ 'Icons': Qt.ToolButtonIconOnly, 'Texts': Qt.ToolButtonTextOnly, 'Icons and texts': Qt.ToolButtonTextBesideIcon, 'Texts below icons': Qt.ToolButtonTextUnderIcon }[str(value)]) def getToolBarApp(self): return { Qt.ToolButtonIconOnly: (0, 'Icons'), Qt.ToolButtonTextOnly: (1, 'Texts'), Qt.ToolButtonTextBesideIcon: (2, 'Icons and texts'), Qt.ToolButtonTextUnderIcon: (3, 'Texts below icons') }[self.FileBar.toolButtonStyle()] def toggleUseThread(self): ComputationTaskManager.toggleUseThread(self) def toggleFitAnimationView(self): self.fitAnimationView = not self.fitAnimationView def textEdited(self): if self.textEditionWatch: self.currentSimulation().textEdited() def projectEdited(self): if self.textEditionWatch: self.currentSimulation().setEdited(True) def projectParameterEdited(self, primitiveChanged=False): if self.currentSimulation().autorun: if not self.isRunning(): self.run(True, primitiveChanged) else: self.shouldrerun = True if hasattr(self, 'primitiveChanged'): self.primitiveChanged |= primitiveChanged else: self.primitiveChanged = primitiveChanged def endTaskCheck(self, task): if hasattr(task, 'checkRerun'): if hasattr(self, 'shouldrerun'): del self.shouldrerun primitiveChanged = self.primitiveChanged del self.primitiveChanged self.run(True, primitiveChanged) def printTitle(self): fname = self.currentSimulation().getFileName() self.setWindowFilePath(fname) t = 'L-Py - ' t += self.currentSimulation().getTabName() self.setWindowTitle(t) self.setWindowIcon(self.currentSimulation().generateIcon()) def printCode(self): printer = QPrinter() dialog = QPrintDialog(printer, self) dialog.setWindowTitle("Print Document") if dialog.exec_() != QDialog.Accepted: return self.codeeditor.print_(printer) def createNewLsystem(self, fname=None): i = len(self.simulations) self.simulations.append(LpySimulation(self, i, fname)) self.currentSimulationId = i self.currentSimulation().registerTab() def setTimeStep(self, val): if isinstance(val, int): self.currentSimulation().timestep = val else: self.currentSimulation().timestep = val * 1000 t = self.currentSimulation().timestep if t != self.animtimestep: self.animtimestep.setValue(t) if t * 0.001 != self.animtimeSpinBox: self.animtimeSpinBox.setValue(t * 0.001) if self.currentSimulation().lsystem: self.currentSimulation().lsystem.context( ).animation_timestep = t * 0.001 self.projectEdited() def newfile(self): self.acquireCR() if not self.currentSimulationId is None and self.currentSimulationId > 0: self.currentSimulation().saveState() self.createNewLsystem() self.releaseCR() self.currentSimulation().restoreState() def recoverPreviousFiles(self): from . import lpytmpfile as tf import os torecover = tf.getPreviousTmpLpyFiles() nbrecoverfile = len(torecover) if nbrecoverfile > 0: answer = QMessageBox.warning( self, "Recovery mode", "Backup files exist (%s). Do you want to recover ?" % nbrecoverfile, QMessageBox.Ok, QMessageBox.Discard) recover = (answer == QMessageBox.Ok) for f in torecover: if recover: self.acquireCR() try: self.currentSimulation().saveState() self.createNewLsystem() self.currentSimulation().importtmpfile(f) self.currentSimulation().restoreState() self.statusBar().showMessage("Load file '" + f + "'", 2000) if len(self.simulations ) == 2 and self.simulations[0].isDefault(): self.closeDocument(0) except: self.graberror() self.releaseCR() else: os.remove(f) def getSimuIndex(self, fname): for sim in self.simulations: if sim.fname == fname: return sim.index return None def openfile(self, fname=None): if fname is None: initialname = os.path.dirname(self.currentSimulation( ).fname) if self.currentSimulation().fname else '.' fname = QFileDialog.getOpenFileName( self, "Open L-Py file", initialname, "L-Py Files (*.lpy);;All Files (*.*)") if not fname: return fname = fname[0] self.appendInHistory(fname) else: if not os.path.exists(fname): self.removeInHistory(fname) QMessageBox.warning( self, "Inexisting file", "File '" + fname + "' does not exist anymore.", QMessageBox.Ok) fname = None else: self.appendInHistory(fname) if fname: ind = self.getSimuIndex(fname) if not ind is None: self.simulations[ind].makeCurrent() else: self.acquireCR() self.showfilecontent(fname) self.releaseCR() def showfilecontent(self, fname): try: self.currentSimulation().saveState() self.createNewLsystem(fname) self.currentSimulation().restoreState() self.statusBar().showMessage("Load file '" + fname + "'", 2000) if len(self.simulations) == 2 and self.simulations[0].isDefault(): self.closeDocument(0) except: self.graberror() def savefile(self): self.currentSimulation().save() def saveas(self): self.currentSimulation().saveas() def saveallfiles(self): nbsaving = 0 for sim in self.simulations: if sim.isEdited(): sim.save() nbsaving += 1 self.saveSession() self.statusBar().showMessage("No file to save." if nbsaving == 0 else "%i file(s) saved." % nbsaving) def importcpfgfile(self, fname=None): if fname is None: initialname = os.path.dirname(self.currentSimulation( ).fname) if self.currentSimulation().fname else '.' fname = QFileDialog.getOpenFileName( self, "Open Cpfg File", initialname, "Cpfg Files (*.l);;All Files (*.*)") if not fname: return fname = str(fname[0]) elif not os.path.exists(fname): QMessageBox.warning(self, "Inexisting file", "File '" + fname + "' does not exist anymore.", QMessageBox.Ok) fname = None if fname: self.importcpfgproject(fname) def importcpfgproject(self, fname=None): if fname is None: initialname = os.path.dirname(self.currentSimulation( ).fname) if self.currentSimulation().fname else '.' fname = QFileDialog.getExistingDirectory(self, "Open Cpfg Project", initialname) if not fname: return fname = str(fname) elif not os.path.exists(fname): QMessageBox.warning(self, "Inexisting file", "File '" + fname + "' does not exist anymore.", QMessageBox.Ok) fname = None if fname: ind = self.getSimuIndex(fname) if not ind is None: self.simulations[ind].makeCurrent() else: self.acquireCR() try: self.currentSimulation().saveState() self.createNewLsystem() self.currentSimulation().importcpfgproject(fname) self.currentSimulation().restoreState() self.statusBar().showMessage("Load file '" + fname + "'", 2000) if len(self.simulations ) == 2 and self.simulations[0].isDefault(): self.closeDocument(0) except: self.graberror() self.releaseCR() def run(self, rerun=False, primitiveChanged=False): self.acquireCR() try: if not self.use_own_view3D: self.viewAbortFunc.reset() simu = self.currentSimulation() self.viewer.start() if not rerun: self.viewer.setAnimation(eStatic) else: if primitiveChanged or simu.getOptimisationLevel() < 2: self.viewer.setAnimation(eAnimatedPrimitives) else: self.viewer.setAnimation(eAnimatedScene) simu.updateLsystemCode() simu.isTextEdited() task = ComputationTask(simu.run, simu.post_run, simu.pre_run, cleanupprocess=simu.cleanup) task.checkRerun = True task.fitRunView = self.fitRunView self.registerTask(task) except: self.graberror() self.releaseCR() def step(self): self.acquireCR() simu = self.currentSimulation() if not self.use_own_view3D: self.viewAbortFunc.reset() try: task = ComputationTask(simu.step, simu.post_step, simu.pre_step, cleanupprocess=simu.cleanup) task.fitRunView = self.fitRunView self.registerTask(task) except: self.graberror() self.releaseCR() def stepInterpretation(self): self.acquireCR() simu = self.currentSimulation() if not self.use_own_view3D: self.viewAbortFunc.reset() try: task = ComputationTask(simu.stepInterpretation, simu.post_stepInterpretation, simu.pre_stepInterpretation, cleanupprocess=simu.cleanup) task.fitRunView = self.fitRunView self.registerTask(task) except: self.graberror() self.releaseCR() def iterateTo(self): simu = self.currentSimulation() initval = simu.iterateStep if initval is None: initval = 1 val, ok = QInputDialog.getInteger(self, "Number of Iterations", "Choose number of iterations", initval, 1) if ok: simu.iterateStep = val self.nextIterate() def nextIterate(self): simu = self.currentSimulation() if simu.iterateStep is None: self.iterateTo() else: self.acquireCR() if not self.use_own_view3D: self.viewAbortFunc.reset() simu = self.currentSimulation() try: task = ComputationTask(simu.iterate, simu.post_step, simu.pre_step, cleanupprocess=simu.cleanup) task.fitRunView = self.fitRunView self.registerTask(task) except: self.graberror() self.releaseCR() def debug(self): if self.debugMode == True: next(self.debugger) else: self.debugMode = True self.acquireCR() if not self.use_own_view3D: self.viewAbortFunc.reset() simu = self.currentSimulation() try: simu.debug() except: self.graberror(displayDialog=False) self.releaseCR() self.debugMode = False def profile(self): self.profilerDock.show() self.acquireCR() simu = self.currentSimulation() if not self.use_own_view3D: self.viewAbortFunc.reset() try: task = ComputationTask(simu.profile, simu.post_profile, simu.pre_profile, cleanupprocess=simu.cleanup) task.mode = self.profilingMode task.fitRunView = self.fitRunView task.profileView = self.profileView self.registerTask(task) except: self.graberror() self.releaseCR() def rewind(self): self.acquireCR() try: self.currentSimulation().rewind() except: self.graberror() self.releaseCR() def animate(self): self.acquireCR() simu = self.currentSimulation() if not self.use_own_view3D: self.viewAbortFunc.reset() try: task = ComputationTask(simu.animate, simu.post_animate, simu.pre_animate, cleanupprocess=simu.cleanup) task.fitAnimationView = self.fitAnimationView self.registerTask(task) except: self.graberror() self.releaseCR() def record(self): fname = '.' if len(self.currentSimulation().fname) > 0: fname = os.path.splitext( self.currentSimulation().fname)[0] + '.png' fname = QFileDialog.getSaveFileName( self, 'Choose template image file name', fname, 'Images (*.png,*.bmp,*.jpg);;All Files (*)') if fname: fname = str(fname[0]) self.acquireCR() simu = self.currentSimulation() if not self.use_own_view3D: self.viewAbortFunc.reset() try: task = ComputationTask(simu.animate, simu.post_animate, simu.pre_animate, cleanupprocess=simu.cleanup) task.fitAnimationView = self.fitAnimationView task.recording = os.path.splitext(fname)[0] + '-' task.recording_suffix = os.path.splitext(fname)[1][1:] self.registerTask(task) except: self.graberror() self.releaseCR() def clear(self): self.acquireCR() try: self.currentSimulation().clear() except: self.graberror() self.releaseCR() def appendInHistory(self, fname): if fname is None: print('Wrong added file in history') fname = str(fname) if not fname in self.history: self.history.insert(0, fname) elif fname == self.history[0]: self.history.remove(fname) self.history.insert(0, fname) if len(self.history) > self.historymaxsize: del self.history[self.historymaxsize:] self.createRecentMenu() def removeInHistory(self, fname): fname = str(fname) if fname in self.history: self.history.remove(fname) self.createRecentMenu() def createRecentMenu(self): self.menuRecents.clear() icon = QIcon() icon.addPixmap(QPixmap(":/images/icons/codefile.png"), QIcon.Normal, QIcon.Off) if len(self.history) > 0: for f in self.history: action = QAction(os.path.basename(f), self.menuRecents) action.setData(to_qvariant(f)) action.setIcon(icon) self.menuRecents.addAction(action) self.menuRecents.addSeparator() self.menuRecents.addAction(self.actionClear) def createTutorialMenu(self): self.menuTutorials.clear() iconfile = QIcon() iconfile.addPixmap(QPixmap(":/images/icons/codefile.png"), QIcon.Normal, QIcon.Off) iconfolder = QIcon() iconfolder.addPixmap(QPixmap(":/images/icons/fileopen.png"), QIcon.Normal, QIcon.Off) from openalea.lpy.gui.shared_data import shared_data import openalea.lpy import os if 'CONDA_PREFIX' in os.environ: if sys.platform == 'win32': shared_data_path = os.path.join(os.environ['CONDA_PREFIX'], 'Library', 'share', 'lpy', 'tutorial') else: shared_data_path = os.path.join(os.environ['CONDA_PREFIX'], 'share', 'lpy', 'tutorial') else: shared_data_path = shared_data(openalea.lpy, share_path='share/tutorial') if not shared_data_path is None and os.path.exists(shared_data_path): import os cpath = os.path.abspath(shared_data_path) cmenu = self.menuTutorials toprocess = [(cpath, cmenu)] while len(toprocess) > 0: cpath, cmenu = toprocess.pop(0) csubpath = os.listdir(cpath) csubpath.sort() for fname in csubpath: absfname = os.path.join(cpath, fname) if os.path.isdir(absfname): childmenu = cmenu.addMenu(iconfolder, os.path.basename(str(fname))) toprocess.append((absfname, childmenu)) childmenu.triggered.connect(self.recentMenuAction) elif fname[-4:] == '.lpy': action = QAction(os.path.basename(str(fname)), cmenu) action.setData(to_qvariant(absfname)) action.setIcon(iconfile) cmenu.addAction(action) else: self.menuFile.removeAction(self.menuTutorials.menuAction()) def recentMenuAction(self, action): self.openfile(str(action.data())) def clearHistory(self): self.history = [] self.createRecentMenu() def setSyntaxHighLightActivation(self, value): self.textEditionWatch = False self.codeeditor.setSyntaxHighLightActivation(value) self.textEditionWatch = True def setTabHighLightActivation(self, value): self.textEditionWatch = False self.codeeditor.setTabHighLightActivation(value) self.textEditionWatch = True def setCCompilerPath(self, p): self.cCompilerPath = str(p) if len(self.cCompilerPath ) != 0 and not self.cCompilerPath in os.environ['PATH']: os.environ['PATH'] += ';' + self.cCompilerPath def executeCode(self): cmd = self.codeeditor.codeToExecute() #print '... '+'\n... '.join(cmd.splitlines()) #self.interpreter.runcode(cmd) self.shellwidget.execute(cmd) cursor = self.codeeditor.textCursor() cursor.movePosition(QTextCursor.Down) self.codeeditor.setTextCursor(cursor) def submitBug(self): import webbrowser webbrowser.open("https://github.com/openalea/lpy/issues") def onlinehelp(self): import webbrowser webbrowser.open("https://lpy-fb.readthedocs.io/") def initSVNMenu(self): from . import svnmanip if not svnmanip.hasSvnSupport(): self.menuSVN.setEnabled(False) else: self.menuSVN.aboutToShow.connect(self.updateSVNMenu) self.actionSVNUpdate.triggered.connect(self.svnUpdate) self.actionSVNCommit.triggered.connect(self.svnCommit) self.actionSVNRevert.triggered.connect(self.svnRevert) self.actionSVNAdd.triggered.connect(self.svnAdd) self.actionSVNIsUpToDate.triggered.connect(self.svnIsUpToDate) def updateSVNMenu(self): from . import svnmanip import os if svnmanip.hasSvnSupport(): fname = self.currentSimulation().fname if not fname or not svnmanip.isSvnFile(fname): for action in [ self.actionSVNUpdate, self.actionSVNCommit, self.actionSVNRevert, self.actionSVNIsUpToDate ]: action.setEnabled(False) self.actionSVNAdd.setEnabled( not fname is None and svnmanip.isSvnFile(os.path.dirname(fname))) else: status = svnmanip.svnFileTextStatus(fname) isadded = (status == svnmanip.added) ismodified = (status == svnmanip.modified) isnormal = (status == svnmanip.normal) self.actionSVNUpdate.setEnabled(not isadded) self.actionSVNIsUpToDate.setEnabled(not isadded) self.actionSVNCommit.setEnabled(ismodified or isadded) self.actionSVNRevert.setEnabled(not isnormal) self.actionSVNAdd.setEnabled(False) def svnUpdate(self): self.currentSimulation().svnUpdate() def svnIsUpToDate(self): self.currentSimulation().svnIsUpToDate() def svnAdd(self): self.currentSimulation().svnAdd() def svnRevert(self): self.currentSimulation().svnRevert() def svnCommit(self): self.currentSimulation().svnCommit()
class Margin(QWidget): lineClicked = pyqtSignal(int) def __init__(self,parent,editor): QWidget.__init__(self,parent) self.editor = editor self.showLines = True self.markers = {} self.markerStack = {} self.markerType = {} def paintEvent( self, paintEvent ): if self.showLines: maxheight = self.editor.viewport().height() maxline = self.editor.document().blockCount() painter = QPainter(self) painter.setPen(QPen(QColor(100,100,100))) h = 0 line = -1 while h < maxheight and line < maxline: cursor = self.editor.cursorForPosition(QPoint(1,h)) nline = cursor.blockNumber()+1 rect = self.editor.cursorRect(cursor) if nline > line: line = nline painter.drawText(0,rect.top()+2,40,rect.height()+2, Qt.AlignHCenter|Qt.AlignTop,str(line)) m = self.markers.get(line,None) if not m is None: lm = self.markerStack.get(line,None) if not lm is None: for slm in lm: painter.drawPixmap(32,rect.top()+2,self.markerType[slm]) painter.drawPixmap(32,rect.top()+2,self.markerType[m]) h = rect.top()+rect.height()+1 painter.end() def mousePressEvent( self, event ): line = self.editor.cursorForPosition(event.pos()).blockNumber() self.lineClicked.emit(line+1) #self.emit(SIGNAL("lineClicked(int)"),) def clear( self ): self.removeAllMarkers() self.markerType = {} def hasMarker(self): return len(self.markers) != 0 def setMarkerAt(self,line,id): self.markers[line] = id if line in self.markerStack: del self.markerStack[line] self.update() def hasMarkerAt(self,line): return line in self.markers def hasMarkerTypeAt(self,line,id): if line in self.markers : if self.markers[line] == id: return True if line in self.markerStack: if id in self.markerStack[line]: return True return False def getCurrentMarkerAt(self,line): return self.markers[line] def removeCurrentMarkerAt(self,line): del self.markers[line] if line in self.markerStack: self.markers[line] = self.markerStack[line].pop() if len(self.markerStack[line]) == 0: del self.markerStack[line] self.update() def removeMarkerTypeAt(self,line,id): if self.markers[line] == id: self.removeCurrentMarkerAt(line) else: self.markerStack[line].remove(id) if len(self.markerStack[line]) == 0: del self.markerStack[line] self.update() def removeAllMarkersAt(self,line): if line in self.marker: del self.markers[line] if line in self.markerStack: del self.markerStack[line] self.update() def removeAllMarkers(self): self.markers = {} self.markerStack = {} self.update() def addMarkerAt(self,line,id): val = self.markers.get(line,None) if not val is None: if line not in self.markerStack: self.markerStack[line] = [] self.markerStack[line].append(val) self.markers[line] = id self.update() def appendMarkerAt(self,line,id): val = self.markers.get(line,None) if not val is None: if line not in self.markerStack: self.markerStack[line] = [] self.markerStack[line].append(id) else: self.markers[line] = id self.update() def defineMarker(self,id,pixmap): self.markerType[id] = pixmap def getAllMarkers(self,id): return set([l for l,lid in self.markers.items() if id == lid]).union(set([l for l,lids in self.markerStack.items() if id in lids])) def decalMarkers(self,line,decal = 1): markers = {} markerStack = {} if decal < 0: for l,v in self.markers.items(): if l <= line+decal: markers[l] = v elif l > line: markers[l+decal] = v for l,v in self.markerStack.items(): if l <= line+decal: markerStack[l] = v elif l > line: markerStack[l+decal] = v if decal > 0: for l,v in self.markers.items(): if l < line: markers[l] = v else: markers[l+decal] = v for l,v in self.markerStack.items(): if l < line: markerStack[l] = v else: markerStack[l+decal] = v if decal != 0: self.markers = markers self.markerStack = markerStack self.update() def saveState(self,obj): obj.markersState = (self.markers,self.markerStack) def restoreState(self,obj): if hasattr(obj,'markersState'): self.markers,self.markerStack = obj.markersState else: self.removeAllMarkers()
class NurbsPatchEditor(QGLViewer): """ The class NurbsPatchEditor is the viewer of the scene, it contains all the informations about the NurbsPatch, that's why a getter and a setter have been created, the NubrsPatch is defined by a 2 dimensional Array of 3d Vectors""" Edit, Rotate = list(range(2)) valueChanged = pyqtSignal() def __init__(self, parent): """ Constructor :param parent: the parent widget """ QGLViewer.__init__(self, parent) self.setStateFileName('.nurbspatcheditor.xml') self.mode = NurbsPatchEditor.Edit # the nurbs patch self.nurbsPatch = None # matrix of ctrl point manipulator self.ctrlPointMatrix = [] # shape and material to display the patch self.nurbsShape = Shape() self.defaultMaterial = Material((30, 200, 30), 1, transparency=0.5) self.nurbsShape.appearance = self.defaultMaterial # plantgl basic object to display nurbs self.discretizer = Discretizer() self.glrenderer = GLRenderer(self.discretizer) self.glrenderer.renderingMode = GLRenderer.Dynamic self.ctrlrenderer = GLCtrlPointRenderer(self.discretizer) self.bboxcomputer = BBoxComputer(self.discretizer) # Current selection self.selectionManipulator = None self.focus = None # rectangular selection self.selectionRect = False # for rectangular selection (with control) self.__rectangle = QRect() self.__rectangleInit = None #creation of a default NurbsPatch if len(self.ctrlPointMatrix) == 0: self.setNurbsPatch(self.newDefaultNurbsPatch()) def __propagate_valuechanged__(self, pid=None): """ emit a SIGNAL every time a value changed """ self.setSceneBoundingBox(*self.getBounds()) self.valueChanged.emit( ) # self.emit(SIGNAL("valueChanged()")) # AUTO SIGNAL TRANSLATION def init(self): """ init function """ self.setSceneBoundingBox(*self.getBounds()) self.restoreStateFromFile() # init mouse interaction self.mode = NurbsPatchEditor.Edit self.setInteractionMode(False) self.setMouseBindingDescription(Qt.ShiftModifier + Qt.LeftButton, "Rectangular selection") self.setMouseBindingDescription(Qt.LeftButton, "Camera/Control Points manipulation") self.setMouseBindingDescription( Qt.LeftButton, "When double clicking on a line, create a new line", True) def getBounds(self): """ Get the Bounding Box of the scene: return the minpos and maxpos that can be used by setSceneBoundingBox function""" self.nurbsPatch.apply(self.bboxcomputer) res = self.bboxcomputer.result return Vec(*res.lowerLeftCorner), Vec(*res.upperRightCorner) def setFocus(self, point): """ Set focus to given control point """ if self.focus: self.focus.hasFocus = False self.focus = point if self.focus: point.hasFocus = True def clearSelectionManipulator(self): """ clear the selection manipulator """ if self.selectionManipulator: self.selectionManipulator.clear() self.selectionManipulator.valueChanged.disconnect( self.__propagate_valuechanged__ ) # QObject.disconnect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) self.selectionManipulator = None def createSelectionManipulator(self): """ ensure that the selection manipulator is existing and valid """ if not self.selectionManipulator: self.selectionManipulator = SelectionManipulator() self.selectionManipulator.valueChanged.connect( self.__propagate_valuechanged__ ) # QObject.connect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) def mousePressEvent(self, event): """ Check for eventual operations the user asks: shift start rectangular selection else check for which point is selected """ #Rectangular selection if event.modifiers() == Qt.ShiftModifier: if event.button() == Qt.LeftButton: self.__rectangle = QRect(event.pos(), event.pos()) self.__rectangleInit = event.pos() self.selectionRect = True elif event.button() == Qt.RightButton: self.clearSelectionManipulator() self.updateGL() else: pointSelection = False if self.selectionManipulator: pointSelection = self.selectionManipulator.checkIfAxisGrabsMouse( event, self) if not pointSelection: for ctrlPointRow in self.ctrlPointMatrix: for cCtrlPoint in ctrlPointRow: cCtrlPoint.checkIfGrabsMouse(event.x(), event.y(), self.camera()) if cCtrlPoint.grabsMouse(): pointSelection = True self.setFocus(cCtrlPoint) if self.selectionManipulator and cCtrlPoint.selected: self.setManipulatedFrame( self.selectionManipulator) else: self.setManipulatedFrame(cCtrlPoint) break self.setInteractionMode(pointSelection) self.updateGL() QGLViewer.mousePressEvent(self, event) def setInteractionMode(self, frame=True): if frame: if self.mode != NurbsPatchEditor.Edit: self.mode = NurbsPatchEditor.Edit self.setMouseBinding(Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.ControlModifier + Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.ROTATE) self.setMouseBinding(Qt.ControlModifier + Qt.RightButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier + Qt.MiddleButton, QGLViewer.CAMERA, QGLViewer.ZOOM) else: if self.mode != NurbsPatchEditor.Rotate: self.mode = NurbsPatchEditor.Rotate self.setMouseBinding(Qt.ControlModifier + Qt.LeftButton, QGLViewer.FRAME, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.ControlModifier + Qt.RightButton, QGLViewer.FRAME, QGLViewer.NO_MOUSE_ACTION) self.setMouseBinding(Qt.LeftButton, QGLViewer.CAMERA, QGLViewer.ROTATE) self.setMouseBinding(Qt.RightButton, QGLViewer.CAMERA, QGLViewer.TRANSLATE) self.setMouseBinding(Qt.MiddleButton, QGLViewer.CAMERA, QGLViewer.ZOOM) def mouseDoubleClickEvent(self, event): """ mouseDoubleClickEvent: add a control point to the selection double clicking on it, empty the selection double clicking elsewhere """ if event.modifiers() & Qt.ShiftModifier: selection = False #Doubleclick on a control point add/delete it from the selection array# for ctrlPointRow in self.ctrlPointMatrix: for cCtrlPoint in ctrlPointRow: cCtrlPoint.checkIfGrabsMouse(event.x(), event.y(), self.camera()) if cCtrlPoint.grabsMouse(): cCtrlPoint.selected = not cCtrlPoint.selected selection = True self.createSelectionManipulator() self.selectionManipulator.toogleSelection(cCtrlPoint) #Double click with shift anywhere but on a control point -> empty selection # if not selection: self.clearSelectionManipulator() else: ## Adding a column on the NurbsPatch# """ when double click on a line, the line is doubled """ self.select( event.pos() ) # select call drwWithName that render line of control points with ids selection = self.selectedName() if selection >= 0: nbcolumn = self.nurbsPatch.ctrlPointMatrix.getColumnNb() minp, maxp = self.getBounds() diff = norm(maxp - minp) / 20 if selection >= nbcolumn: row = selection - nbcolumn newpatch = addRowToPatch(self.nurbsPatch, row, Vector4(diff, 0)) selectedPoints = [(row, i) for i in range(nbcolumn)] else: newpatch = addColumnToPatch(self.nurbsPatch, selection, Vector4(diff, 0)) selectedPoints = [(i, selection) for i in range( self.nurbsPatch.ctrlPointMatrix.getRowNb())] self.setNurbsPatch(newpatch) self.createSelectionManipulator() for index in selectedPoints: self.selectionManipulator.add( self.ctrlPointMatrix[index[0]][index[1]]) else: self.clearSelectionManipulator() def mouseMoveEvent(self, event): """ mouseMoveEvent """ #rectangular selection if self.selectionRect: self.__rectangle = QRect(self.__rectangleInit, event.pos()).normalized() self.updateGL() # by default camera or manipulated frame is manipulated QGLViewer.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): """On mouse release, we release every grabbed objects""" QGLViewer.mouseReleaseEvent(self, event) # clear manipulated object self.setManipulatedFrame(None) self.setFocus(None) if self.selectionManipulator: self.selectionManipulator.clearFocus() # end of rectangular selection : if self.selectionRect: self.selectionRect = False # Possibly swap left/right and top/bottom to make rectangle_ valid. self.__rectangle = QRect(self.__rectangleInit, event.pos()).normalized() # make rectangle with a minimal size if self.__rectangle.width() < 10 or self.__rectangle.height() < 10: self.__rectangle = QRect(event.pos().x() - 5, event.pos().y() - 5, 10, 10) self.selectionFromRect(self.__rectangle) self.setInteractionMode(False) self.updateGL() def selectionFromRect(self, rect): """ check if control point projections are in the given rectangle. If yes, put point in selection """ for ctrlPointRow in self.ctrlPointMatrix: for cCtrlPoint in ctrlPointRow: point = self.camera().projectedCoordinatesOf( cCtrlPoint.position()) if (self.__rectangle.contains(point.x, point.y)): if not self.selectionManipulator: self.selectionManipulator = SelectionManipulator() self.selectionManipulator.valueChanged.connect( self.__propagate_valuechanged__ ) # QObject.connect(self.selectionManipulator,SIGNAL("valueChanged()"),self.__propagate_valuechanged__) self.selectionManipulator.toogleSelection(cCtrlPoint) if self.selectionManipulator and self.selectionManipulator.empty(): self.clearSelectionManipulator() def draw(self): """ Draw function : Control points of the nurbsPatch are represented by spheres. If some of them are selected, a medium axis will be drawn by lines and little spheres, allowing us to grab one of them to perform a constrained move """ scradius = self.sceneRadius() pointsize = scradius / 30. shapes = [] #define the nurbsShape and the control points geometry #self.p=[] for ctrlPointLine in self.ctrlPointMatrix: for ctrlPoint in ctrlPointLine: shapes.append(ctrlPoint.representation(pointsize)) # draw the frame of multiple selection if self.selectionManipulator: shapes += self.selectionManipulator.representation(pointsize * 1.2) shapes.append(self.nurbsShape) sc = Scene(shapes) ogl.glDisable(ogl.GL_LIGHTING) ogl.glPolygonMode(ogl.GL_FRONT_AND_BACK, ogl.GL_LINE) # draw the wire of the patch ogl.glLineWidth(1) shwire = Shape(self.nurbsPatch, Material((120, 255, 0), 0.8)) shwire.apply(self.glrenderer) # ctrl line rendering ogl.glLineWidth(1) Material((255, 255, 255), 1).apply(self.glrenderer) self.nurbsPatch.apply(self.ctrlrenderer) # patch and control point shpere rendering ogl.glPolygonMode(ogl.GL_FRONT_AND_BACK, ogl.GL_FILL) ogl.glEnable(ogl.GL_LIGHTING) ogl.glLightModeli(ogl.GL_LIGHT_MODEL_TWO_SIDE, ogl.GL_TRUE) ogl.glEnable(ogl.GL_BLEND) ogl.glBlendFunc(ogl.GL_SRC_ALPHA, ogl.GL_ONE_MINUS_SRC_ALPHA) sc.apply(self.glrenderer) ogl.glDisable(ogl.GL_BLEND) if self.selectionRect: # Draw rectangular selection self.startScreenCoordinatesSystem() # use screen coordinates r = self.__rectangle rect = Shape( Polyline2D([(r.x(), r.y()), (r.x() + r.width(), r.y()), (r.x() + r.width(), r.y() + r.height()), (r.x(), r.y() + r.height()), (r.x(), r.y())]), Material((200, 200, 200), 1)) ogl.glEnable(ogl.GL_LINE_STIPPLE) ogl.glLineStipple(1, 0x0FFF) ogl.glLineWidth(2) rect.apply(self.glrenderer) ogl.glDisable(ogl.GL_LINE_STIPPLE) self.stopScreenCoordinatesSystem() ogl.glLineWidth(1) def drawWithNames(self): """ draw control lines with names """ pointmatrix = self.nurbsPatch.ctrlPointMatrix lines = [] m = Material() maxcolumn = pointmatrix.getColumnNb() for colid in range(maxcolumn): lines.append( Shape( Polyline(Point4Array( pointmatrix.getColumn(colid)).project(), width=3), m, colid)) for rowid in range(pointmatrix.getRowNb()): lines.append( Shape( Polyline(Point4Array(pointmatrix.getRow(rowid)).project(), width=3), m, maxcolumn + rowid)) for sh in lines: ogl.glPushName(sh.id) sh.apply(self.glrenderer) ogl.glPopName() @staticmethod def newDefaultNurbsPatch(): """ return a default nurbs patch """ return NurbsPatch([[Vector4(j - 1.5, i - 1.5, 0, 1) for j in range(4)] for i in range(4)]) def getNurbsPatch(self): """ return the edited nurbs patch """ return self.nurbsPatch def setNurbsPatch(self, nurbsPatch): """ set the nurbs patch to edit """ self.clear() self.nurbsPatch = nurbsPatch self.nurbsShape.geometry = self.nurbsPatch nbLines = float(len(self.nurbsPatch.ctrlPointMatrix)) pid = 1 for j, linePoint in enumerate(self.nurbsPatch.ctrlPointMatrix): lineCtrlPoint = [] nbCols = len(linePoint) for i in range(nbCols): ctrlPoint = CtrlPoint(linePoint[i].project(), Pos4Setter( self.nurbsPatch.ctrlPointMatrix, (j, i)), color=(30 + int(220 * j / nbLines), 30 + int(220 * i / nbCols), 250), id=pid) pid += 1 ctrlPoint.setCallBack(self.__propagate_valuechanged__) lineCtrlPoint.append(ctrlPoint) self.ctrlPointMatrix.append(lineCtrlPoint) self.setSceneBoundingBox(*self.getBounds()) def clear(self): """ clear current edition """ self.ctrlPointMatrix = [] self.clearSelectionManipulator() def helpString(self): return helpstr def closeEvent(self, event): """close the windows properly""" helpwidget = self.helpWidget() if helpwidget and helpwidget.isVisible(): helpwidget.hide() QGLViewer.closeEvent(self, event)