PlotCanvasLegacy.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. ############################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://caram.cl/software/flatcam #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 2/5/2014 #
  6. # MIT Licence #
  7. ############################################################
  8. from PyQt5 import QtGui, QtCore, QtWidgets
  9. # Prevent conflict with Qt5 and above.
  10. from matplotlib import use as mpl_use
  11. from matplotlib.figure import Figure
  12. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  13. from matplotlib.backends.backend_agg import FigureCanvasAgg
  14. from matplotlib.widgets import Cursor
  15. # needed for legacy mode
  16. # Used for solid polygons in Matplotlib
  17. from descartes.patch import PolygonPatch
  18. from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
  19. import FlatCAMApp
  20. from copy import deepcopy
  21. import logging
  22. mpl_use("Qt5Agg")
  23. log = logging.getLogger('base')
  24. class CanvasCache(QtCore.QObject):
  25. """
  26. Case story #1:
  27. 1) No objects in the project.
  28. 2) Object is created (new_object() emits object_created(obj)).
  29. on_object_created() adds (i) object to collection and emits
  30. (ii) new_object_available() then calls (iii) object.plot()
  31. 3) object.plot() creates axes if necessary on
  32. app.collection.figure. Then plots on it.
  33. 4) Plots on a cache-size canvas (in background).
  34. 5) Plot completes. Bitmap is generated.
  35. 6) Visible canvas is painted.
  36. """
  37. # Signals:
  38. # A bitmap is ready to be displayed.
  39. new_screen = QtCore.pyqtSignal()
  40. def __init__(self, plotcanvas, app, dpi=50):
  41. super(CanvasCache, self).__init__()
  42. self.app = app
  43. self.plotcanvas = plotcanvas
  44. self.dpi = dpi
  45. self.figure = Figure(dpi=dpi)
  46. self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
  47. self.axes.set_frame_on(False)
  48. self.axes.set_xticks([])
  49. self.axes.set_yticks([])
  50. self.canvas = FigureCanvasAgg(self.figure)
  51. self.cache = None
  52. def run(self):
  53. log.debug("CanvasCache Thread Started!")
  54. self.plotcanvas.update_screen_request.connect(self.on_update_req)
  55. def on_update_req(self, extents):
  56. """
  57. Event handler for an updated display request.
  58. :param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
  59. """
  60. # log.debug("Canvas update requested: %s" % str(extents))
  61. # Note: This information below might be out of date. Establish
  62. # a protocol regarding when to change the canvas in the main
  63. # thread and when to check these values here in the background,
  64. # or pass this data in the signal (safer).
  65. # log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
  66. # log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
  67. # Move the requested screen portion to the main thread
  68. # and inform about the update:
  69. self.new_screen.emit()
  70. # Continue to update the cache.
  71. # def on_new_object_available(self):
  72. #
  73. # log.debug("A new object is available. Should plot it!")
  74. class PlotCanvasLegacy(QtCore.QObject):
  75. """
  76. Class handling the plotting area in the application.
  77. """
  78. # Signals:
  79. # Request for new bitmap to display. The parameter
  80. # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
  81. update_screen_request = QtCore.pyqtSignal(list)
  82. double_click = QtCore.pyqtSignal(object)
  83. def __init__(self, container, app):
  84. """
  85. The constructor configures the Matplotlib figure that
  86. will contain all plots, creates the base axes and connects
  87. events to the plotting area.
  88. :param container: The parent container in which to draw plots.
  89. :rtype: PlotCanvas
  90. """
  91. super(PlotCanvasLegacy, self).__init__()
  92. self.app = app
  93. # Options
  94. self.x_margin = 15 # pixels
  95. self.y_margin = 25 # Pixels
  96. # Parent container
  97. self.container = container
  98. # Plots go onto a single matplotlib.figure
  99. self.figure = Figure(dpi=50) # TODO: dpi needed?
  100. self.figure.patch.set_visible(False)
  101. # These axes show the ticks and grid. No plotting done here.
  102. # New axes must have a label, otherwise mpl returns an existing one.
  103. self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
  104. self.axes.set_aspect(1)
  105. self.axes.grid(True)
  106. self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
  107. self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
  108. # The canvas is the top level container (FigureCanvasQTAgg)
  109. self.canvas = FigureCanvas(self.figure)
  110. self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
  111. self.canvas.setFocus()
  112. self.native = self.canvas
  113. # self.canvas.set_can_focus(True) # For key press
  114. # Attach to parent
  115. # self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns??
  116. self.container.addWidget(self.canvas) # Qt
  117. # Copy a bitmap of the canvas for quick animation.
  118. # Update every time the canvas is re-drawn.
  119. self.background = self.canvas.copy_from_bbox(self.axes.bbox)
  120. # ## Bitmap Cache
  121. self.cache = CanvasCache(self, self.app)
  122. self.cache_thread = QtCore.QThread()
  123. self.cache.moveToThread(self.cache_thread)
  124. # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
  125. self.cache_thread.started.connect(self.cache.run)
  126. self.cache_thread.start()
  127. self.cache.new_screen.connect(self.on_new_screen)
  128. # Events
  129. self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press)
  130. self.mr = self.graph_event_connect('button_release_event', self.on_mouse_release)
  131. self.mm = self.graph_event_connect('motion_notify_event', self.on_mouse_move)
  132. # self.canvas.connect('configure-event', self.auto_adjust_axes)
  133. self.aaa = self.graph_event_connect('resize_event', self.auto_adjust_axes)
  134. # self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
  135. # self.canvas.connect("scroll-event", self.on_scroll)
  136. self.osc = self.graph_event_connect('scroll_event', self.on_scroll)
  137. # self.graph_event_connect('key_press_event', self.on_key_down)
  138. # self.graph_event_connect('key_release_event', self.on_key_up)
  139. self.odr = self.graph_event_connect('draw_event', self.on_draw)
  140. self.mouse = [0, 0]
  141. self.key = None
  142. self.pan_axes = []
  143. self.panning = False
  144. # signal is the mouse is dragging
  145. self.is_dragging = False
  146. # signal if there is a doubleclick
  147. self.is_dblclk = False
  148. def graph_event_connect(self, event_name, callback):
  149. """
  150. Attach an event handler to the canvas through the Matplotlib interface.
  151. :param event_name: Name of the event
  152. :type event_name: str
  153. :param callback: Function to call
  154. :type callback: func
  155. :return: Connection id
  156. :rtype: int
  157. """
  158. if event_name == 'mouse_move':
  159. event_name = 'motion_notify_event'
  160. if event_name == 'mouse_press':
  161. event_name = 'button_press_event'
  162. if event_name == 'mouse_release':
  163. event_name = 'button_release_event'
  164. if event_name == 'mouse_double_click':
  165. return self.double_click.connect(callback)
  166. if event_name == 'key_press':
  167. event_name = 'key_press_event'
  168. return self.canvas.mpl_connect(event_name, callback)
  169. def graph_event_disconnect(self, cid):
  170. """
  171. Disconnect callback with the give id.
  172. :param cid: Callback id.
  173. :return: None
  174. """
  175. # self.double_click.disconnect(cid)
  176. self.canvas.mpl_disconnect(cid)
  177. def on_new_screen(self):
  178. pass
  179. # log.debug("Cache updated the screen!")
  180. def new_cursor(self, axes=None):
  181. # if axes is None:
  182. # c = MplCursor(axes=self.axes, color='black', linewidth=1)
  183. # else:
  184. # c = MplCursor(axes=axes, color='black', linewidth=1)
  185. c = FakeCursor()
  186. return c
  187. def on_key_down(self, event):
  188. """
  189. :param event:
  190. :return:
  191. """
  192. FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
  193. self.key = event.key
  194. def on_key_up(self, event):
  195. """
  196. :param event:
  197. :return:
  198. """
  199. self.key = None
  200. def connect(self, event_name, callback):
  201. """
  202. Attach an event handler to the canvas through the native Qt interface.
  203. :param event_name: Name of the event
  204. :type event_name: str
  205. :param callback: Function to call
  206. :type callback: function
  207. :return: Nothing
  208. """
  209. self.canvas.connect(event_name, callback)
  210. def clear(self):
  211. """
  212. Clears axes and figure.
  213. :return: None
  214. """
  215. # Clear
  216. self.axes.cla()
  217. try:
  218. self.figure.clf()
  219. except KeyError:
  220. FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
  221. # Re-build
  222. self.figure.add_axes(self.axes)
  223. self.axes.set_aspect(1)
  224. self.axes.grid(True)
  225. # Re-draw
  226. self.canvas.draw_idle()
  227. def adjust_axes(self, xmin, ymin, xmax, ymax):
  228. """
  229. Adjusts all axes while maintaining the use of the whole canvas
  230. and an aspect ratio to 1:1 between x and y axes. The parameters are an original
  231. request that will be modified to fit these restrictions.
  232. :param xmin: Requested minimum value for the X axis.
  233. :type xmin: float
  234. :param ymin: Requested minimum value for the Y axis.
  235. :type ymin: float
  236. :param xmax: Requested maximum value for the X axis.
  237. :type xmax: float
  238. :param ymax: Requested maximum value for the Y axis.
  239. :type ymax: float
  240. :return: None
  241. """
  242. # FlatCAMApp.App.log.debug("PC.adjust_axes()")
  243. width = xmax - xmin
  244. height = ymax - ymin
  245. try:
  246. r = width / height
  247. except ZeroDivisionError:
  248. FlatCAMApp.App.log.error("Height is %f" % height)
  249. return
  250. canvas_w, canvas_h = self.canvas.get_width_height()
  251. canvas_r = float(canvas_w) / canvas_h
  252. x_ratio = float(self.x_margin) / canvas_w
  253. y_ratio = float(self.y_margin) / canvas_h
  254. if r > canvas_r:
  255. ycenter = (ymin + ymax) / 2.0
  256. newheight = height * r / canvas_r
  257. ymin = ycenter - newheight / 2.0
  258. ymax = ycenter + newheight / 2.0
  259. else:
  260. xcenter = (xmax + xmin) / 2.0
  261. newwidth = width * canvas_r / r
  262. xmin = xcenter - newwidth / 2.0
  263. xmax = xcenter + newwidth / 2.0
  264. # Adjust axes
  265. for ax in self.figure.get_axes():
  266. if ax._label != 'base':
  267. ax.set_frame_on(False) # No frame
  268. ax.set_xticks([]) # No tick
  269. ax.set_yticks([]) # No ticks
  270. ax.patch.set_visible(False) # No background
  271. ax.set_aspect(1)
  272. ax.set_xlim((xmin, xmax))
  273. ax.set_ylim((ymin, ymax))
  274. ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
  275. # Sync re-draw to proper paint on form resize
  276. self.canvas.draw()
  277. # #### Temporary place-holder for cached update #####
  278. self.update_screen_request.emit([0, 0, 0, 0, 0])
  279. def auto_adjust_axes(self, *args):
  280. """
  281. Calls ``adjust_axes()`` using the extents of the base axes.
  282. :rtype : None
  283. :return: None
  284. """
  285. xmin, xmax = self.axes.get_xlim()
  286. ymin, ymax = self.axes.get_ylim()
  287. self.adjust_axes(xmin, ymin, xmax, ymax)
  288. def fit_view(self):
  289. self.auto_adjust_axes()
  290. def zoom(self, factor, center=None):
  291. """
  292. Zooms the plot by factor around a given
  293. center point. Takes care of re-drawing.
  294. :param factor: Number by which to scale the plot.
  295. :type factor: float
  296. :param center: Coordinates [x, y] of the point around which to scale the plot.
  297. :type center: list
  298. :return: None
  299. """
  300. xmin, xmax = self.axes.get_xlim()
  301. ymin, ymax = self.axes.get_ylim()
  302. width = xmax - xmin
  303. height = ymax - ymin
  304. if center is None or center == [None, None]:
  305. center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
  306. # For keeping the point at the pointer location
  307. relx = (xmax - center[0]) / width
  308. rely = (ymax - center[1]) / height
  309. new_width = width / factor
  310. new_height = height / factor
  311. xmin = center[0] - new_width * (1 - relx)
  312. xmax = center[0] + new_width * relx
  313. ymin = center[1] - new_height * (1 - rely)
  314. ymax = center[1] + new_height * rely
  315. # Adjust axes
  316. for ax in self.figure.get_axes():
  317. ax.set_xlim((xmin, xmax))
  318. ax.set_ylim((ymin, ymax))
  319. # Async re-draw
  320. self.canvas.draw_idle()
  321. # #### Temporary place-holder for cached update #####
  322. self.update_screen_request.emit([0, 0, 0, 0, 0])
  323. def pan(self, x, y):
  324. xmin, xmax = self.axes.get_xlim()
  325. ymin, ymax = self.axes.get_ylim()
  326. width = xmax - xmin
  327. height = ymax - ymin
  328. # Adjust axes
  329. for ax in self.figure.get_axes():
  330. ax.set_xlim((xmin + x * width, xmax + x * width))
  331. ax.set_ylim((ymin + y * height, ymax + y * height))
  332. # Re-draw
  333. self.canvas.draw_idle()
  334. # #### Temporary place-holder for cached update #####
  335. self.update_screen_request.emit([0, 0, 0, 0, 0])
  336. def new_axes(self, name):
  337. """
  338. Creates and returns an Axes object attached to this object's Figure.
  339. :param name: Unique label for the axes.
  340. :return: Axes attached to the figure.
  341. :rtype: Axes
  342. """
  343. return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
  344. def on_scroll(self, event):
  345. """
  346. Scroll event handler.
  347. :param event: Event object containing the event information.
  348. :return: None
  349. """
  350. # So it can receive key presses
  351. # self.canvas.grab_focus()
  352. self.canvas.setFocus()
  353. # Event info
  354. # z, direction = event.get_scroll_direction()
  355. if self.key is None:
  356. if event.button == 'up':
  357. self.zoom(1.5, self.mouse)
  358. else:
  359. self.zoom(1 / 1.5, self.mouse)
  360. return
  361. if self.key == 'shift':
  362. if event.button == 'up':
  363. self.pan(0.3, 0)
  364. else:
  365. self.pan(-0.3, 0)
  366. return
  367. if self.key == 'control':
  368. if event.button == 'up':
  369. self.pan(0, 0.3)
  370. else:
  371. self.pan(0, -0.3)
  372. return
  373. def on_mouse_press(self, event):
  374. self.is_dragging = True
  375. # Check for middle mouse button press
  376. if self.app.defaults["global_pan_button"] == '2':
  377. pan_button = 3 # right button for Matplotlib
  378. else:
  379. pan_button = 2 # middle button for Matplotlib
  380. if event.button == pan_button:
  381. # Prepare axes for pan (using 'matplotlib' pan function)
  382. self.pan_axes = []
  383. for a in self.figure.get_axes():
  384. if (event.x is not None and event.y is not None and a.in_axes(event) and
  385. a.get_navigate() and a.can_pan()):
  386. a.start_pan(event.x, event.y, 1)
  387. self.pan_axes.append(a)
  388. # Set pan view flag
  389. if len(self.pan_axes) > 0:
  390. self.panning = True
  391. if event.dblclick:
  392. self.double_click.emit(event)
  393. def on_mouse_release(self, event):
  394. self.is_dragging = False
  395. # Check for middle mouse button release to complete pan procedure
  396. # Check for middle mouse button press
  397. if self.app.defaults["global_pan_button"] == '2':
  398. pan_button = 3 # right button for Matplotlib
  399. else:
  400. pan_button = 2 # middle button for Matplotlib
  401. if event.button == pan_button:
  402. for a in self.pan_axes:
  403. a.end_pan()
  404. # Clear pan flag
  405. self.panning = False
  406. def on_mouse_move(self, event):
  407. """
  408. Mouse movement event hadler. Stores the coordinates. Updates view on pan.
  409. :param event: Contains information about the event.
  410. :return: None
  411. """
  412. try:
  413. x = float(event.xdata)
  414. y = float(event.ydata)
  415. except TypeError:
  416. return
  417. self.mouse = [event.xdata, event.ydata]
  418. self.canvas.restore_region(self.background)
  419. # Update pan view on mouse move
  420. if self.panning is True:
  421. # x_pan, y_pan = self.app.geo_editor.snap(event.xdata, event.ydata)
  422. # self.app.app_cursor.set_data(event, (x_pan, y_pan))
  423. for a in self.pan_axes:
  424. a.drag_pan(1, event.key, event.x, event.y)
  425. # Async re-draw (redraws only on thread idle state, uses timer on backend)
  426. self.canvas.draw_idle()
  427. # #### Temporary place-holder for cached update #####
  428. self.update_screen_request.emit([0, 0, 0, 0, 0])
  429. x, y = self.app.geo_editor.snap(x, y)
  430. if self.app.app_cursor.enabled is True:
  431. # Pointer (snapped)
  432. elements = self.axes.plot(x, y, 'k+', ms=40, mew=2, animated=True)
  433. for el in elements:
  434. self.axes.draw_artist(el)
  435. self.canvas.blit(self.axes.bbox)
  436. def translate_coords(self, position):
  437. """
  438. This does not do much. It's just for code compatibility
  439. :param position: Mouse event position
  440. :return: Tuple with mouse position
  441. """
  442. return (position[0], position[1])
  443. def on_draw(self, renderer):
  444. # Store background on canvas redraw
  445. self.background = self.canvas.copy_from_bbox(self.axes.bbox)
  446. def get_axes_pixelsize(self):
  447. """
  448. Axes size in pixels.
  449. :return: Pixel width and height
  450. :rtype: tuple
  451. """
  452. bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
  453. width, height = bbox.width, bbox.height
  454. width *= self.figure.dpi
  455. height *= self.figure.dpi
  456. return width, height
  457. def get_density(self):
  458. """
  459. Returns unit length per pixel on horizontal
  460. and vertical axes.
  461. :return: X and Y density
  462. :rtype: tuple
  463. """
  464. xpx, ypx = self.get_axes_pixelsize()
  465. xmin, xmax = self.axes.get_xlim()
  466. ymin, ymax = self.axes.get_ylim()
  467. width = xmax - xmin
  468. height = ymax - ymin
  469. return width / xpx, height / ypx
  470. class FakeCursor():
  471. def __init__(self):
  472. self._enabled = True
  473. @property
  474. def enabled(self):
  475. return True if self._enabled else False
  476. @enabled.setter
  477. def enabled(self, value):
  478. self._enabled = value
  479. class MplCursor(Cursor):
  480. def __init__(self, axes, color='red', linewidth=1):
  481. super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth)
  482. self._enabled = True
  483. self.axes = axes
  484. self.color = color
  485. self.linewidth = linewidth
  486. self.x = None
  487. self.y = None
  488. @property
  489. def enabled(self):
  490. return True if self._enabled else False
  491. @enabled.setter
  492. def enabled(self, value):
  493. self._enabled = value
  494. self.visible = self._enabled
  495. self.canvas.draw()
  496. def onmove(self, event):
  497. pass
  498. def set_data(self, event, pos):
  499. """Internal event handler to draw the cursor when the mouse moves."""
  500. self.x = pos[0]
  501. self.y = pos[1]
  502. if self.ignore(event):
  503. return
  504. if not self.canvas.widgetlock.available(self):
  505. return
  506. if event.inaxes != self.ax:
  507. self.linev.set_visible(False)
  508. self.lineh.set_visible(False)
  509. if self.needclear:
  510. self.canvas.draw()
  511. self.needclear = False
  512. return
  513. self.needclear = True
  514. if not self.visible:
  515. return
  516. self.linev.set_xdata((self.x, self.x))
  517. self.lineh.set_ydata((self.y, self.y))
  518. self.linev.set_visible(self.visible and self.vertOn)
  519. self.lineh.set_visible(self.visible and self.horizOn)
  520. self._update()
  521. class ShapeCollectionLegacy():
  522. def __init__(self, obj, app, name=None):
  523. self.obj = obj
  524. self.app = app
  525. self._shapes = dict()
  526. self.shape_dict = dict()
  527. self.shape_id = 0
  528. self._color = None
  529. self._face_color = None
  530. self._visible = True
  531. self._update = False
  532. self._obj = None
  533. self._gcode_parsed = None
  534. if name is None:
  535. axes_name = self.obj.options['name']
  536. else:
  537. axes_name = name
  538. # Axes must exist and be attached to canvas.
  539. if axes_name not in self.app.plotcanvas.figure.axes:
  540. self.axes = self.app.plotcanvas.new_axes(axes_name)
  541. def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
  542. update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None):
  543. self._color = color[:-2] if color is not None else None
  544. self._face_color = face_color[:-2] if face_color is not None else None
  545. self._alpha = int(face_color[-2:], 16) / 255 if face_color is not None else 0.75
  546. if alpha is not None:
  547. self._alpha = alpha
  548. self._visible = visible
  549. self._update = update
  550. # CNCJob oject related arguments
  551. self._obj = obj
  552. self._gcode_parsed = gcode_parsed
  553. self._tool_tolerance = tool_tolerance
  554. self._tooldia = tooldia
  555. # if self._update:
  556. # self.clear()
  557. try:
  558. for sh in shape:
  559. self.shape_id += 1
  560. self.shape_dict.update({
  561. 'color': self._color,
  562. 'face_color': self._face_color,
  563. 'alpha': self._alpha,
  564. 'shape': sh
  565. })
  566. self._shapes.update({
  567. self.shape_id: deepcopy(self.shape_dict)
  568. })
  569. except TypeError:
  570. self.shape_id += 1
  571. self.shape_dict.update({
  572. 'color': self._color,
  573. 'face_color': self._face_color,
  574. 'alpha': self._alpha,
  575. 'shape': shape
  576. })
  577. self._shapes.update({
  578. self.shape_id: deepcopy(self.shape_dict)
  579. })
  580. return self.shape_id
  581. def clear(self, update=None):
  582. self._shapes.clear()
  583. self.shape_id = 0
  584. self.axes.cla()
  585. self.app.plotcanvas.auto_adjust_axes()
  586. if update is True:
  587. self.redraw()
  588. def redraw(self):
  589. path_num = 0
  590. try:
  591. obj_type = self.obj.kind
  592. except AttributeError:
  593. obj_type = 'utility'
  594. if self._visible:
  595. for element in self._shapes:
  596. if obj_type == 'excellon':
  597. # Plot excellon (All polygons?)
  598. if self.obj.options["solid"] and isinstance(self._shapes[element]['shape'], Polygon):
  599. patch = PolygonPatch(self._shapes[element]['shape'],
  600. facecolor="#C40000",
  601. edgecolor="#750000",
  602. alpha=self._shapes[element]['alpha'],
  603. zorder=3)
  604. self.axes.add_patch(patch)
  605. else:
  606. x, y = self._shapes[element]['shape'].exterior.coords.xy
  607. self.axes.plot(x, y, 'r-')
  608. for ints in self._shapes[element]['shape'].interiors:
  609. x, y = ints.coords.xy
  610. self.axes.plot(x, y, 'o-')
  611. elif obj_type== 'geometry':
  612. if type(self._shapes[element]['shape']) == Polygon:
  613. x, y = self._shapes[element]['shape'].exterior.coords.xy
  614. self.axes.plot(x, y, self._shapes[element]['color'], linestyle='-')
  615. for ints in self._shapes[element]['shape'].interiors:
  616. x, y = ints.coords.xy
  617. self.axes.plot(x, y, self._shapes[element]['color'], linestyle='-')
  618. elif type(element) == LineString or type(element) == LinearRing:
  619. x, y = element.coords.xy
  620. self.axes.plot(x, y, self._shapes[element]['color'], marker='-')
  621. return
  622. elif obj_type == 'gerber':
  623. if self.obj.options["multicolored"]:
  624. linespec = '-'
  625. else:
  626. linespec = 'k-'
  627. if self.obj.options["solid"]:
  628. try:
  629. patch = PolygonPatch(self._shapes[element]['shape'],
  630. facecolor=self._shapes[element]['face_color'],
  631. edgecolor=self._shapes[element]['color'],
  632. alpha=self._shapes[element]['alpha'],
  633. zorder=2)
  634. self.axes.add_patch(patch)
  635. except AssertionError:
  636. FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
  637. FlatCAMApp.App.log.warning(str(element))
  638. else:
  639. x, y = self._shapes[element]['shape'].exterior.xy
  640. self.axes.plot(x, y, linespec)
  641. for ints in self._shapes[element]['shape'].interiors:
  642. x, y = ints.coords.xy
  643. self.axes.plot(x, y, linespec)
  644. elif obj_type == 'cncjob':
  645. if self._shapes[element]['face_color'] is None:
  646. linespec = '--'
  647. linecolor = self._shapes[element]['color']
  648. # if geo['kind'][0] == 'C':
  649. # linespec = 'k-'
  650. x, y = self._shapes[element]['shape'].coords.xy
  651. self.axes.plot(x, y, linespec, color=linecolor)
  652. else:
  653. path_num += 1
  654. if isinstance(self._shapes[element]['shape'], Polygon):
  655. self.axes.annotate(str(path_num), xy=self._shapes[element]['shape'].exterior.coords[0],
  656. xycoords='data', fontsize=20)
  657. else:
  658. self.axes.annotate(str(path_num), xy=self._shapes[element]['shape'].coords[0],
  659. xycoords='data', fontsize=20)
  660. patch = PolygonPatch(self._shapes[element]['shape'],
  661. facecolor=self._shapes[element]['face_color'],
  662. edgecolor=self._shapes[element]['color'],
  663. alpha=self._shapes[element]['alpha'], zorder=2)
  664. self.axes.add_patch(patch)
  665. elif obj_type == 'utility':
  666. # not a FlatCAM object, must be utility
  667. if self._shapes[element]['face_color']:
  668. try:
  669. patch = PolygonPatch(self._shapes[element]['shape'],
  670. facecolor=self._shapes[element]['face_color'],
  671. edgecolor=self._shapes[element]['color'],
  672. alpha=self._shapes[element]['alpha'],
  673. zorder=2)
  674. self.axes.add_patch(patch)
  675. except AssertionError:
  676. FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
  677. FlatCAMApp.App.log.warning(str(element))
  678. else:
  679. if isinstance(self._shapes[element]['shape'], Polygon):
  680. x, y = self._shapes[element]['shape'].exterior.xy
  681. self.axes.plot(x, y, self._shapes[element]['color'], linestyle='-')
  682. for ints in self._shapes[element]['shape'].interiors:
  683. x, y = ints.coords.xy
  684. self.axes.plot(x, y, self._shapes[element]['color'], linestyle='-')
  685. else:
  686. x, y = self._shapes[element]['shape'].coords.xy
  687. self.axes.plot(x, y, self._shapes[element]['color'], linestyle='-')
  688. self.app.plotcanvas.auto_adjust_axes()
  689. @property
  690. def visible(self):
  691. return self._visible
  692. @visible.setter
  693. def visible(self, value):
  694. if value is False:
  695. self.axes.cla()
  696. self.app.plotcanvas.auto_adjust_axes()
  697. else:
  698. if self._visible is False:
  699. self.redraw()
  700. self._visible = value