Spaces:
Sleeping
Sleeping
| 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 https://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 | |
| 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 | |
| ============================= | |
| """ | |
| 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 = {"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 | |