# $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