plot_mode_base.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import pyglet.gl as pgl
  2. from sympy.core import S
  3. from sympy.plotting.pygletplot.color_scheme import ColorScheme
  4. from sympy.plotting.pygletplot.plot_mode import PlotMode
  5. from sympy.utilities.iterables import is_sequence
  6. from time import sleep
  7. from threading import Thread, Event, RLock
  8. import warnings
  9. class PlotModeBase(PlotMode):
  10. """
  11. Intended parent class for plotting
  12. modes. Provides base functionality
  13. in conjunction with its parent,
  14. PlotMode.
  15. """
  16. ##
  17. ## Class-Level Attributes
  18. ##
  19. """
  20. The following attributes are meant
  21. to be set at the class level, and serve
  22. as parameters to the plot mode registry
  23. (in PlotMode). See plot_modes.py for
  24. concrete examples.
  25. """
  26. """
  27. i_vars
  28. 'x' for Cartesian2D
  29. 'xy' for Cartesian3D
  30. etc.
  31. d_vars
  32. 'y' for Cartesian2D
  33. 'r' for Polar
  34. etc.
  35. """
  36. i_vars, d_vars = '', ''
  37. """
  38. intervals
  39. Default intervals for each i_var, and in the
  40. same order. Specified [min, max, steps].
  41. No variable can be given (it is bound later).
  42. """
  43. intervals = []
  44. """
  45. aliases
  46. A list of strings which can be used to
  47. access this mode.
  48. 'cartesian' for Cartesian2D and Cartesian3D
  49. 'polar' for Polar
  50. 'cylindrical', 'polar' for Cylindrical
  51. Note that _init_mode chooses the first alias
  52. in the list as the mode's primary_alias, which
  53. will be displayed to the end user in certain
  54. contexts.
  55. """
  56. aliases = []
  57. """
  58. is_default
  59. Whether to set this mode as the default
  60. for arguments passed to PlotMode() containing
  61. the same number of d_vars as this mode and
  62. at most the same number of i_vars.
  63. """
  64. is_default = False
  65. """
  66. All of the above attributes are defined in PlotMode.
  67. The following ones are specific to PlotModeBase.
  68. """
  69. """
  70. A list of the render styles. Do not modify.
  71. """
  72. styles = {'wireframe': 1, 'solid': 2, 'both': 3}
  73. """
  74. style_override
  75. Always use this style if not blank.
  76. """
  77. style_override = ''
  78. """
  79. default_wireframe_color
  80. default_solid_color
  81. Can be used when color is None or being calculated.
  82. Used by PlotCurve and PlotSurface, but not anywhere
  83. in PlotModeBase.
  84. """
  85. default_wireframe_color = (0.85, 0.85, 0.85)
  86. default_solid_color = (0.6, 0.6, 0.9)
  87. default_rot_preset = 'xy'
  88. ##
  89. ## Instance-Level Attributes
  90. ##
  91. ## 'Abstract' member functions
  92. def _get_evaluator(self):
  93. if self.use_lambda_eval:
  94. try:
  95. e = self._get_lambda_evaluator()
  96. return e
  97. except Exception:
  98. warnings.warn("\nWarning: creating lambda evaluator failed. "
  99. "Falling back on SymPy subs evaluator.")
  100. return self._get_sympy_evaluator()
  101. def _get_sympy_evaluator(self):
  102. raise NotImplementedError()
  103. def _get_lambda_evaluator(self):
  104. raise NotImplementedError()
  105. def _on_calculate_verts(self):
  106. raise NotImplementedError()
  107. def _on_calculate_cverts(self):
  108. raise NotImplementedError()
  109. ## Base member functions
  110. def __init__(self, *args, bounds_callback=None, **kwargs):
  111. self.verts = []
  112. self.cverts = []
  113. self.bounds = [[S.Infinity, S.NegativeInfinity, 0],
  114. [S.Infinity, S.NegativeInfinity, 0],
  115. [S.Infinity, S.NegativeInfinity, 0]]
  116. self.cbounds = [[S.Infinity, S.NegativeInfinity, 0],
  117. [S.Infinity, S.NegativeInfinity, 0],
  118. [S.Infinity, S.NegativeInfinity, 0]]
  119. self._draw_lock = RLock()
  120. self._calculating_verts = Event()
  121. self._calculating_cverts = Event()
  122. self._calculating_verts_pos = 0.0
  123. self._calculating_verts_len = 0.0
  124. self._calculating_cverts_pos = 0.0
  125. self._calculating_cverts_len = 0.0
  126. self._max_render_stack_size = 3
  127. self._draw_wireframe = [-1]
  128. self._draw_solid = [-1]
  129. self._style = None
  130. self._color = None
  131. self.predraw = []
  132. self.postdraw = []
  133. self.use_lambda_eval = self.options.pop('use_sympy_eval', None) is None
  134. self.style = self.options.pop('style', '')
  135. self.color = self.options.pop('color', 'rainbow')
  136. self.bounds_callback = bounds_callback
  137. self._on_calculate()
  138. def synchronized(f):
  139. def w(self, *args, **kwargs):
  140. self._draw_lock.acquire()
  141. try:
  142. r = f(self, *args, **kwargs)
  143. return r
  144. finally:
  145. self._draw_lock.release()
  146. return w
  147. @synchronized
  148. def push_wireframe(self, function):
  149. """
  150. Push a function which performs gl commands
  151. used to build a display list. (The list is
  152. built outside of the function)
  153. """
  154. assert callable(function)
  155. self._draw_wireframe.append(function)
  156. if len(self._draw_wireframe) > self._max_render_stack_size:
  157. del self._draw_wireframe[1] # leave marker element
  158. @synchronized
  159. def push_solid(self, function):
  160. """
  161. Push a function which performs gl commands
  162. used to build a display list. (The list is
  163. built outside of the function)
  164. """
  165. assert callable(function)
  166. self._draw_solid.append(function)
  167. if len(self._draw_solid) > self._max_render_stack_size:
  168. del self._draw_solid[1] # leave marker element
  169. def _create_display_list(self, function):
  170. dl = pgl.glGenLists(1)
  171. pgl.glNewList(dl, pgl.GL_COMPILE)
  172. function()
  173. pgl.glEndList()
  174. return dl
  175. def _render_stack_top(self, render_stack):
  176. top = render_stack[-1]
  177. if top == -1:
  178. return -1 # nothing to display
  179. elif callable(top):
  180. dl = self._create_display_list(top)
  181. render_stack[-1] = (dl, top)
  182. return dl # display newly added list
  183. elif len(top) == 2:
  184. if pgl.GL_TRUE == pgl.glIsList(top[0]):
  185. return top[0] # display stored list
  186. dl = self._create_display_list(top[1])
  187. render_stack[-1] = (dl, top[1])
  188. return dl # display regenerated list
  189. def _draw_solid_display_list(self, dl):
  190. pgl.glPushAttrib(pgl.GL_ENABLE_BIT | pgl.GL_POLYGON_BIT)
  191. pgl.glPolygonMode(pgl.GL_FRONT_AND_BACK, pgl.GL_FILL)
  192. pgl.glCallList(dl)
  193. pgl.glPopAttrib()
  194. def _draw_wireframe_display_list(self, dl):
  195. pgl.glPushAttrib(pgl.GL_ENABLE_BIT | pgl.GL_POLYGON_BIT)
  196. pgl.glPolygonMode(pgl.GL_FRONT_AND_BACK, pgl.GL_LINE)
  197. pgl.glEnable(pgl.GL_POLYGON_OFFSET_LINE)
  198. pgl.glPolygonOffset(-0.005, -50.0)
  199. pgl.glCallList(dl)
  200. pgl.glPopAttrib()
  201. @synchronized
  202. def draw(self):
  203. for f in self.predraw:
  204. if callable(f):
  205. f()
  206. if self.style_override:
  207. style = self.styles[self.style_override]
  208. else:
  209. style = self.styles[self._style]
  210. # Draw solid component if style includes solid
  211. if style & 2:
  212. dl = self._render_stack_top(self._draw_solid)
  213. if dl > 0 and pgl.GL_TRUE == pgl.glIsList(dl):
  214. self._draw_solid_display_list(dl)
  215. # Draw wireframe component if style includes wireframe
  216. if style & 1:
  217. dl = self._render_stack_top(self._draw_wireframe)
  218. if dl > 0 and pgl.GL_TRUE == pgl.glIsList(dl):
  219. self._draw_wireframe_display_list(dl)
  220. for f in self.postdraw:
  221. if callable(f):
  222. f()
  223. def _on_change_color(self, color):
  224. Thread(target=self._calculate_cverts).start()
  225. def _on_calculate(self):
  226. Thread(target=self._calculate_all).start()
  227. def _calculate_all(self):
  228. self._calculate_verts()
  229. self._calculate_cverts()
  230. def _calculate_verts(self):
  231. if self._calculating_verts.is_set():
  232. return
  233. self._calculating_verts.set()
  234. try:
  235. self._on_calculate_verts()
  236. finally:
  237. self._calculating_verts.clear()
  238. if callable(self.bounds_callback):
  239. self.bounds_callback()
  240. def _calculate_cverts(self):
  241. if self._calculating_verts.is_set():
  242. return
  243. while self._calculating_cverts.is_set():
  244. sleep(0) # wait for previous calculation
  245. self._calculating_cverts.set()
  246. try:
  247. self._on_calculate_cverts()
  248. finally:
  249. self._calculating_cverts.clear()
  250. def _get_calculating_verts(self):
  251. return self._calculating_verts.is_set()
  252. def _get_calculating_verts_pos(self):
  253. return self._calculating_verts_pos
  254. def _get_calculating_verts_len(self):
  255. return self._calculating_verts_len
  256. def _get_calculating_cverts(self):
  257. return self._calculating_cverts.is_set()
  258. def _get_calculating_cverts_pos(self):
  259. return self._calculating_cverts_pos
  260. def _get_calculating_cverts_len(self):
  261. return self._calculating_cverts_len
  262. ## Property handlers
  263. def _get_style(self):
  264. return self._style
  265. @synchronized
  266. def _set_style(self, v):
  267. if v is None:
  268. return
  269. if v == '':
  270. step_max = 0
  271. for i in self.intervals:
  272. if i.v_steps is None:
  273. continue
  274. step_max = max([step_max, int(i.v_steps)])
  275. v = ['both', 'solid'][step_max > 40]
  276. if v not in self.styles:
  277. raise ValueError("v should be there in self.styles")
  278. if v == self._style:
  279. return
  280. self._style = v
  281. def _get_color(self):
  282. return self._color
  283. @synchronized
  284. def _set_color(self, v):
  285. try:
  286. if v is not None:
  287. if is_sequence(v):
  288. v = ColorScheme(*v)
  289. else:
  290. v = ColorScheme(v)
  291. if repr(v) == repr(self._color):
  292. return
  293. self._on_change_color(v)
  294. self._color = v
  295. except Exception as e:
  296. raise RuntimeError("Color change failed. "
  297. "Reason: %s" % (str(e)))
  298. style = property(_get_style, _set_style)
  299. color = property(_get_color, _set_color)
  300. calculating_verts = property(_get_calculating_verts)
  301. calculating_verts_pos = property(_get_calculating_verts_pos)
  302. calculating_verts_len = property(_get_calculating_verts_len)
  303. calculating_cverts = property(_get_calculating_cverts)
  304. calculating_cverts_pos = property(_get_calculating_cverts_pos)
  305. calculating_cverts_len = property(_get_calculating_cverts_len)
  306. ## String representations
  307. def __str__(self):
  308. f = ", ".join(str(d) for d in self.d_vars)
  309. o = "'mode=%s'" % (self.primary_alias)
  310. return ", ".join([f, o])
  311. def __repr__(self):
  312. f = ", ".join(str(d) for d in self.d_vars)
  313. i = ", ".join(str(i) for i in self.intervals)
  314. d = [('mode', self.primary_alias),
  315. ('color', str(self.color)),
  316. ('style', str(self.style))]
  317. o = "'%s'" % ("; ".join("%s=%s" % (k, v)
  318. for k, v in d if v != 'None'))
  319. return ", ".join([f, i, o])