Source code for gui.draw

# $Id$
##
##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: http://pyformex.org
##  Project page:  http://savannah.nongnu.org/projects/pyformex/
##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""Create 3D graphical representations.

The draw module provides the basic user interface to the OpenGL
rendering capabilities of pyFormex. The full contents of this module
is available to scripts running in the pyFormex GUI without the need
to import it.
"""
from __future__ import absolute_import, division, print_function

import numpy as np

import pyformex as pf

from pyformex import utils
from pyformex.gui import widgets

# Things that we want in the scripting language
Dialog = widgets.InputDialog
_I = widgets.simpleInputItem
_G = widgets.groupInputItem
_T = widgets.tabInputItem
from pyformex.script import *
from pyformex.opengl.colors import *
from pyformex.gui.toolbar import timeout
from pyformex.gui import image


#################### Interacting with the user ###############################


[docs]def exitGui(res=0): """Terminate the GUI with a given status. """ print("Terminating the GUI with a value %s" % res) pf.app.closeAllWindows() pf.app.exit(res)
[docs]def closeGui(): """Close the GUI. Calling this function from a script closes the GUI and terminates pyFormex. """ pf.debug("Closing the GUI: currently, this will also terminate pyformex.", pf.DEBUG.GUI) pf.GUI.close()
[docs]def closeDialog(name): """Close the named dialog. Closes the InputDialog with the given name. If multiple dialogs are open with the same name, all these dialogs are closed. This only works for dialogs owned by the pyFormex GUI. """ pf.GUI.closeDialog(name)
[docs]def showMessage(text,actions=['OK'],level='info',modal=True,align='00',**kargs): """Show a short message widget and wait for user acknowledgement. There are three levels of messages: 'info', 'warning' and 'error'. They differ only in the icon that is shown next to the test. By default, the message widget has a single button with the text 'OK'. The dialog is closed if the user clicks a button. The return value is the button text. """ w = widgets.MessageBox(text,level=level,actions=actions,**kargs) if align == '--': w.move(100, 100) if modal: return w.getResults() else: w.show() return None
[docs]def showInfo(text,actions=['OK'],modal=True): """Show an informational message and wait for user acknowledgement.""" return showMessage(text, actions, 'info', modal)
[docs]def warning(text,actions=['OK']): """Show a warning message and wait for user acknowledgement.""" return showMessage(text, actions, 'warning')
[docs]def error(text,actions=['OK']): """Show an error message and wait for user acknowledgement.""" return showMessage(text, actions, 'error')
[docs]def ask(question,choices=None,**kargs): """Ask a question and present possible answers. Return answer if accepted or default if rejected. The remaining arguments are passed to the InputDialog getResult method. """ return showMessage(question,choices,'question',**kargs)
[docs]def ack(question,**kargs): """Show a Yes/No question and return True/False depending on answer.""" return ask(question,['No', 'Yes'],**kargs) == 'Yes'
[docs]def showText(text,itemtype='text',actions=[('OK', None)],modal=True,mono=False): """Display a text in a dialog window. Creates a dialog window displaying some text. The dialog can be modal (blocking user input to the main window) or modeless. Scrollbars are added if the text is too large to display at once. By default, the dialog has a single button to close the dialog. Parameters: - `text`: a multiline text to be displayed. It can be plain text or html or reStructuredText (starts with '..'). - `itemtype`: an InputItem type that can be used for text display. This should be either 'text' of 'info'. - `actions`: a list of action button definitions. - `modal`: bool: if True, a modal dialog is constructed. Else, the dialog is modeless. - `mono`: if True, a monospace font will be used. This is only useful for plain text, e.g. to show the output of an external command. Returns: :modal dialog: the result of the dialog after closing. The result is a dictionary with a single key: 'text' having the displayed text as a value. If an itemtype 'text' was used, this may be a changed text. :modeless dialog: the open dialog window itself. """ if mono: font = "DejaVu Sans Mono" else: font = None w = Dialog(size=(0.75, 0.75), items=[_I('text', text, itemtype=itemtype, text='', font=font, size=(-1, -1))], modal=modal, actions=actions, caption='pyFormex Text Display', ) if modal: return w.getResults() else: w.show() return w
[docs]def showFile(filename,mono=True,**kargs): """Display a text file. This will use the :func:`showText()` function to display a text read from a file. By default this uses a monospaced font. Other arguments may also be passed to ShowText. """ try: f = open(filename, 'r') except IOError: return showText(f.read(),mono=mono,**kargs) f.close()
[docs]def showDoc(obj=None,rst=True,modal=False): """Show the docstring of an object. Parameters: - `obj`: any object (module, class, method, function) that has a __doc__ attribute. If None is specified, the docstring of the current application is shown. - `rst`: bool. If True (default) the docstring is treated as being reStructuredText and will be nicely formatted accordingly. If False, the docstring is shown as plain text. """ text = None if obj is None: if not pf.GUI.canPlay: return obj = pf.prefcfg['curfile'] if utils.is_script(obj): #print "obj is a script" from pyformex.utils import getDocString text = getDocString(obj) obj = None else: from pyformex import apps obj = apps.load(obj) if obj: text = obj.__doc__ if text is None: raise ValueError("No documentation found for object %s" % obj) text = utils.forceReST(text, underline=True) if pf.GUI.doc_dialog is None: if modal: actions=[('OK', None)] else: actions = [('Close', pf.GUI.close_doc_dialog)] pf.GUI.doc_dialog = showText(text, actions=actions, modal=modal) else: # # TODO: check why needed: without sometimes fails # RuntimeError: wrapped C/C++ object of %S has been deleted # probably when runall? # try: pf.GUI.doc_dialog.updateData({'text':text}) # pf.GUI.doc_dialog.show() pf.GUI.doc_dialog.raise_() pf.GUI.doc_dialog.update() pf.app.processEvents() except: pass
[docs]def editFile(fn,exist=False): """Load a file into the editor. Parameters: - `fn`: filename. The corresponding file is loaded into the editor. - `exist`: bool. If True, only existing filenames will be accepted. Loading a file in the editor is done by executing an external command with the filename as argument. The command to be used can be set in the configuration. If none is set, pyFormex will try to lok at the `EDITOR` and `VISUAL` environment settings. The main author of pyFormex uses 'emacsclient' as editor command, to load the files in a running copy of Emacs. """ print("Edit File: %s" % fn) if pf.cfg['editor']: if exist and not os.path.exists(fn): return utils.system('%s %s' % (pf.cfg['editor'], fn),wait=False) else: warning('No known editor was found or configured')
# widget and result status of the widget in askItems() function _dialog_widget = None _dialog_result = None
[docs]def askItems(items,timeout=None,**kargs): """Ask the value of some items to the user. Create an interactive widget to let the user set the value of some items. The items are specified as a list of dictionaries. Each dictionary contains the input arguments for a widgets.InputItem. It is often convenient to use one of the _I, _G, ot _T functions to create these dictionaries. These will respectively create the input for a simpleInputItem, a groupInputItem or a tabInputItem. For convenience, simple items can also be specified as a tuple. A tuple (key,value) will be transformed to a dict {'key':key, 'value':value}. See the widgets.InputDialog class for complete description of the available input items. A timeout (in seconds) can be specified to have the input dialog interrupted automatically and return the default values. The remaining arguments are keyword arguments that are passed to the widgets.InputDialog.getResult method. Returns a dictionary with the results: for each input item there is a (key,value) pair. Returns an empty dictionary if the dialog was canceled. Sets the dialog timeout and accepted status in global variables. """ global _dialog_widget, _dialog_result w = widgets.InputDialog(items,**kargs) _dialog_widget = w _dialog_result = None res = w.getResults(timeout) _dialog_widget = None _dialog_result = w.result() return res
[docs]def currentDialog(): """Returns the current dialog widget. This returns the dialog widget created by the askItems() function, while the dialog is still active. If no askItems() has been called or if the user already closed the dialog, None is returned. """ return _dialog_widget
[docs]def dialogAccepted(): """Returns True if the last askItems() dialog was accepted.""" return _dialog_result == widgets.ACCEPTED
[docs]def dialogRejected(): """Returns True if the last askItems() dialog was rejected.""" return _dialog_result == widgets.REJECTED
[docs]def dialogTimedOut(): """Returns True if the last askItems() dialog timed out.""" return _dialog_result == widgets.TIMEOUT
[docs]def askFile(cur=None,filter='all',exist=True,multi=False,compr=False,change=True,timeout=None,caption=None,sidebar=None): """Ask for a file name or multiple file names using a file dialog. Parameters: - `cur`: directory or filename. Specifies the starting point of the selection dialog. All the files in the specified directory (or the file's directory) matching the `filter` will be presented to the user. If cur is a file, it will be set as the initial selection. - `filter`: string or list of strings. Specifies a (set of) filter(s) to be applied on the files in the selected directory. This allows to narrow down the selection possibilities. The `filter` argument is passed through the :func:`utils.fileDescription` function to create the actual filter set. If multiple filters are included, the user can select the appropriate one from the dialog. - `exist`: bool: if True, the filename must exist. The default (False) will allow a new file to be created or an existing to be used. - `multi`: bool. If True, allows the user to pick multiple file names in a single operation. - `compr`: bool. If True, the specified pattern will be extended with the corresponding compressed file types. - `change`: bool. If True (default), the current working directory will be changed to the parent directory of the selection. - `caption`: string. This string will be displayed as the dialog title instead of the default one. - `timeout`: float. If specified, the dialog will timeout after the specified number of seconds. - `caption`: string. If specified, it will be displayed as the FileDialog title. - `sidebar`: list of paths. If specified, these will be added to the sidebar (in addition to the configured paths). Returns the result of the file dialog. If the user accepted the selection, this will be a Dict with at least a key 'fn' holding the selected filename(s): a single file name is if `multi` is False, or a list of file names if `multi` is True. If the user canceled the selection process, an empty dict is returned. """ if cur is None: cur = pf.cfg['workdir'] if os.path.isdir(cur): fn = '' else: fn = os.path.basename(cur) cur = os.path.dirname(cur) if filter == 'pgf': w = widgets.GeometryFileDialog(cur, filter, exist, compr=compr, caption=caption,sidebar=sidebar) else: w = widgets.FileDialog(cur, filter, exist, multi=multi, compr=compr, caption=caption, sidebar=sidebar) if fn: w.selectFile(fn) res = w.getResults(timeout) if res: fn = res.fn fs = w.selectedNameFilter() if not exist and not multi: # Check and force extension for single new file okext = utils.fileExtensionsFromFilter(fs) print("Accepted extensions: %s" % okext) ok = False for ext in okext: if fn.endswith(ext): ok = True break if not ok: fn += okext[0] res['fn'] = fn if fn and change: if multi: cur = fn[0] else: cur = fn cur = os.path.dirname(cur) chdir(cur) pf.GUI.update() pf.app.processEvents() return res
[docs]def askFilename(*args,**kargs): """Ask for a file name or multiple file names using a file dialog. This functions takes the same parameters as :func:`askFile`, and is functionally equivalent to it. However, in case of an accepted dialog it only returns the filename(s) and in case of a canceled dialog it returns None. """ res = askFile(*args,**kargs) if res: return res['fn'] else: return None
[docs]def askNewFilename(cur=None,filter="All files (*.*)",compr=False,timeout=None,caption=None,sidebar=None): """Ask a single new filename. This is a convenience function for calling askFilename with the arguments exist=False. """ return askFilename(cur=cur, filter=filter, exist=False, multi=False, compr=compr, timeout=timeout, caption=caption, sidebar=sidebar)
[docs]def askDirname(path=None,change=True,byfile=False,caption=None): """Interactively select a directory and change the current workdir. The user is asked to select a directory through the standard file dialog. Initially, the dialog shows all the subdirectories in the specified path, or by default in the current working directory. The selected directory becomes the new working directory, unless the user canceled the operation, or the change parameter was set to False. """ if path is None: path = pf.cfg['workdir'] if not os.path.isdir(path): path = os.path.dirname(path) if byfile: dirmode = 'auto' else: dirmode = True fn = widgets.FileDialog(path, '*', dir=dirmode,caption=caption).getFilename() if fn: if not os.path.isdir(fn): fn = os.path.dirname(fn) if change: chdir(fn) pf.GUI.update() pf.app.processEvents() return fn
def askImageFile(fn=None,compr=False): if not fn: fn = pf.cfg['pyformexdir'] return askFilename(fn, filter=['img', 'all'], multi=False, exist=True)
[docs]def checkWorkdir(): """Ask the user to change the current workdir if it is not writable. Returns True if the new workdir is writable. """ workdir = os.getcwd() ok = os.access(workdir, os.W_OK) if not ok: warning("Your current working directory (%s) is not writable. Change your working directory to a path where you have write permission." % workdir) askDirname() ok = os.access(os.getcwd(), os.W_OK) return ok
logfile = None # the log file
[docs]def printMessage(s,**kargs): """Print a message on the message board. Parameters: - `s`: string to print - `kargs`: more keyword arguments are passed to meth:`MessageBpard.write`. This function forces an update of the GUI, so that the output message is guaranteed to be visible. If a logfile was opened, the message is also written to the log file. """ if logfile is not None: logfile.write(str(s)+'\n') pf.GUI.board.write(str(s),**kargs) pf.GUI.update() pf.app.processEvents()
[docs]def delay(s=None): """Get/Set the draw delay time. Returns the current setting of the draw wait time (in seconds). This drawing delay is obeyed by drawing and viewing operations. A parameter may be given to set the delay time to a new value. It should be convertable to a float. The function still returns the old setting. This may be practical to save that value to restore it later. """ saved = pf.GUI.drawwait if s is not None: pf.GUI.drawwait = float(s) return saved
[docs]def wait(relock=True): """Wait until the drawing lock is released. This uses the drawing lock mechanism to pause. The drawing lock ensures that subsequent draws are retarded to give the user the time to view. The use of this function is prefered over that of :func:`pause` or :func:`sleep`, because it allows your script to continue the numerical computations while waiting to draw the next screen. This function can be used to retard other functions than `draw` and `view`. """ pf.GUI.drawlock.wait() if relock: pf.GUI.drawlock.lock()
# Functions corresponding with control buttons
[docs]def play(refresh=False): """Start the current script or if already running, continue it. """ if len(pf.scriptlock) > 0: # An application is running if pf.GUI.drawlock.locked: pf.GUI.drawlock.release() else: # Start current application runAny(refresh=refresh)
[docs]def replay(): """Replay the current app. This works pretty much like the play() function, but will reload the current application prior to running it. This function is especially interesting during development of an application. If the current application is a script, then it is equivalent with play(). """ appname = pf.cfg['curfile'] play(refresh=utils.is_app(appname))
[docs]def fforward(): """Releases the drawing lock mechanism indefinely. Releasing the drawing lock indefinely means that the lock will not be set again and your script will execute till the end. """ pf.GUI.drawlock.free()
# # IDEA: The pause() could display a progress bar showing how much time # is left in the pause, # maybe also with buttons to repeat, pause indefinitely, ... #
[docs]def pause(timeout=None,msg=None): """Pause the execution until an external event occurs or timeout. When the pause statement is executed, execution of the pyformex script is suspended until some external event forces it to proceed again. Clicking the PLAY, STEP or CONTINUE button will produce such an event. - `timeout`: float: if specified, the pause will only last for this many seconds. It can still be interrupted by the STEP buttons. - `msg`: string: a message to write to the board to explain the user about the pause """ from pyformex.gui.drawlock import Repeater def _continue_(): return not pf.GUI.drawlock.locked if msg is None and timeout is None: msg = "Use the Play/Step/Continue button to proceed" pf.debug("Pause (%s): %s" % (timeout, msg), pf.DEBUG.SCRIPT) if msg: print(msg) pf.GUI.enableButtons(pf.GUI.actions, ['Step', 'Continue'], True) pf.GUI.drawlock.release() if pf.GUI.drawlock.allowed: pf.GUI.drawlock.locked = True if timeout is None: timeout = widgets.input_timeout R = Repeater(_continue_, timeout, sleep=0.1) R.start() pf.GUI.drawlock.release()
################### EXPERIMENTAL STUFF: AVOID! ############### def sleep(duration,granularity=0.01): from pyformex.gui.drawlock import Repeater R = Repeater(None, duration, sleep=granularity) R.start() ########################## print information ################################ def printbbox(): print(pf.canvas.bbox) def printviewportsettings(): pf.GUI.viewports.printSettings() def reportCamera(): print(pf.canvas.camera.report()) #################### camera ################################## def zoom_factor(factor=None): if factor is None: factor = pf.cfg['gui/zoomfactor'] return float(factor) def pan_factor(factor=None): if factor is None: factor = pf.cfg['gui/panfactor'] return float(factor) def rot_factor(factor=None): if factor is None: factor = pf.cfg['gui/rotfactor'] return float(factor) def zoomIn(factor=None): pf.canvas.camera.zoomArea(1./zoom_factor(factor)) pf.canvas.update() def zoomOut(factor=None): pf.canvas.camera.zoomArea(zoom_factor(factor)) pf.canvas.update() def panRight(factor=None): pf.canvas.camera.transArea(-pan_factor(factor), 0.) pf.canvas.update() def panLeft(factor=None): pf.canvas.camera.transArea(pan_factor(factor), 0.) pf.canvas.update() def panUp(factor=None): pf.canvas.camera.transArea(0., -pan_factor(factor)) pf.canvas.update() def panDown(factor=None): pf.canvas.camera.transArea(0., pan_factor(factor)) pf.canvas.update() def rotRight(factor=None): pf.canvas.camera.rotate(rot_factor(factor), 0, 1, 0) pf.canvas.update() def rotLeft(factor=None): pf.canvas.camera.rotate(-rot_factor(factor), 0, 1, 0) pf.canvas.update() def rotUp(factor=None): pf.canvas.camera.rotate(-rot_factor(factor), 1, 0, 0) pf.canvas.update() def rotDown(factor=None): pf.canvas.camera.rotate(rot_factor(factor), 1, 0, 0) pf.canvas.update() def twistLeft(factor=None): pf.canvas.camera.rotate(rot_factor(factor), 0, 0, 1) pf.canvas.update() def twistRight(factor=None): pf.canvas.camera.rotate(-rot_factor(factor), 0, 0, 1) pf.canvas.update() def barrelRoll(n=36): d = 360./n t = 2./n for i in range(n): twistRight(d) sleep(t) def transLeft(factor=None): val = pan_factor(factor) * pf.canvas.camera.dist pf.canvas.camera.translate(-val, 0, 0, pf.cfg['draw/localaxes']) pf.canvas.update() def transRight(factor=None): val = pan_factor(factor) * pf.canvas.camera.dist pf.canvas.camera.translate(+val, 0, 0, pf.cfg['draw/localaxes']) pf.canvas.update() def transDown(factor=None): val = pan_factor(factor) * pf.canvas.camera.dist pf.canvas.camera.translate(0, -val, 0, pf.cfg['draw/localaxes']) pf.canvas.update() def transUp(factor=None): val = pan_factor(factor) * pf.canvas.camera.dist pf.canvas.camera.translate(0, +val, 0, pf.cfg['draw/localaxes']) pf.canvas.update() def dollyIn(factor=None): pf.canvas.camera.dolly(1./zoom_factor(factor)) pf.canvas.update() def dollyOut(factor=None): pf.canvas.camera.dolly(zoom_factor(factor)) pf.canvas.update() def lockCamera(): pf.canvas.camera.lock() def unlockCamera(): pf.canvas.camera.lock(False)
[docs]def zoomRectangle(): """Zoom a rectangle selected by the user.""" pf.canvas.zoom_rectangle() pf.canvas.update()
[docs]def getRectangle(): """Zoom a rectangle selected by the user.""" r = pf.canvas.get_rectangle() print(r) pf.canvas.update()
[docs]def zoomBbox(bb): """Zoom thus that the specified bbox becomes visible.""" pf.canvas.setCamera(bbox=bb) pf.canvas.update()
[docs]def zoomObj(object): """Zoom thus that the specified object becomes visible. object can be anything having a bbox() method or a list thereof. """ zoomBbox(coords.bbox(object))
[docs]def zoomAll(): """Zoom thus that all actors become visible.""" zoomBbox(pf.canvas.sceneBbox())
# Can this be replaced with zoomIn/Out?
[docs]def zoom(f): """Zoom with a factor f A factor > 1.0 zooms out, a factor < 1.0 zooms in. """ pf.canvas.zoom(f) pf.canvas.update()
[docs]def focus(point): """Move the camera focus to the specified point. Parameters: - `point`: float(3,) or alike The camera focus is set to the specified point, while keeping a parallel camera direction and same zoom factor. The specified point becomes the center of the screen and the center of camera rotations. """ pf.canvas.camera.focus = point pf.canvas.camera.setArea(0.,0.,1.,1.,True,center=True) pf.canvas.update()
[docs]def flyAlong(path,upvector=[0., 1., 0.],sleeptime=None): """Fly through the current scene along the specified path. - `path`: a plex-2 or plex-3 Formex (or convertibel to such Formex) specifying the paths of camera eye and center (and upvector). - `upvector`: the direction of the vertical axis of the camera, in case of a 2-plex camera path. - `sleeptime`: a delay between subsequent images, to slow down the camera movement. This function moves the camera through the subsequent elements of the Formex. For each element the first point is used as the center of the camera and the second point as the eye (the center of the scene looked at). For a 3-plex Formex, the third point is used to define the upvector (i.e. the vertical axis of the image) of the camera. For a 2-plex Formex, the upvector is constant as specified in the arguments. """ try: if not isinstance(path, Formex): path = path.toFormex() if not path.nplex() in (2, 3): raise ValueError except: raise ValueError("The camera path should be (convertible to) a plex-2 or plex-3 Formex!") nplex = path.nplex() if sleeptime is None: sleeptime = pf.cfg['draw/flywait'] saved = delay(sleeptime) saved1 = pf.GUI.actions['Continue'].isEnabled() pf.GUI.enableButtons(pf.GUI.actions, ['Continue'], True) for elem in path: eye, center = elem[:2] if nplex == 3: upv = elem[2] - center else: upv = upvector pf.canvas.camera.lookAt(eye, center, upv) wait() pf.canvas.display() pf.canvas.update() image.saveNext() delay(saved) pf.GUI.enableButtons(pf.GUI.actions, ['Continue'], saved1) pf.canvas.camera.focus = center pf.canvas.camera.dist = length(center-eye) pf.canvas.update()
#################### viewports ################################## ### BEWARE FOR EFFECTS OF SPLITTING pf.canvas and pf.canvas if these ### are called from interactive functions!
[docs]def viewport(n=None): """Select the current viewport. n is an integer number in the range of the number of viewports, or is one of the viewport objects in pyformex.GUI.viewports if n is None, selects the current GUI viewport for drawing """ if n is not None: pf.canvas.update() pf.GUI.viewports.setCurrent(n) pf.canvas = pf.GUI.viewports.current
[docs]def nViewports(): """Return the number of viewports.""" return len(pf.GUI.viewports.all)
[docs]def layout(nvps=None,ncols=None,nrows=None,pos=None,rstretch=None,cstretch=None): """Set the viewports layout.""" pf.GUI.viewports.changeLayout(nvps, ncols, nrows, pos, rstretch, cstretch) viewport()
[docs]def addViewport(): """Add a new viewport.""" pf.GUI.viewports.addView() viewport()
[docs]def removeViewport(): """Remove the last viewport.""" if nViewports() > 1: pf.GUI.viewports.removeView() viewport()
[docs]def linkViewport(vp, tovp): """Link viewport vp to viewport tovp. Both vp and tovp should be numbers of viewports. """ pf.GUI.viewports.link(vp, tovp) viewport()
####################
[docs]def updateGUI(): """Update the GUI.""" pf.GUI.update() pf.canvas.update() pf.app.processEvents()
######### Highlighting ############### # # Most highlight functions have been moved to canvas.py # They are retained here for compatibility, but should be deprecated #
[docs]def highlightActor(actor): """Highlight an actor in the scene.""" pf.canvas.highlightActor(actor) pf.canvas.update()
[docs]def removeHighlight(): """Remove the highlights from the current viewport""" pf.canvas.removeHighlight() pf.canvas.update()
[docs]def pick(mode='actor',filter=None,oneshot=False,func=None,pickable=None,prompt=None): """Enter interactive picking mode and return selection. See :func:`Canvas.pick` for more details. This function differs in that it provides default highlighting during the picking operation and a OK/Cancel buttons to stop the picking operation. Parameters: - `mode`: defines what to pick : one of 'actor', 'element', 'face', 'edge', 'point' or 'prop'. 'actor' picks complete actors. 'element' picks elements from one or more actor(s). 'face' and 'edge' pick faces, resp. edges of elements (only available for Mesh objects). 'point' picks points of Formices or nodes of Meshes. 'prop' is like 'element', but returns the property numbers of the picked elements instead of the element numbers. - `filter`: one of the `selection_filters`. The default picking filter is activated on entering the pick mode. All available filters are presented in a combobox. - `oneshot`: if True, the function returns as soon as the user ends a picking operation. The default is to let the user modify his selection and only to return after an explicit cancel (ESC or right mouse button). - `func`: if specified, this function will be called after each atomic pick operation. The Collection with the currently selected objects is passed as an argument. This can e.g. be used to highlight the selected objects during picking. - `pickable`: a list of Actors to pick from. The default is to use a list with all Actors having the pickable=True attribute (which is the default for newly constructed Actors). - `prompt`: the text printed to prompt the user to start picking. If None, a default prompt is printed. Specify an empty string to avoid printing a prompt. Returns a (possibly empty) Collection with the picked items. After return, the value of the pf.canvas.selection_accepted variable can be tested to find how the picking operation was exited: True means accepted (right mouse click, ENTER key, or OK button), False means canceled (ESC key, or Cancel button). In the latter case, the returned Collection is always empty """ if mode == 'prop': return pickProps(filter,oneshot,func,pickable,prompt) subsel_values = { 'any': 'any vertex', 'all': 'all vertices' } def _set_selection_filter(s): """Set the selection filter mode This function is used to change the selection filter from the selection InputCombo widget. s is one of the strings in selection_filters. """ s = str(s) if pf.canvas.pick_mode is not None and s in pf.canvas.selection_filters: pf.canvas.start_selection(None, s) def _set_subsel_mode(val): """Toggle the value of the subsel mode""" pf.canvas.pick_mode_subsel = str(val)[:3] if pf.canvas.pick_mode is not None: warning("You need to finish the previous picking operation first!") return if mode not in pf.canvas.getPickModes(): warning("Invalid picking mode: %s. Expected one of %s." % (mode, pf.canvas.getPickModes())) return pick_buttons = widgets.ButtonBox('Selection:', [('Cancel', pf.canvas.cancel_selection), ('OK', pf.canvas.accept_selection)]) if mode == 'element': filters = pf.canvas.selection_filters else: filters = pf.canvas.selection_filters[:3] filter_combo = widgets.InputCombo('Filter:', None, choices=filters, onselect=_set_selection_filter) if filter is not None and filter in pf.canvas.selection_filters: filter_combo.setValue(filter) if mode in [ 'actor', 'element', 'face', 'edge' ]: txt = subsel_values[pf.canvas.pick_mode_subsel] #subsel_button = widgets.ButtonBox('Pick by ', [(txt, _toggle_subsel_mode), ]) subsel_button = widgets.InputCombo('Pick by ', txt, choices=list(subsel_values.values()), onselect=_set_subsel_mode) else: subsel_button = None if func is None: func = pf.canvas.highlight_funcs.get(mode, None) if prompt is None: prompt = "Pick: Mode %s; Filter %s" % (mode,filter) if prompt: print(prompt) pf.GUI.statusbar.addWidget(pick_buttons) pf.GUI.statusbar.addWidget(filter_combo) if subsel_button: pf.GUI.statusbar.addWidget(subsel_button) try: sel = pf.canvas.pick(mode, oneshot, func, filter, pickable) finally: # cleanup if pf.canvas.pick_mode is not None: pf.canvas.finish_selection() pf.GUI.statusbar.removeWidget(pick_buttons) pf.GUI.statusbar.removeWidget(filter_combo) if subsel_button: pf.GUI.statusbar.removeWidget(subsel_button) return sel
# These are undocumented, and deprecated: use pick() instead def pickActors(filter=None,oneshot=False,func=None): return pick('actor', filter, oneshot, func) def pickElements(filter=None,oneshot=False,func=None): return pick('element', filter, oneshot, func) def pickPoints(filter=None,oneshot=False,func=None): return pick('point', filter, oneshot, func) def pickEdges(filter=None,oneshot=False,func=None): return pick('edge', filter, oneshot, func) # This could probably be moved into the canvas picking functions
[docs]def pickProps(filter=None,oneshot=False,func=None,pickable=None,prompt=None): """Pick property numbers This is like pick('element'), but returns the (unique) property numbers of the picked elements of the actors instead. """ C = pick('element', filter, oneshot, func) for a in C.keys(): actor = pf.canvas.actors[a] object = actor.object elems = C[a] if hasattr(object,'prop') and object.prop is not None: # Replace elem ids with unique props C[a] = np.unique(object.prop[elems]) else: # Actor with no props: delete it del C.d[a] C.setType('prop') return C
[docs]def pickNumbers(marks=None): """Pick drawn numbers""" if marks: pf.canvas.numbers = marks return pf.canvas.pickNumbers()
[docs]def pickFocus(): """Enter interactive focus setting. This enters interactive point picking mode and sets the focus to the center of the picked points. """ K = pick('point',oneshot=True) removeHighlight() if K: X = [] for k in K: a = pf.canvas.actors[k] o = a.object x = o.points()[K[k]] X.append(x.center()) X = Coords(X).center() focus(X)
LineDrawing = None edit_modes = ['undo', 'clear', 'close']
[docs]def set_edit_mode(s): """Set the drawing edit mode.""" s = str(s) if s in edit_modes: pf.canvas.edit_drawing(s)
[docs]def drawLinesInter(mode ='line',single=False,func=None): """Enter interactive drawing mode and return the line drawing. See viewport.py for more details. This function differs in that it provides default displaying during the drawing operation and a button to stop the drawing operation. The drawing can be edited using the methods 'undo', 'clear' and 'close', which are presented in a combobox. """ if pf.canvas.drawing_mode is not None: warning("You need to finish the previous drawing operation first!") return if func is None: func = showLineDrawing drawing_buttons = widgets.ButtonBox('Drawing:', [('Cancel', pf.canvas.cancel_drawing), ('OK', pf.canvas.accept_drawing)]) pf.GUI.statusbar.addWidget(drawing_buttons) edit_combo = widgets.InputCombo('Edit:', None, choices=edit_modes, onselect=set_edit_mode) pf.GUI.statusbar.addWidget(edit_combo) lines = pf.canvas.drawLinesInter(mode, single, func) pf.GUI.statusbar.removeWidget(drawing_buttons) pf.GUI.statusbar.removeWidget(edit_combo) return lines
[docs]def showLineDrawing(L): """Show a line drawing. L is usually the return value of an interactive draw operation, but might also be set by the user. """ global LineDrawing if LineDrawing: undecorate(LineDrawing) LineDrawing = None if L.size != 0: LineDrawing = decors.Lines(L, color='yellow', linewidth=3) decorate(LineDrawing)
[docs]def exportWebGL(fn,createdby=50,**kargs): """Export the current scene to WebGL. Parameters: - `fn` : string: the (relative or absolute) filename of the .html, .js and .pgf files comprising the WebGL model. It can contain a directory path and an any extension. The latter is dropped and not used. - `createdby`: int: width in pixels of the 'Created by pyFormex' logo appearing on the page. If < 0, the logo is displayed at its natural width. If 0, the logo is suppressed. - `**kargs`: any other keyword parameteris passed to the :class:`WebGL` initialization. The `name` can not be specified: it is derived from the `fn` parameter. Returns the absolute pathname of the generated .html file. """ from pyformex.plugins.webgl import WebGL print("Exporting current scene to %s" % fn) pf.GUI.setBusy() if os.path.isabs(fn): chdir(os.path.dirname(fn)) fn = os.path.basename(fn) name = utils.projectName(fn) W = WebGL(name=name,**kargs) W.addScene(name) fn = W.export(createdby=createdby) pf.GUI.setBusy(False) return fn
the_multiWebGL = None
[docs]def multiWebGL(name=None,fn=None,title=None,description=None,keywords=None,author=None,createdby=50): """Export the current scene to WebGL. fn is the (relative or absolute) pathname of the .html and .js files to be created. When the export is finished, returns the absolute pathname of the generated .html file. Else, returns None. """ global the_multiWebGL from pyformex.plugins.webgl import WebGL ret = None pf.GUI.setBusy() if fn is not None: if the_multiWebGL is not None: the_multiWebGL.export() the_multiWebGL = None print("OK",the_multiWebGL) if os.path.isabs(fn): chdir(os.path.dirname(fn)) fn = os.path.basename(fn) proj = utils.projectName(fn) print("PROJECT %s" % proj) the_multiWebGL = WebGL(proj, title=title, description=description, keywords=keywords, author=author) if the_multiWebGL is not None: if name is not None: print("Exporting current scene to %s" % the_multiWebGL.name) the_multiWebGL.addScene(name) elif fn is None: # No name, and not just starting print("Finishing export of %s" % the_multiWebGL.name) ret = the_multiWebGL.export(createdby=createdby) the_multiWebGL = None pf.GUI.setBusy(False) return ret
[docs]def showURL(url): """Show an URL in the browser - `url`: url to load """ from pyformex.gui.menus.Help import help help(url)
[docs]def showHTML(fn=None): """Show a local .html file in the browser - `fn`: name of a local .html file. If unspecified, a FileDialog dialog is popped up to select a file. """ if not fn: fn = askFilename(filter='html') if fn: showURL('file:%s' % fn)
################################ def setLocalAxes(mode=True): pf.cfg['draw/localaxes'] = mode def setGlobalAxes(mode=True): setLocalAxes(not mode)
[docs]def resetGUI(): """Reset the GUI to its default operating mode. When an exception is raised during the execution of a script, the GUI may be left in a non-consistent state. This function may be called to reset most of the GUI components to their default operating mode. """ ## resetPick() pf.GUI.resetCursor() pf.GUI.enableButtons(pf.GUI.actions, ['Play', 'Step'], True) pf.GUI.enableButtons(pf.GUI.actions, ['Continue', 'Stop'], False)
########################################################################### # import opengl specific drawing functions # from pyformex.opengl.draw import * ########################################################################### # Make _I, _G and _T be included when doing 'from gui.draw import *' # __all__ = [ n for n in list(globals().keys()) if not n.startswith('_')] + ['_I', '_G', '_T'] #### End