123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- from threading import RLock
- # it is sufficient to import "pyglet" here once
- try:
- import pyglet.gl as pgl
- except ImportError:
- raise ImportError("pyglet is required for plotting.\n "
- "visit http://www.pyglet.org/")
- from sympy.core.numbers import Integer
- from sympy.external.gmpy import SYMPY_INTS
- from sympy.geometry.entity import GeometryEntity
- from sympy.plotting.pygletplot.plot_axes import PlotAxes
- from sympy.plotting.pygletplot.plot_mode import PlotMode
- from sympy.plotting.pygletplot.plot_object import PlotObject
- from sympy.plotting.pygletplot.plot_window import PlotWindow
- from sympy.plotting.pygletplot.util import parse_option_string
- from sympy.utilities.decorator import doctest_depends_on
- from sympy.utilities.iterables import is_sequence
- from time import sleep
- from os import getcwd, listdir
- import ctypes
- @doctest_depends_on(modules=('pyglet',))
- class PygletPlot:
- """
- Plot Examples
- =============
- See examples/advanced/pyglet_plotting.py for many more examples.
- >>> from sympy.plotting.pygletplot import PygletPlot as Plot
- >>> from sympy.abc import x, y, z
- >>> Plot(x*y**3-y*x**3)
- [0]: -x**3*y + x*y**3, 'mode=cartesian'
- >>> p = Plot()
- >>> p[1] = x*y
- >>> p[1].color = z, (0.4,0.4,0.9), (0.9,0.4,0.4)
- >>> p = Plot()
- >>> p[1] = x**2+y**2
- >>> p[2] = -x**2-y**2
- Variable Intervals
- ==================
- The basic format is [var, min, max, steps], but the
- syntax is flexible and arguments left out are taken
- from the defaults for the current coordinate mode:
- >>> Plot(x**2) # implies [x,-5,5,100]
- [0]: x**2, 'mode=cartesian'
- >>> Plot(x**2, [], []) # [x,-1,1,40], [y,-1,1,40]
- [0]: x**2, 'mode=cartesian'
- >>> Plot(x**2-y**2, [100], [100]) # [x,-1,1,100], [y,-1,1,100]
- [0]: x**2 - y**2, 'mode=cartesian'
- >>> Plot(x**2, [x,-13,13,100])
- [0]: x**2, 'mode=cartesian'
- >>> Plot(x**2, [-13,13]) # [x,-13,13,100]
- [0]: x**2, 'mode=cartesian'
- >>> Plot(x**2, [x,-13,13]) # [x,-13,13,10]
- [0]: x**2, 'mode=cartesian'
- >>> Plot(1*x, [], [x], mode='cylindrical')
- ... # [unbound_theta,0,2*Pi,40], [x,-1,1,20]
- [0]: x, 'mode=cartesian'
- Coordinate Modes
- ================
- Plot supports several curvilinear coordinate modes, and
- they independent for each plotted function. You can specify
- a coordinate mode explicitly with the 'mode' named argument,
- but it can be automatically determined for Cartesian or
- parametric plots, and therefore must only be specified for
- polar, cylindrical, and spherical modes.
- Specifically, Plot(function arguments) and Plot[n] =
- (function arguments) will interpret your arguments as a
- Cartesian plot if you provide one function and a parametric
- plot if you provide two or three functions. Similarly, the
- arguments will be interpreted as a curve if one variable is
- used, and a surface if two are used.
- Supported mode names by number of variables:
- 1: parametric, cartesian, polar
- 2: parametric, cartesian, cylindrical = polar, spherical
- >>> Plot(1, mode='spherical')
- Calculator-like Interface
- =========================
- >>> p = Plot(visible=False)
- >>> f = x**2
- >>> p[1] = f
- >>> p[2] = f.diff(x)
- >>> p[3] = f.diff(x).diff(x)
- >>> p
- [1]: x**2, 'mode=cartesian'
- [2]: 2*x, 'mode=cartesian'
- [3]: 2, 'mode=cartesian'
- >>> p.show()
- >>> p.clear()
- >>> p
- <blank plot>
- >>> p[1] = x**2+y**2
- >>> p[1].style = 'solid'
- >>> p[2] = -x**2-y**2
- >>> p[2].style = 'wireframe'
- >>> p[1].color = z, (0.4,0.4,0.9), (0.9,0.4,0.4)
- >>> p[1].style = 'both'
- >>> p[2].style = 'both'
- >>> p.close()
- Plot Window Keyboard Controls
- =============================
- Screen Rotation:
- X,Y axis Arrow Keys, A,S,D,W, Numpad 4,6,8,2
- Z axis Q,E, Numpad 7,9
- Model Rotation:
- Z axis Z,C, Numpad 1,3
- Zoom: R,F, PgUp,PgDn, Numpad +,-
- Reset Camera: X, Numpad 5
- Camera Presets:
- XY F1
- XZ F2
- YZ F3
- Perspective F4
- Sensitivity Modifier: SHIFT
- Axes Toggle:
- Visible F5
- Colors F6
- Close Window: ESCAPE
- =============================
- """
- @doctest_depends_on(modules=('pyglet',))
- def __init__(self, *fargs, **win_args):
- """
- Positional Arguments
- ====================
- Any given positional arguments are used to
- initialize a plot function at index 1. In
- other words...
- >>> from sympy.plotting.pygletplot import PygletPlot as Plot
- >>> from sympy.abc import x
- >>> p = Plot(x**2, visible=False)
- ...is equivalent to...
- >>> p = Plot(visible=False)
- >>> p[1] = x**2
- Note that in earlier versions of the plotting
- module, you were able to specify multiple
- functions in the initializer. This functionality
- has been dropped in favor of better automatic
- plot plot_mode detection.
- Named Arguments
- ===============
- axes
- An option string of the form
- "key1=value1; key2 = value2" which
- can use the following options:
- style = ordinate
- none OR frame OR box OR ordinate
- stride = 0.25
- val OR (val_x, val_y, val_z)
- overlay = True (draw on top of plot)
- True OR False
- colored = False (False uses Black,
- True uses colors
- R,G,B = X,Y,Z)
- True OR False
- label_axes = False (display axis names
- at endpoints)
- True OR False
- visible = True (show immediately
- True OR False
- The following named arguments are passed as
- arguments to window initialization:
- antialiasing = True
- True OR False
- ortho = False
- True OR False
- invert_mouse_zoom = False
- True OR False
- """
- # Register the plot modes
- from . import plot_modes # noqa
- self._win_args = win_args
- self._window = None
- self._render_lock = RLock()
- self._functions = {}
- self._pobjects = []
- self._screenshot = ScreenShot(self)
- axe_options = parse_option_string(win_args.pop('axes', ''))
- self.axes = PlotAxes(**axe_options)
- self._pobjects.append(self.axes)
- self[0] = fargs
- if win_args.get('visible', True):
- self.show()
- ## Window Interfaces
- def show(self):
- """
- Creates and displays a plot window, or activates it
- (gives it focus) if it has already been created.
- """
- if self._window and not self._window.has_exit:
- self._window.activate()
- else:
- self._win_args['visible'] = True
- self.axes.reset_resources()
- #if hasattr(self, '_doctest_depends_on'):
- # self._win_args['runfromdoctester'] = True
- self._window = PlotWindow(self, **self._win_args)
- def close(self):
- """
- Closes the plot window.
- """
- if self._window:
- self._window.close()
- def saveimage(self, outfile=None, format='', size=(600, 500)):
- """
- Saves a screen capture of the plot window to an
- image file.
- If outfile is given, it can either be a path
- or a file object. Otherwise a png image will
- be saved to the current working directory.
- If the format is omitted, it is determined from
- the filename extension.
- """
- self._screenshot.save(outfile, format, size)
- ## Function List Interfaces
- def clear(self):
- """
- Clears the function list of this plot.
- """
- self._render_lock.acquire()
- self._functions = {}
- self.adjust_all_bounds()
- self._render_lock.release()
- def __getitem__(self, i):
- """
- Returns the function at position i in the
- function list.
- """
- return self._functions[i]
- def __setitem__(self, i, args):
- """
- Parses and adds a PlotMode to the function
- list.
- """
- if not (isinstance(i, (SYMPY_INTS, Integer)) and i >= 0):
- raise ValueError("Function index must "
- "be an integer >= 0.")
- if isinstance(args, PlotObject):
- f = args
- else:
- if (not is_sequence(args)) or isinstance(args, GeometryEntity):
- args = [args]
- if len(args) == 0:
- return # no arguments given
- kwargs = dict(bounds_callback=self.adjust_all_bounds)
- f = PlotMode(*args, **kwargs)
- if f:
- self._render_lock.acquire()
- self._functions[i] = f
- self._render_lock.release()
- else:
- raise ValueError("Failed to parse '%s'."
- % ', '.join(str(a) for a in args))
- def __delitem__(self, i):
- """
- Removes the function in the function list at
- position i.
- """
- self._render_lock.acquire()
- del self._functions[i]
- self.adjust_all_bounds()
- self._render_lock.release()
- def firstavailableindex(self):
- """
- Returns the first unused index in the function list.
- """
- i = 0
- self._render_lock.acquire()
- while i in self._functions:
- i += 1
- self._render_lock.release()
- return i
- def append(self, *args):
- """
- Parses and adds a PlotMode to the function
- list at the first available index.
- """
- self.__setitem__(self.firstavailableindex(), args)
- def __len__(self):
- """
- Returns the number of functions in the function list.
- """
- return len(self._functions)
- def __iter__(self):
- """
- Allows iteration of the function list.
- """
- return self._functions.itervalues()
- def __repr__(self):
- return str(self)
- def __str__(self):
- """
- Returns a string containing a new-line separated
- list of the functions in the function list.
- """
- s = ""
- if len(self._functions) == 0:
- s += "<blank plot>"
- else:
- self._render_lock.acquire()
- s += "\n".join(["%s[%i]: %s" % ("", i, str(self._functions[i]))
- for i in self._functions])
- self._render_lock.release()
- return s
- def adjust_all_bounds(self):
- self._render_lock.acquire()
- self.axes.reset_bounding_box()
- for f in self._functions:
- self.axes.adjust_bounds(self._functions[f].bounds)
- self._render_lock.release()
- def wait_for_calculations(self):
- sleep(0)
- self._render_lock.acquire()
- for f in self._functions:
- a = self._functions[f]._get_calculating_verts
- b = self._functions[f]._get_calculating_cverts
- while a() or b():
- sleep(0)
- self._render_lock.release()
- class ScreenShot:
- def __init__(self, plot):
- self._plot = plot
- self.screenshot_requested = False
- self.outfile = None
- self.format = ''
- self.invisibleMode = False
- self.flag = 0
- def __bool__(self):
- return self.screenshot_requested
- def _execute_saving(self):
- if self.flag < 3:
- self.flag += 1
- return
- size_x, size_y = self._plot._window.get_size()
- size = size_x*size_y*4*ctypes.sizeof(ctypes.c_ubyte)
- image = ctypes.create_string_buffer(size)
- pgl.glReadPixels(0, 0, size_x, size_y, pgl.GL_RGBA, pgl.GL_UNSIGNED_BYTE, image)
- from PIL import Image
- im = Image.frombuffer('RGBA', (size_x, size_y),
- image.raw, 'raw', 'RGBA', 0, 1)
- im.transpose(Image.FLIP_TOP_BOTTOM).save(self.outfile, self.format)
- self.flag = 0
- self.screenshot_requested = False
- if self.invisibleMode:
- self._plot._window.close()
- def save(self, outfile=None, format='', size=(600, 500)):
- self.outfile = outfile
- self.format = format
- self.size = size
- self.screenshot_requested = True
- if not self._plot._window or self._plot._window.has_exit:
- self._plot._win_args['visible'] = False
- self._plot._win_args['width'] = size[0]
- self._plot._win_args['height'] = size[1]
- self._plot.axes.reset_resources()
- self._plot._window = PlotWindow(self._plot, **self._plot._win_args)
- self.invisibleMode = True
- if self.outfile is None:
- self.outfile = self._create_unique_path()
- print(self.outfile)
- def _create_unique_path(self):
- cwd = getcwd()
- l = listdir(cwd)
- path = ''
- i = 0
- while True:
- if not 'plot_%s.png' % i in l:
- path = cwd + '/plot_%s.png' % i
- break
- i += 1
- return path
|