| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656 |
- ############################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://caram.cl/software/flatcam #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- # Modified by Marius Stanciu 09/21/2019 #
- ############################################################
- from PyQt5 import QtCore
- from PyQt5.QtCore import pyqtSignal
- # needed for legacy mode
- # Used for solid polygons in Matplotlib
- from descartes.patch import PolygonPatch
- from shapely.geometry import Polygon, LineString, LinearRing
- from copy import deepcopy
- import logging
- import numpy as np
- import gettext
- import appTranslation as fcTranslate
- import builtins
- # Prevent conflict with Qt5 and above.
- from matplotlib import use as mpl_use
- mpl_use("Qt5Agg")
- from matplotlib.figure import Figure
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
- from matplotlib.lines import Line2D
- from matplotlib.offsetbox import AnchoredText
- # from matplotlib.widgets import Cursor
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- log = logging.getLogger('base')
- class CanvasCache(QtCore.QObject):
- """
- Case story #1:
- 1) No objects in the project.
- 2) Object is created (app_obj.new_object() emits object_created(obj)).
- on_object_created() adds (i) object to collection and emits
- (ii) app_obj.new_object_available() then calls (iii) object.plot()
- 3) object.plot() creates axes if necessary on
- app.collection.figure. Then plots on it.
- 4) Plots on a cache-size canvas (in background).
- 5) Plot completes. Bitmap is generated.
- 6) Visible canvas is painted.
- """
- # Signals:
- # A bitmap is ready to be displayed.
- new_screen = QtCore.pyqtSignal()
- def __init__(self, plotcanvas, app, dpi=50):
- super(CanvasCache, self).__init__()
- self.app = app
- self.plotcanvas = plotcanvas
- self.dpi = dpi
- self.figure = Figure(dpi=dpi)
- self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
- self.axes.set_frame_on(False)
- self.axes.set_xticks([])
- self.axes.set_yticks([])
- if self.app.defaults['global_theme'] == 'white':
- self.axes.set_facecolor('#FFFFFF')
- else:
- self.axes.set_facecolor('#000000')
- self.canvas = FigureCanvas(self.figure)
- self.cache = None
- def run(self):
- log.debug("CanvasCache Thread Started!")
- self.plotcanvas.update_screen_request.connect(self.on_update_req)
- def on_update_req(self, extents):
- """
- Event handler for an updated display request.
- :param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
- """
- # log.debug("Canvas update requested: %s" % str(extents))
- # Note: This information below might be out of date. Establish
- # a protocol regarding when to change the canvas in the main
- # thread and when to check these values here in the background,
- # or pass this data in the signal (safer).
- # log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
- # log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
- # Move the requested screen portion to the main thread
- # and inform about the update:
- self.new_screen.emit()
- # Continue to update the cache.
- # def on_app_obj.new_object_available(self):
- #
- # log.debug("A new object is available. Should plot it!")
- class PlotCanvasLegacy(QtCore.QObject):
- """
- Class handling the plotting area in the application.
- """
- # Signals:
- # Request for new bitmap to display. The parameter
- # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
- update_screen_request = QtCore.pyqtSignal(list)
- double_click = QtCore.pyqtSignal(object)
- def __init__(self, container, app):
- """
- The constructor configures the Matplotlib figure that
- will contain all plots, creates the base axes and connects
- events to the plotting area.
- :param container: The parent container in which to draw plots.
- :rtype: PlotCanvas
- """
- super(PlotCanvasLegacy, self).__init__()
- self.app = app
- if self.app.defaults['global_theme'] == 'white':
- theme_color = '#FFFFFF'
- tick_color = '#000000'
- self.rect_hud_color = '#0000FF10'
- self.text_hud_color = '#000000'
- else:
- theme_color = '#000000'
- tick_color = '#FFFFFF'
- self.rect_hud_color = '#80808040'
- self.text_hud_color = '#FFFFFF'
- # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
- # which might decrease performance
- # self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
- self.workspace_line = None
- self.pagesize_dict = {}
- self.pagesize_dict.update(
- {
- 'A0': (841, 1189),
- 'A1': (594, 841),
- 'A2': (420, 594),
- 'A3': (297, 420),
- 'A4': (210, 297),
- 'A5': (148, 210),
- 'A6': (105, 148),
- 'A7': (74, 105),
- 'A8': (52, 74),
- 'A9': (37, 52),
- 'A10': (26, 37),
- 'B0': (1000, 1414),
- 'B1': (707, 1000),
- 'B2': (500, 707),
- 'B3': (353, 500),
- 'B4': (250, 353),
- 'B5': (176, 250),
- 'B6': (125, 176),
- 'B7': (88, 125),
- 'B8': (62, 88),
- 'B9': (44, 62),
- 'B10': (31, 44),
- 'C0': (917, 1297),
- 'C1': (648, 917),
- 'C2': (458, 648),
- 'C3': (324, 458),
- 'C4': (229, 324),
- 'C5': (162, 229),
- 'C6': (114, 162),
- 'C7': (81, 114),
- 'C8': (57, 81),
- 'C9': (40, 57),
- 'C10': (28, 40),
- # American paper sizes
- 'LETTER': (8.5*25.4, 11*25.4),
- 'LEGAL': (8.5*25.4, 14*25.4),
- 'ELEVENSEVENTEEN': (11*25.4, 17*25.4),
- # From https://en.wikipedia.org/wiki/Paper_size
- 'JUNIOR_LEGAL': (5*25.4, 8*25.4),
- 'HALF_LETTER': (5.5*25.4, 8*25.4),
- 'GOV_LETTER': (8*25.4, 10.5*25.4),
- 'GOV_LEGAL': (8.5*25.4, 13*25.4),
- 'LEDGER': (17*25.4, 11*25.4),
- }
- )
- # Options
- self.x_margin = 15 # pixels
- self.y_margin = 25 # Pixels
- # Parent container
- self.container = container
- # Plots go onto a single matplotlib.figure
- self.figure = Figure(dpi=50)
- self.figure.patch.set_visible(True)
- self.figure.set_facecolor(theme_color)
- # These axes show the ticks and grid. No plotting done here.
- # New axes must have a label, otherwise mpl returns an existing one.
- self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
- self.axes.set_aspect(1)
- self.axes.grid(True, color='gray')
- self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.axes.tick_params(axis='x', color=tick_color, labelcolor=tick_color)
- self.axes.tick_params(axis='y', color=tick_color, labelcolor=tick_color)
- self.axes.spines['bottom'].set_color(tick_color)
- self.axes.spines['top'].set_color(tick_color)
- self.axes.spines['right'].set_color(tick_color)
- self.axes.spines['left'].set_color(tick_color)
- self.axes.set_facecolor(theme_color)
- self.ch_line = None
- self.cv_line = None
- # The canvas is the top level container (FigureCanvasQTAgg)
- self.canvas = FigureCanvas(self.figure)
- self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
- self.canvas.setFocus()
- self.native = self.canvas
- self.adjust_axes(-10, -10, 100, 100)
- # self.canvas.set_can_focus(True) # For key press
- # Attach to parent
- # self.container.attach(self.canvas, 0, 0, 600, 400)
- self.container.addWidget(self.canvas) # Qt
- # Copy a bitmap of the canvas for quick animation.
- # Update every time the canvas is re-drawn.
- self.background = self.canvas.copy_from_bbox(self.axes.bbox)
- # ################### NOT IMPLEMENTED YET - EXPERIMENTAL #######################
- # ## Bitmap Cache
- # self.cache = CanvasCache(self, self.app)
- # self.cache_thread = QtCore.QThread()
- # self.cache.moveToThread(self.cache_thread)
- # # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
- # self.cache_thread.started.connect(self.cache.run)
- #
- # self.cache_thread.start()
- # self.cache.new_screen.connect(self.on_new_screen)
- # ##############################################################################
- # Events
- self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press)
- self.mr = self.graph_event_connect('button_release_event', self.on_mouse_release)
- self.mm = self.graph_event_connect('motion_notify_event', self.on_mouse_move)
- # self.canvas.connect('configure-event', self.auto_adjust_axes)
- self.aaa = self.graph_event_connect('resize_event', self.auto_adjust_axes)
- # self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
- # self.canvas.connect("scroll-event", self.on_scroll)
- self.osc = self.graph_event_connect('scroll_event', self.on_scroll)
- # self.graph_event_connect('key_press_event', self.on_key_down)
- # self.graph_event_connect('key_release_event', self.on_key_up)
- self.odr = self.graph_event_connect('draw_event', self.on_draw)
- self.key = None
- self.pan_axes = []
- self.panning = False
- self.mouse = [0, 0]
- self.big_cursor = False
- self.big_cursor_isdisabled = None
- # signal is the mouse is dragging
- self.is_dragging = False
- self.mouse_press_pos = None
- # signal if there is a doubleclick
- self.is_dblclk = False
- # HUD Display
- self.hud_enabled = False
- self.text_hud = self.Thud(plotcanvas=self)
- if self.app.defaults['global_hud'] is True:
- self.on_toggle_hud(state=True, silent=None)
- # enable Grid lines
- self.grid_lines_enabled = True
- # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
- # all CNC have a limited workspace
- if self.app.defaults['global_workspace'] is True:
- self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
- # Axis Display
- self.axis_enabled = True
- # enable Axis
- self.on_toggle_axis(state=True, silent=True)
- self.app.ui.axis_status_label.setStyleSheet("""
- QLabel
- {
- color: black;
- background-color: orange;
- }
- """)
- def on_toggle_axis(self, signal=None, state=None, silent=None):
- if not state:
- state = not self.axis_enabled
- if state:
- self.axis_enabled = True
- self.app.defaults['global_axis'] = True
- if self.h_line not in self.axes.lines and self.v_line not in self.axes.lines:
- self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.app.ui.axis_status_label.setStyleSheet("""
- QLabel
- {
- color: black;
- background-color: orange;
- }
- """)
- if silent is None:
- self.app.inform[str, bool].emit(_("Axis enabled."), False)
- else:
- self.axis_enabled = False
- self.app.defaults['global_axis'] = False
- if self.h_line in self.axes.lines and self.v_line in self.axes.lines:
- self.axes.lines.remove(self.h_line)
- self.axes.lines.remove(self.v_line)
- self.app.ui.axis_status_label.setStyleSheet("")
- if silent is None:
- self.app.inform[str, bool].emit(_("Axis disabled."), False)
- self.canvas.draw()
- def on_toggle_hud(self, signal=None, state=None, silent=None):
- if state is None:
- state = not self.hud_enabled
- if state:
- self.hud_enabled = True
- self.text_hud.add_artist()
- self.app.defaults['global_hud'] = True
- self.app.ui.hud_label.setStyleSheet("""
- QLabel
- {
- color: black;
- background-color: mediumpurple;
- }
- """)
- if silent is None:
- self.app.inform[str, bool].emit(_("HUD enabled."), False)
- else:
- self.hud_enabled = False
- self.text_hud.remove_artist()
- self.app.defaults['global_hud'] = False
- self.app.ui.hud_label.setStyleSheet("")
- if silent is None:
- self.app.inform[str, bool].emit(_("HUD disabled."), False)
- self.canvas.draw()
- class Thud(QtCore.QObject):
- text_changed = QtCore.pyqtSignal(str)
- def __init__(self, plotcanvas):
- super().__init__()
- self.p = plotcanvas
- units = self.p.app.defaults['units']
- self._text = 'Dx: %s [%s]\nDy: %s [%s]\n\nX: %s [%s]\nY: %s [%s]' % \
- ('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
- # set font size
- qsettings = QtCore.QSettings("Open Source", "FlatCAM")
- if qsettings.contains("hud_font_size"):
- # I multiply with 2.5 because this seems to be the difference between the value taken by the VisPy (3D)
- # and Matplotlib (Legacy2D FlatCAM graphic engine)
- fsize = int(qsettings.value('hud_font_size', type=int) * 2.5)
- else:
- fsize = 20
- self.hud_holder = AnchoredText(self._text, prop=dict(size=fsize), frameon=True, loc='upper left')
- self.hud_holder.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
- fc_color = self.p.rect_hud_color[:-2]
- fc_alpha = int(self.p.rect_hud_color[-2:], 16) / 255
- text_color = self.p.text_hud_color
- self.hud_holder.patch.set_facecolor(fc_color)
- self.hud_holder.patch.set_alpha(fc_alpha)
- self.hud_holder.patch.set_edgecolor((0, 0, 0, 0))
- self. hud_holder.txt._text.set_color(color=text_color)
- self.text_changed.connect(self.on_text_changed)
- @property
- def text(self):
- return self._text
- @text.setter
- def text(self, val):
- self.text_changed.emit(val)
- self._text = val
- def on_text_changed(self, txt):
- try:
- txt = txt.replace('\t', ' ')
- self.hud_holder.txt.set_text(txt)
- self.p.canvas.draw()
- except Exception:
- pass
- def add_artist(self):
- if self.hud_holder not in self.p.axes.artists:
- self.p.axes.add_artist(self.hud_holder)
- def remove_artist(self):
- if self.hud_holder in self.p.axes.artists:
- self.p.axes.artists.remove(self.hud_holder)
- def on_toggle_grid_lines(self, signal=None, silent=None):
- state = not self.grid_lines_enabled
- if state:
- self.app.defaults['global_grid_lines'] = True
- self.grid_lines_enabled = True
- self.axes.grid(True)
- try:
- self.canvas.draw()
- except IndexError:
- pass
- if silent is None:
- self.app.inform[str, bool].emit(_("Grid enabled."), False)
- else:
- self.app.defaults['global_grid_lines'] = False
- self.grid_lines_enabled = False
- self.axes.grid(False)
- try:
- self.canvas.draw()
- except IndexError:
- pass
- if silent is None:
- self.app.inform[str, bool].emit(_("Grid disabled."), False)
- def draw_workspace(self, workspace_size):
- """
- Draw a rectangular shape on canvas to specify our valid workspace.
- :param workspace_size: the workspace size; tuple
- :return:
- """
- try:
- if self.app.defaults['units'].upper() == 'MM':
- dims = self.pagesize_dict[workspace_size]
- else:
- dims = (self.pagesize_dict[workspace_size][0]/25.4, self.pagesize_dict[workspace_size][1]/25.4)
- except Exception as e:
- log.debug("PlotCanvasLegacy.draw_workspace() --> %s" % str(e))
- return
- if self.app.defaults['global_workspace_orientation'] == 'l':
- dims = (dims[1], dims[0])
- xdata = [0, dims[0], dims[0], 0, 0]
- ydata = [0, 0, dims[1], dims[1], 0]
- if self.workspace_line not in self.axes.lines:
- self.workspace_line = Line2D(xdata=xdata, ydata=ydata, linewidth=2, antialiased=True, color='#b34d4d')
- self.axes.add_line(self.workspace_line)
- self.canvas.draw()
- self.app.ui.wplace_label.set_value(workspace_size[:3])
- self.app.ui.wplace_label.setToolTip(workspace_size)
- self.fcapp.ui.wplace_label.setStyleSheet("""
- QLabel
- {
- color: black;
- background-color: olivedrab;
- }
- """)
- def delete_workspace(self):
- try:
- self.axes.lines.remove(self.workspace_line)
- self.canvas.draw()
- except Exception:
- pass
- self.fcapp.ui.wplace_label.setStyleSheet("")
- def graph_event_connect(self, event_name, callback):
- """
- Attach an event handler to the canvas through the Matplotlib interface.
- :param event_name: Name of the event
- :type event_name: str
- :param callback: Function to call
- :type callback: func
- :return: Connection id
- :rtype: int
- """
- if event_name == 'mouse_move':
- event_name = 'motion_notify_event'
- if event_name == 'mouse_press':
- event_name = 'button_press_event'
- if event_name == 'mouse_release':
- event_name = 'button_release_event'
- if event_name == 'mouse_double_click':
- return self.double_click.connect(callback)
- if event_name == 'key_press':
- event_name = 'key_press_event'
- return self.canvas.mpl_connect(event_name, callback)
- def graph_event_disconnect(self, cid):
- """
- Disconnect callback with the give id.
- :param cid: Callback id.
- :return: None
- """
- self.canvas.mpl_disconnect(cid)
- def on_new_screen(self):
- pass
- # log.debug("Cache updated the screen!")
- def new_cursor(self, axes=None, big=None):
- # if axes is None:
- # c = MplCursor(axes=self.axes, color='black', linewidth=1)
- # else:
- # c = MplCursor(axes=axes, color='black', linewidth=1)
- if self.app.defaults["global_cursor_color_enabled"]:
- color = self.app.defaults["global_cursor_color"]
- else:
- if self.app.defaults['global_theme'] == 'white':
- color = '#000000'
- else:
- color = '#FFFFFF'
- if big is True:
- self.big_cursor = True
- self.ch_line = self.axes.axhline(color=color, linewidth=self.app.defaults["global_cursor_width"])
- self.cv_line = self.axes.axvline(color=color, linewidth=self.app.defaults["global_cursor_width"])
- self.big_cursor_isdisabled = False
- else:
- self.big_cursor = False
- c = FakeCursor()
- c.mouse_state_updated.connect(self.clear_cursor)
- return c
- def draw_cursor(self, x_pos, y_pos, color=None):
- """
- Draw a cursor at the mouse grid snapped position
- :param x_pos: mouse x position
- :param y_pos: mouse y position
- :param color: custom color of the mouse
- :return:
- """
- # there is no point in drawing mouse cursor when panning as it jumps in a confusing way
- if self.app.app_cursor.enabled is True and self.panning is False:
- if color:
- color = color
- else:
- if self.app.defaults['global_theme'] == 'white':
- color = '#000000'
- else:
- color = '#FFFFFF'
- if self.big_cursor is False:
- try:
- x, y = self.snap(x_pos, y_pos)
- # Pointer (snapped)
- # The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
- # one in the OpenGL(3D) graphic engine
- pointer_size = int(float(self.app.defaults["global_cursor_size"]) * 1.65)
- elements = self.axes.plot(x, y, '+', color=color, ms=pointer_size,
- mew=self.app.defaults["global_cursor_width"], animated=True)
- for el in elements:
- self.axes.draw_artist(el)
- except Exception as e:
- # this happen at app initialization since self.app.geo_editor does not exist yet
- # I could reshuffle the object instantiating order but what's the point?
- # I could crash something else and that's pythonic, too
- log.debug("PlotCanvasLegacy.draw_cursor() big_cursor is False --> %s" % str(e))
- else:
- try:
- self.ch_line.set_markeredgewidth(self.app.defaults["global_cursor_width"])
- self.cv_line.set_markeredgewidth(self.app.defaults["global_cursor_width"])
- except Exception:
- pass
- try:
- x, y = self.app.geo_editor.snap(x_pos, y_pos)
- self.ch_line.set_ydata(y)
- self.cv_line.set_xdata(x)
- except Exception:
- # this happen at app initialization since self.app.geo_editor does not exist yet
- # I could reshuffle the object instantiating order but what's the point?
- # I could crash something else and that's pythonic, too
- pass
- self.canvas.draw_idle()
- self.canvas.blit(self.axes.bbox)
- def clear_cursor(self, state):
- if state is True:
- if self.big_cursor is True and self.big_cursor_isdisabled is True:
- if self.app.defaults["global_cursor_color_enabled"]:
- color = self.app.defaults["global_cursor_color"]
- else:
- if self.app.defaults['global_theme'] == 'white':
- color = '#000000'
- else:
- color = '#FFFFFF'
- self.ch_line = self.axes.axhline(color=color, linewidth=self.app.defaults["global_cursor_width"])
- self.cv_line = self.axes.axvline(color=color, linewidth=self.app.defaults["global_cursor_width"])
- self.big_cursor_isdisabled = False
- if self.app.defaults["global_cursor_color_enabled"] is True:
- self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1], color=self.app.cursor_color_3D)
- else:
- self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])
- else:
- if self.big_cursor is True:
- self.big_cursor_isdisabled = True
- try:
- self.ch_line.remove()
- self.cv_line.remove()
- self.canvas.draw_idle()
- except Exception as e:
- log.debug("PlotCanvasLegacy.clear_cursor() big_cursor is True --> %s" % str(e))
- self.canvas.restore_region(self.background)
- self.canvas.blit(self.axes.bbox)
- def on_key_down(self, event):
- """
- :param event:
- :return:
- """
- log.debug('on_key_down(): ' + str(event.key))
- self.key = event.key
- def on_key_up(self, event):
- """
- :param event:
- :return:
- """
- self.key = None
- def connect(self, event_name, callback):
- """
- Attach an event handler to the canvas through the native Qt interface.
- :param event_name: Name of the event
- :type event_name: str
- :param callback: Function to call
- :type callback: function
- :return: Nothing
- """
- self.canvas.connect(event_name, callback)
- def clear(self):
- """
- Clears axes and figure.
- :return: None
- """
- # Clear
- self.axes.cla()
- try:
- self.figure.clf()
- except KeyError:
- log.warning("KeyError in MPL figure.clf()")
- # Re-build
- self.figure.add_axes(self.axes)
- self.axes.set_aspect(1)
- self.axes.grid(True)
- self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
- self.adjust_axes(-10, -10, 100, 100)
- # Re-draw
- self.canvas.draw_idle()
- def redraw(self):
- """
- Created only to serve for compatibility with the VisPy plotcanvas (the other graphic engine, 3D)
- :return:
- """
- self.clear()
- def adjust_axes(self, xmin, ymin, xmax, ymax):
- """
- Adjusts all axes while maintaining the use of the whole canvas
- and an aspect ratio to 1:1 between x and y axes. The parameters are an original
- request that will be modified to fit these restrictions.
- :param xmin: Requested minimum value for the X axis.
- :type xmin: float
- :param ymin: Requested minimum value for the Y axis.
- :type ymin: float
- :param xmax: Requested maximum value for the X axis.
- :type xmax: float
- :param ymax: Requested maximum value for the Y axis.
- :type ymax: float
- :return: None
- """
- # FlatCAMApp.App.log.debug("PC.adjust_axes()")
- if not self.app.collection.get_list():
- xmin = -10
- ymin = -10
- xmax = 100
- ymax = 100
- width = xmax - xmin
- height = ymax - ymin
- try:
- r = width / height
- except ZeroDivisionError:
- log.error("Height is %f" % height)
- return
- canvas_w, canvas_h = self.canvas.get_width_height()
- canvas_r = float(canvas_w) / canvas_h
- x_ratio = float(self.x_margin) / canvas_w
- y_ratio = float(self.y_margin) / canvas_h
- if r > canvas_r:
- ycenter = (ymin + ymax) / 2.0
- newheight = height * r / canvas_r
- ymin = ycenter - newheight / 2.0
- ymax = ycenter + newheight / 2.0
- else:
- xcenter = (xmax + xmin) / 2.0
- newwidth = width * canvas_r / r
- xmin = xcenter - newwidth / 2.0
- xmax = xcenter + newwidth / 2.0
- # Adjust axes
- for ax in self.figure.get_axes():
- if ax._label != 'base':
- ax.set_frame_on(False) # No frame
- ax.set_xticks([]) # No tick
- ax.set_yticks([]) # No ticks
- ax.patch.set_visible(False) # No background
- ax.set_aspect(1)
- ax.set_xlim((xmin, xmax))
- ax.set_ylim((ymin, ymax))
- ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
- # Sync re-draw to proper paint on form resize
- self.canvas.draw()
- # #### Temporary place-holder for cached update #####
- self.update_screen_request.emit([0, 0, 0, 0, 0])
- def auto_adjust_axes(self, *args):
- """
- Calls ``adjust_axes()`` using the extents of the base axes.
- :rtype : None
- :return: None
- """
- xmin, xmax = self.axes.get_xlim()
- ymin, ymax = self.axes.get_ylim()
- self.adjust_axes(xmin, ymin, xmax, ymax)
- def fit_view(self):
- self.auto_adjust_axes()
- def fit_center(self, loc, rect=None):
- x = loc[0]
- y = loc[1]
- xmin, xmax = self.axes.get_xlim()
- ymin, ymax = self.axes.get_ylim()
- half_width = (xmax - xmin) / 2
- half_height = (ymax - ymin) / 2
- # Adjust axes
- for ax in self.figure.get_axes():
- ax.set_xlim((x - half_width, x + half_width))
- ax.set_ylim((y - half_height, y + half_height))
- # Re-draw
- self.canvas.draw()
- # #### Temporary place-holder for cached update #####
- self.update_screen_request.emit([0, 0, 0, 0, 0])
- def zoom(self, factor, center=None):
- """
- Zooms the plot by factor around a given
- center point. Takes care of re-drawing.
- :param factor: Number by which to scale the plot.
- :type factor: float
- :param center: Coordinates [x, y] of the point around which to scale the plot.
- :type center: list
- :return: None
- """
- factor = 1 / factor
- xmin, xmax = self.axes.get_xlim()
- ymin, ymax = self.axes.get_ylim()
- width = xmax - xmin
- height = ymax - ymin
- if center is None or center == [None, None]:
- center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
- # For keeping the point at the pointer location
- relx = (xmax - center[0]) / width
- rely = (ymax - center[1]) / height
- new_width = width / factor
- new_height = height / factor
- xmin = center[0] - new_width * (1 - relx)
- xmax = center[0] + new_width * relx
- ymin = center[1] - new_height * (1 - rely)
- ymax = center[1] + new_height * rely
- # Adjust axes
- for ax in self.figure.get_axes():
- ax.set_xlim((xmin, xmax))
- ax.set_ylim((ymin, ymax))
- # Async re-draw
- self.canvas.draw_idle()
- # #### Temporary place-holder for cached update #####
- self.update_screen_request.emit([0, 0, 0, 0, 0])
- def pan(self, x, y, idle=True):
- xmin, xmax = self.axes.get_xlim()
- ymin, ymax = self.axes.get_ylim()
- width = xmax - xmin
- height = ymax - ymin
- # Adjust axes
- for ax in self.figure.get_axes():
- ax.set_xlim((xmin + x * width, xmax + x * width))
- ax.set_ylim((ymin + y * height, ymax + y * height))
- # Re-draw
- if idle:
- self.canvas.draw_idle()
- else:
- self.canvas.draw()
- # #### Temporary place-holder for cached update #####
- self.update_screen_request.emit([0, 0, 0, 0, 0])
- def new_axes(self, name):
- """
- Creates and returns an Axes object attached to this object's Figure.
- :param name: Unique label for the axes.
- :return: Axes attached to the figure.
- :rtype: Axes
- """
- new_ax = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
- return new_ax
- def remove_current_axes(self):
- """
- :return: The name of the deleted axes
- """
- axes_to_remove = self.figure.axes.gca()
- current_axes_name = deepcopy(axes_to_remove._label)
- self.figure.axes.remove(axes_to_remove)
- return current_axes_name
- def on_scroll(self, event):
- """
- Scroll event handler.
- :param event: Event object containing the event information.
- :return: None
- """
- # So it can receive key presses
- # self.canvas.grab_focus()
- self.canvas.setFocus()
- # Event info
- # z, direction = event.get_scroll_direction()
- if self.key is None:
- if event.button == 'up':
- self.zoom(1 / 1.5, self.mouse)
- else:
- self.zoom(1.5, self.mouse)
- return
- if self.key == 'shift':
- if event.button == 'up':
- self.pan(0.3, 0)
- else:
- self.pan(-0.3, 0)
- return
- if self.key == 'control':
- if event.button == 'up':
- self.pan(0, 0.3)
- else:
- self.pan(0, -0.3)
- return
- def on_mouse_press(self, event):
- self.is_dragging = True
- self.mouse_press_pos = (event.x, event.y)
- # Check for middle mouse button press
- if self.app.defaults["global_pan_button"] == '2':
- pan_button = 3 # right button for Matplotlib
- else:
- pan_button = 2 # middle button for Matplotlib
- if event.button == pan_button:
- # Prepare axes for pan (using 'matplotlib' pan function)
- self.pan_axes = []
- for a in self.figure.get_axes():
- if (event.x is not None and event.y is not None and a.in_axes(event) and
- a.get_navigate() and a.can_pan()):
- a.start_pan(event.x, event.y, 1)
- self.pan_axes.append(a)
- # Set pan view flag
- if len(self.pan_axes) > 0:
- self.panning = True
- if event.dblclick:
- self.double_click.emit(event)
- def on_mouse_release(self, event):
- mouse_release_pos = (event.x, event.y)
- delta = 0.05
- if abs(self.distance(self.mouse_press_pos, mouse_release_pos)) < delta:
- self.is_dragging = False
- # Check for middle mouse button release to complete pan procedure
- # Check for middle mouse button press
- if self.app.defaults["global_pan_button"] == '2':
- pan_button = 3 # right button for Matplotlib
- else:
- pan_button = 2 # middle button for Matplotlib
- if event.button == pan_button:
- for a in self.pan_axes:
- a.end_pan()
- # Clear pan flag
- self.panning = False
- # And update the cursor
- if self.app.defaults["global_cursor_color_enabled"] is True:
- self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1], color=self.app.cursor_color_3D)
- else:
- self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])
- def on_mouse_move(self, event):
- """
- Mouse movement event handler. Stores the coordinates. Updates view on pan.
- :param event: Contains information about the event.
- :return: None
- """
- try:
- x = float(event.xdata)
- y = float(event.ydata)
- except TypeError:
- return
- self.mouse = [event.xdata, event.ydata]
- self.canvas.restore_region(self.background)
- # Update pan view on mouse move
- if self.panning is True:
- for a in self.pan_axes:
- a.drag_pan(1, event.key, event.x, event.y)
- # x_pan, y_pan = self.app.geo_editor.snap(event.xdata, event.ydata)
- # self.draw_cursor(x_pos=x_pan, y_pos=y_pan)
- # Async re-draw (redraws only on thread idle state, uses timer on backend)
- self.canvas.draw_idle()
- # #### Temporary place-holder for cached update #####
- # self.update_screen_request.emit([0, 0, 0, 0, 0])
- if self.app.defaults["global_cursor_color_enabled"] is True:
- self.draw_cursor(x_pos=x, y_pos=y, color=self.app.cursor_color_3D)
- else:
- self.draw_cursor(x_pos=x, y_pos=y)
- # self.canvas.blit(self.axes.bbox)
- def translate_coords(self, position):
- """
- This does not do much. It's just for code compatibility
- :param position: Mouse event position
- :return: Tuple with mouse position
- """
- return position[0], position[1]
- def on_draw(self, renderer):
- # Store background on canvas redraw
- self.background = self.canvas.copy_from_bbox(self.axes.bbox)
- def get_axes_pixelsize(self):
- """
- Axes size in pixels.
- :return: Pixel width and height
- :rtype: tuple
- """
- bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
- width, height = bbox.width, bbox.height
- width *= self.figure.dpi
- height *= self.figure.dpi
- return width, height
- def get_density(self):
- """
- Returns unit length per pixel on horizontal
- and vertical axes.
- :return: X and Y density
- :rtype: tuple
- """
- xpx, ypx = self.get_axes_pixelsize()
- xmin, xmax = self.axes.get_xlim()
- ymin, ymax = self.axes.get_ylim()
- width = xmax - xmin
- height = ymax - ymin
- return width / xpx, height / ypx
- def snap(self, x, y):
- """
- Adjusts coordinates to snap settings.
- :param x: Input coordinate X
- :param y: Input coordinate Y
- :return: Snapped (x, y)
- """
- snap_x, snap_y = (x, y)
- snap_distance = np.Inf
- # ### Grid snap
- if self.app.grid_status():
- if self.app.defaults["global_gridx"] != 0:
- try:
- snap_x_ = round(x / float(self.app.defaults["global_gridx"])) * \
- float(self.app.defaults["global_gridx"])
- except TypeError:
- snap_x_ = x
- else:
- snap_x_ = x
- # If the Grid_gap_linked on Grid Toolbar is checked then the snap distance on GridY entry will be ignored
- # and it will use the snap distance from GridX entry
- if self.app.ui.grid_gap_link_cb.isChecked():
- if self.app.defaults["global_gridx"] != 0:
- try:
- snap_y_ = round(y / float(self.app.defaults["global_gridx"])) * \
- float(self.app.defaults["global_gridx"])
- except TypeError:
- snap_y_ = y
- else:
- snap_y_ = y
- else:
- if self.app.defaults["global_gridy"] != 0:
- try:
- snap_y_ = round(y / float(self.app.defaults["global_gridy"])) * \
- float(self.app.defaults["global_gridy"])
- except TypeError:
- snap_y_ = y
- else:
- snap_y_ = y
- nearest_grid_distance = self.distance((x, y), (snap_x_, snap_y_))
- if nearest_grid_distance < snap_distance:
- snap_x, snap_y = (snap_x_, snap_y_)
- return snap_x, snap_y
- @staticmethod
- def distance(pt1, pt2):
- return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
- class FakeCursor(QtCore.QObject):
- """
- This is a fake cursor to ensure compatibility with the OpenGL engine (VisPy).
- This way I don't have to chane (disable) things related to the cursor all over when
- using the low performance Matplotlib 2D graphic engine.
- """
- mouse_state_updated = pyqtSignal(bool)
- def __init__(self):
- super().__init__()
- self._enabled = True
- @property
- def enabled(self):
- return True if self._enabled else False
- @enabled.setter
- def enabled(self, value):
- self._enabled = value
- self.mouse_state_updated.emit(value)
- def set_data(self, pos, **kwargs):
- """Internal event handler to draw the cursor when the mouse moves."""
- return
- class ShapeCollectionLegacy:
- """
- This will create the axes for each collection of shapes and will also
- hold the collection of shapes into a dict self._shapes.
- This handles the shapes redraw on canvas.
- """
- def __init__(self, obj, app, name=None, annotation_job=None, linewidth=1):
- """
- :param obj: This is the object to which the shapes collection is attached and for
- which it will have to draw shapes
- :param app: This is the FLatCAM.App usually, needed because we have to access attributes there
- :param name: This is the name given to the Matplotlib axes; it needs to be unique due of
- Matplotlib requurements
- :param annotation_job: Make this True if the job needed is just for annotation
- :param linewidth: THe width of the line (outline where is the case)
- """
- self.obj = obj
- self.app = app
- self.annotation_job = annotation_job
- self._shapes = {}
- self.shape_dict = {}
- self.shape_id = 0
- self._color = None
- self._face_color = None
- self._visible = True
- self._update = False
- self._alpha = None
- self._tool_tolerance = None
- self._tooldia = None
- self._obj = None
- self._gcode_parsed = None
- self._linewidth = linewidth
- if name is None:
- axes_name = self.obj.options['name']
- else:
- axes_name = name
- # Axes must exist and be attached to canvas.
- if axes_name not in self.app.plotcanvas.figure.axes:
- self.axes = self.app.plotcanvas.new_axes(axes_name)
- def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
- update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None,
- linewidth=None):
- """
- This function will add shapes to the shape collection
- :param shape: the Shapely shape to be added to the shape collection
- :param color: edge color of the shape, hex value
- :param face_color: the body color of the shape, hex value
- :param alpha: level of transparency of the shape [0.0 ... 1.0]; Float
- :param visible: if True will allow the shapes to be added
- :param update: not used; just for compatibility with VIsPy canvas
- :param layer: just for compatibility with VIsPy canvas
- :param tolerance: just for compatibility with VIsPy canvas
- :param obj: not used
- :param gcode_parsed: not used; just for compatibility with VIsPy canvas
- :param tool_tolerance: just for compatibility with VIsPy canvas
- :param tooldia:
- :param linewidth: the width of the line
- :return:
- """
- self._color = color if color is not None else "#006E20"
- # self._face_color = face_color if face_color is not None else "#BBF268"
- self._face_color = face_color
- if linewidth is None:
- line_width = self._linewidth
- else:
- line_width = linewidth
- if len(self._color) > 7:
- self._color = self._color[:7]
- if self._face_color is not None:
- if len(self._face_color) > 7:
- self._face_color = self._face_color[:7]
- # self._alpha = int(self._face_color[-2:], 16) / 255
- self._alpha = 0.75
- if alpha is not None:
- self._alpha = alpha
- self._visible = visible
- self._update = update
- # CNCJob object related arguments
- self._obj = obj
- self._gcode_parsed = gcode_parsed
- self._tool_tolerance = tool_tolerance
- self._tooldia = tooldia
- # if self._update:
- # self.clear()
- try:
- for sh in shape:
- self.shape_id += 1
- self.shape_dict.update({
- 'color': self._color,
- 'face_color': self._face_color,
- 'linewidth': line_width,
- 'alpha': self._alpha,
- 'visible': self._visible,
- 'shape': sh
- })
- self._shapes.update({
- self.shape_id: deepcopy(self.shape_dict)
- })
- except TypeError:
- self.shape_id += 1
- self.shape_dict.update({
- 'color': self._color,
- 'face_color': self._face_color,
- 'linewidth': line_width,
- 'alpha': self._alpha,
- 'visible': self._visible,
- 'shape': shape
- })
- self._shapes.update({
- self.shape_id: deepcopy(self.shape_dict)
- })
- return self.shape_id
- def remove(self, shape_id, update=None):
- for k in list(self._shapes.keys()):
- if shape_id == k:
- self._shapes.pop(k, None)
- if update is True:
- self.redraw()
- def clear(self, update=None):
- """
- Clear the canvas of the shapes.
- :param update:
- :return: None
- """
- self._shapes.clear()
- self.shape_id = 0
- self.axes.cla()
- try:
- self.app.plotcanvas.auto_adjust_axes()
- except Exception as e:
- log.debug("ShapeCollectionLegacy.clear() --> %s" % str(e))
- if update is True:
- self.redraw()
- def redraw(self, update_colors=None):
- """
- This draw the shapes in the shapes collection, on canvas
- :return: None
- """
- path_num = 0
- local_shapes = deepcopy(self._shapes)
- try:
- obj_type = self.obj.kind
- except AttributeError:
- obj_type = 'utility'
- # if we don't use this then when adding each new shape, the old ones will be added again, too
- # if obj_type == 'utility':
- # self.axes.patches.clear()
- self.axes.patches.clear()
- for element in local_shapes:
- if local_shapes[element]['visible'] is True:
- if obj_type == 'excellon':
- # Plot excellon (All polygons?)
- if self.obj.options["solid"] and isinstance(local_shapes[element]['shape'], Polygon):
- try:
- patch = PolygonPatch(local_shapes[element]['shape'],
- facecolor=local_shapes[element]['face_color'],
- edgecolor=local_shapes[element]['color'],
- alpha=local_shapes[element]['alpha'],
- zorder=3,
- linewidth=local_shapes[element]['linewidth']
- )
- self.axes.add_patch(patch)
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() excellon poly --> %s" % str(e))
- else:
- try:
- if isinstance(local_shapes[element]['shape'], Polygon):
- x, y = local_shapes[element]['shape'].exterior.coords.xy
- self.axes.plot(x, y, 'r-', linewidth=local_shapes[element]['linewidth'])
- for ints in local_shapes[element]['shape'].interiors:
- x, y = ints.coords.xy
- self.axes.plot(x, y, 'o-', linewidth=local_shapes[element]['linewidth'])
- elif isinstance(local_shapes[element]['shape'], LinearRing):
- x, y = local_shapes[element]['shape'].coords.xy
- self.axes.plot(x, y, 'r-', linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() excellon no poly --> %s" % str(e))
- elif obj_type == 'geometry':
- if type(local_shapes[element]['shape']) == Polygon:
- try:
- x, y = local_shapes[element]['shape'].exterior.coords.xy
- self.axes.plot(x, y, local_shapes[element]['color'],
- linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- for ints in local_shapes[element]['shape'].interiors:
- x, y = ints.coords.xy
- self.axes.plot(x, y, local_shapes[element]['color'],
- linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() geometry poly --> %s" % str(e))
- elif type(local_shapes[element]['shape']) == LineString or \
- type(local_shapes[element]['shape']) == LinearRing:
- try:
- x, y = local_shapes[element]['shape'].coords.xy
- self.axes.plot(x, y, local_shapes[element]['color'],
- linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() geometry no poly --> %s" % str(e))
- elif obj_type == 'gerber':
- if self.obj.options["multicolored"]:
- linespec = '-'
- else:
- linespec = 'k-'
- if self.obj.options["solid"]:
- if update_colors:
- gerber_fill_color = update_colors[0]
- gerber_outline_color = update_colors[1]
- else:
- gerber_fill_color = local_shapes[element]['face_color']
- gerber_outline_color = local_shapes[element]['color']
- try:
- patch = PolygonPatch(local_shapes[element]['shape'],
- facecolor=gerber_fill_color,
- edgecolor=gerber_outline_color,
- alpha=local_shapes[element]['alpha'],
- zorder=2,
- linewidth=local_shapes[element]['linewidth'])
- self.axes.add_patch(patch)
- except AssertionError:
- log.warning("A geometry component was not a polygon:")
- log.warning(str(element))
- except Exception as e:
- log.debug(
- "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e))
- else:
- try:
- x, y = local_shapes[element]['shape'].exterior.xy
- self.axes.plot(x, y, linespec, linewidth=local_shapes[element]['linewidth'])
- for ints in local_shapes[element]['shape'].interiors:
- x, y = ints.coords.xy
- self.axes.plot(x, y, linespec, linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() gerber no 'solid' --> %s" % str(e))
- elif obj_type == 'cncjob':
- if local_shapes[element]['face_color'] is None:
- try:
- linespec = '--'
- linecolor = local_shapes[element]['color']
- # if geo['kind'][0] == 'C':
- # linespec = 'k-'
- x, y = local_shapes[element]['shape'].coords.xy
- self.axes.plot(x, y, linespec, color=linecolor,
- linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() cncjob with face_color --> %s" % str(e))
- else:
- try:
- path_num += 1
- if self.obj.ui.annotation_cb.get_value():
- if isinstance(local_shapes[element]['shape'], Polygon):
- self.axes.annotate(
- str(path_num),
- xy=local_shapes[element]['shape'].exterior.coords[0],
- xycoords='data', fontsize=20)
- else:
- self.axes.annotate(
- str(path_num),
- xy=local_shapes[element]['shape'].coords[0],
- xycoords='data', fontsize=20)
- patch = PolygonPatch(local_shapes[element]['shape'],
- facecolor=local_shapes[element]['face_color'],
- edgecolor=local_shapes[element]['color'],
- alpha=local_shapes[element]['alpha'], zorder=2,
- linewidth=local_shapes[element]['linewidth'])
- self.axes.add_patch(patch)
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() cncjob no face_color --> %s" % str(e))
- elif obj_type == 'utility':
- # not a FlatCAM object, must be utility
- if local_shapes[element]['face_color']:
- try:
- patch = PolygonPatch(local_shapes[element]['shape'],
- facecolor=local_shapes[element]['face_color'],
- edgecolor=local_shapes[element]['color'],
- alpha=local_shapes[element]['alpha'],
- zorder=2,
- linewidth=local_shapes[element]['linewidth'])
- self.axes.add_patch(patch)
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() utility poly with face_color --> %s" % str(e))
- else:
- if isinstance(local_shapes[element]['shape'], Polygon):
- try:
- ext_shape = local_shapes[element]['shape'].exterior
- if ext_shape is not None:
- x, y = ext_shape.xy
- self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- for ints in local_shapes[element]['shape'].interiors:
- if ints is not None:
- x, y = ints.coords.xy
- self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() utility poly no face_color --> %s" % str(e))
- else:
- try:
- if local_shapes[element]['shape'] is not None:
- x, y = local_shapes[element]['shape'].coords.xy
- self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-',
- linewidth=local_shapes[element]['linewidth'])
- except Exception as e:
- log.debug("ShapeCollectionLegacy.redraw() utility lines no face_color --> %s" % str(e))
- self.app.plotcanvas.auto_adjust_axes()
- def set(self, text, pos, visible=True, font_size=16, color=None):
- """
- This will set annotations on the canvas.
- :param text: a list of text elements to be used as annotations
- :param pos: a list of positions for showing the text elements above
- :param visible: if True will display annotations, if False will clear them on canvas
- :param font_size: the font size or the annotations
- :param color: color of the annotations
- :return: None
- """
- if color is None:
- color = "#000000FF"
- if visible is not True:
- self.clear()
- return
- if len(text) != len(pos):
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not annotate due of a difference between the number "
- "of text elements and the number of text positions."))
- return
- for idx in range(len(text)):
- try:
- self.axes.annotate(text[idx], xy=pos[idx], xycoords='data', fontsize=font_size, color=color)
- except Exception as e:
- log.debug("ShapeCollectionLegacy.set() --> %s" % str(e))
- self.app.plotcanvas.auto_adjust_axes()
- @property
- def visible(self):
- return self._visible
- @visible.setter
- def visible(self, value):
- if value is False:
- self.axes.cla()
- self.app.plotcanvas.auto_adjust_axes()
- else:
- if self._visible is False:
- self.redraw()
- self._visible = value
- def update_visibility(self, state, indexes=None):
- if indexes:
- for i in indexes:
- if i in self._shapes:
- self._shapes[i]['visible'] = state
- else:
- for i in self._shapes:
- self._shapes[i]['visible'] = state
- self.redraw()
- @property
- def enabled(self):
- return self._visible
- @enabled.setter
- def enabled(self, value):
- if value is False:
- self.axes.cla()
- self.app.plotcanvas.auto_adjust_axes()
- else:
- if self._visible is False:
- self.redraw()
- self._visible = value
- # class MplCursor(Cursor):
- # """
- # Unfortunately this gets attached to the current axes and if a new axes is added
- # it will not be showed until that axes is deleted.
- # Not the kind of behavior needed here so I don't use it anymore.
- # """
- # def __init__(self, axes, color='red', linewidth=1):
- #
- # super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth)
- # self._enabled = True
- #
- # self.axes = axes
- # self.color = color
- # self.linewidth = linewidth
- #
- # self.x = None
- # self.y = None
- #
- # @property
- # def enabled(self):
- # return True if self._enabled else False
- #
- # @enabled.setter
- # def enabled(self, value):
- # self._enabled = value
- # self.visible = self._enabled
- # self.canvas.draw()
- #
- # def onmove(self, event):
- # pass
- #
- # def set_data(self, event, pos):
- # """Internal event handler to draw the cursor when the mouse moves."""
- # self.x = pos[0]
- # self.y = pos[1]
- #
- # if self.ignore(event):
- # return
- # if not self.canvas.widgetlock.available(self):
- # return
- # if event.inaxes != self.ax:
- # self.linev.set_visible(False)
- # self.lineh.set_visible(False)
- #
- # if self.needclear:
- # self.canvas.draw()
- # self.needclear = False
- # return
- # self.needclear = True
- # if not self.visible:
- # return
- # self.linev.set_xdata((self.x, self.x))
- #
- # self.lineh.set_ydata((self.y, self.y))
- # self.linev.set_visible(self.visible and self.vertOn)
- # self.lineh.set_visible(self.visible and self.horizOn)
- #
- # self._update()
|