From ca87475694829d618bb48a3736c2d92d5e87333d Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 24 Jan 2020 22:12:15 +0200 Subject: [PATCH] - small changes to the Toolchange manual preprocessor - fix for plotting Excellon objects if the color is changed and then the object is moved - laying the GUI for a new Tool: Punch Gerber Tool which will add holes in the Gerber apertures --- FlatCAMApp.py | 5 + FlatCAMObj.py | 23 +- README.md | 6 + flatcamGUI/FlatCAMGUI.py | 2 + flatcamTools/ToolExtractDrills.py | 5 +- flatcamTools/ToolPunchGerber.py | 373 +++++++++++++++++++++++++++++ flatcamTools/__init__.py | 1 + preprocessors/Toolchange_manual.py | 2 + share/punch16.png | Bin 0 -> 551 bytes share/punch32.png | Bin 0 -> 839 bytes 10 files changed, 409 insertions(+), 8 deletions(-) create mode 100644 flatcamTools/ToolPunchGerber.py create mode 100644 share/punch16.png create mode 100644 share/punch32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 9ddc1770..05cc7136 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2517,6 +2517,7 @@ class App(QtCore.QObject): self.fiducial_tool = None self.edrills_tool = None self.align_objects_tool = None + self.punch_tool = None # always install tools only after the shell is initialized because the self.inform.emit() depends on shell try: @@ -3150,6 +3151,9 @@ class App(QtCore.QObject): self.qrcode_tool.install(icon=QtGui.QIcon(self.resource_location + '/qrcode32.png'), pos=self.ui.menutool) + self.punch_tool = ToolPunchGerber(self) + self.punch_tool.install(icon=QtGui.QIcon(self.resource_location + '/punch32.png'), pos=self.ui.menutool) + self.transform_tool = ToolTransform(self) self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'), pos=self.ui.menuoptions, separator=True) @@ -3291,6 +3295,7 @@ class App(QtCore.QObject): self.ui.qrcode_btn.triggered.connect(lambda: self.qrcode_tool.run(toggle=True)) self.ui.copperfill_btn.triggered.connect(lambda: self.copper_thieving_tool.run(toggle=True)) self.ui.fiducials_btn.triggered.connect(lambda: self.fiducial_tool.run(toggle=True)) + self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True)) def object2editor(self): """ diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 751063a7..8cbc596a 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1004,15 +1004,21 @@ class FlatCAMGerber(FlatCAMObj, Gerber): def geo_init(geo_obj, app_obj): assert isinstance(geo_obj, FlatCAMGeometry) if isinstance(self.solid_geometry, list): - self.solid_geometry = cascaded_union(self.solid_geometry) + try: + self.solid_geometry = MultiPolygon(self.solid_geometry) + except Exception: + self.solid_geometry = cascaded_union(self.solid_geometry) bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"])) if not self.options["noncopperrounded"]: bounding_box = bounding_box.envelope non_copper = bounding_box.difference(self.solid_geometry) + + if non_copper is None or non_copper.is_empty: + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) + return "fail" geo_obj.solid_geometry = non_copper - # TODO: Check for None self.app.new_object("geometry", name, geo_init) def on_generatebb_button_click(self, *args): @@ -1024,12 +1030,19 @@ class FlatCAMGerber(FlatCAMObj, Gerber): assert isinstance(geo_obj, FlatCAMGeometry) if isinstance(self.solid_geometry, list): - self.solid_geometry = MultiPolygon(self.solid_geometry) + try: + self.solid_geometry = MultiPolygon(self.solid_geometry) + except Exception: + self.solid_geometry = cascaded_union(self.solid_geometry) # Bounding box with rounded corners bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"])) if not self.options["bboxrounded"]: # Remove rounded corners bounding_box = bounding_box.envelope + + if bounding_box is None or bounding_box.is_empty: + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) + return "fail" geo_obj.solid_geometry = bounding_box self.app.new_object("geometry", name, geo_init) @@ -3641,8 +3654,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if self.options["solid"]: for geo in self.solid_geometry: self.add_shape(shape=geo, - color=self.app.defaults["excellon_plot_line"], - face_color=self.app.defaults["excellon_plot_fill"], + color=self.outline_color, + face_color=self.fill_color, visible=visible, layer=2) else: diff --git a/README.md b/README.md index 8c96a14b..33637b91 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing. ================================================= +24.02.2020 + +- small changes to the Toolchange manual preprocessor +- fix for plotting Excellon objects if the color is changed and then the object is moved +- laying the GUI for a new Tool: Punch Gerber Tool which will add holes in the Gerber apertures + 22.01.2020 - fixed a bug in the bounding box generation diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 9f7700d3..2c921197 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -920,6 +920,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/fiducials_32.png'), _("Fiducials Tool")) self.cal_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/calibrate_32.png'), _("Calibration Tool")) + self.punch_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool")) # ######################################################################## # ########################## Excellon Editor Toolbar# #################### diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index d75a61b5..6973aecc 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -43,8 +43,7 @@ class ToolExtractDrills(FlatCAMTool): """) self.layout.addWidget(title_label) - self.empty_lb = QtWidgets.QLabel("") - self.layout.addWidget(self.empty_lb) + self.layout.addWidget(QtWidgets.QLabel("")) # ## Grid Layout grid_lay = QtWidgets.QGridLayout() @@ -128,7 +127,7 @@ class ToolExtractDrills(FlatCAMTool): self.method_label = QtWidgets.QLabel('%s' % _("Method")) grid1.addWidget(self.method_label, 2, 0, 1, 2) - # ## Axis + # ## Holes Size self.hole_size_radio = RadioSet( [ {'label': _("Fixed Diameter"), 'value': 'fixed'}, diff --git a/flatcamTools/ToolPunchGerber.py b/flatcamTools/ToolPunchGerber.py new file mode 100644 index 00000000..17fa3a80 --- /dev/null +++ b/flatcamTools/ToolPunchGerber.py @@ -0,0 +1,373 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File Author: Marius Adrian Stanciu (c) # +# Date: 1/24/2020 # +# MIT Licence # +# ########################################################## + +from PyQt5 import QtGui, QtCore, QtWidgets + +from FlatCAMTool import FlatCAMTool +from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \ + OptionalHideInputSection, OptionalInputSection, FCComboBox + +from copy import deepcopy +import logging +from shapely.geometry import Polygon, MultiPolygon, Point + +from reportlab.graphics import renderPDF +from reportlab.pdfgen import canvas +from reportlab.graphics import renderPM +from reportlab.lib.units import inch, mm +from reportlab.lib.pagesizes import landscape, portrait + +from svglib.svglib import svg2rlg +from xml.dom.minidom import parseString as parse_xml_string +from lxml import etree as ET +from io import StringIO + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class ToolPunchGerber(FlatCAMTool): + + toolName = _("Punch Gerber") + + def __init__(self, app): + FlatCAMTool.__init__(self, app) + + self.decimals = self.app.decimals + + # Title + title_label = QtWidgets.QLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + self.layout.addWidget(title_label) + + # Punch Drill holes + self.layout.addWidget(QtWidgets.QLabel("")) + + # ## Grid Layout + grid_lay = QtWidgets.QGridLayout() + self.layout.addLayout(grid_lay) + grid_lay.setColumnStretch(0, 1) + grid_lay.setColumnStretch(1, 0) + + # ## Gerber Object + self.gerber_object_combo = QtWidgets.QComboBox() + self.gerber_object_combo.setModel(self.app.collection) + self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.gerber_object_combo.setCurrentIndex(1) + + self.grb_label = QtWidgets.QLabel("%s:" % _("GERBER")) + self.grb_label.setToolTip('%s.' % _("Gerber into which to punch holes")) + + grid_lay.addWidget(self.grb_label, 0, 0, 1, 2) + grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid_lay.addWidget(separator_line, 2, 0, 1, 2) + + # Grid Layout + grid0 = QtWidgets.QGridLayout() + self.layout.addLayout(grid0) + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + + self.method_label = QtWidgets.QLabel('%s:' % _("Method")) + self.method_label.setToolTip( + _("The punch hole source can be:\n" + "- Excellon -> an Excellon holes center will serve as reference.\n" + "- Fixed Diameter -> will try to use the pads center as reference.\n" + "- Fixed Annular Ring -> will try to use the pads center as reference.\n" + "- Proportional -> will try to use the pads center as reference.\n") + ) + self.method_punch = RadioSet( + [ + {'label': _('Excellon'), 'value': 'exc'}, + {'label': _("Fixed Diameter"), 'value': 'fixed'}, + {'label': _("Fixed Annular Ring"), 'value': 'ring'}, + {'label': _("Proportional"), 'value': 'prop'} + ], + orientation='vertical', + stretch=False) + grid0.addWidget(self.method_label, 0, 0) + grid0.addWidget(self.method_punch, 0, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 1, 0, 1, 2) + + self.exc_label = QtWidgets.QLabel('%s' % _("Excellon")) + self.exc_label.setToolTip( + _("Remove the geometry of Excellon from the Gerber to create the holes in pads.") + ) + + self.exc_combo = QtWidgets.QComboBox() + self.exc_combo.setModel(self.app.collection) + self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) + self.exc_combo.setCurrentIndex(1) + + grid0.addWidget(self.exc_label, 2, 0, 1, 2) + grid0.addWidget(self.exc_combo, 3, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 5, 0, 1, 2) + + # Fixed Dia + self.fixed_label = QtWidgets.QLabel('%s' % _("Fixed Diameter")) + grid0.addWidget(self.fixed_label, 6, 0, 1, 2) + + # Diameter value + self.dia_entry = FCDoubleSpinner() + self.dia_entry.set_precision(self.decimals) + self.dia_entry.set_range(0.0000, 9999.9999) + + self.dia_label = QtWidgets.QLabel('%s:' % _("Value")) + self.dia_label.setToolTip( + _("Fixed hole diameter.") + ) + + grid0.addWidget(self.dia_label, 8, 0) + grid0.addWidget(self.dia_entry, 8, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 9, 0, 1, 2) + + self.ring_frame = QtWidgets.QFrame() + self.ring_frame.setContentsMargins(0, 0, 0, 0) + grid0.addWidget(self.ring_frame, 10, 0, 1, 2) + + self.ring_box = QtWidgets.QVBoxLayout() + self.ring_box.setContentsMargins(0, 0, 0, 0) + self.ring_frame.setLayout(self.ring_box) + + # ## Grid Layout + grid1 = QtWidgets.QGridLayout() + grid1.setColumnStretch(0, 0) + grid1.setColumnStretch(1, 1) + self.ring_box.addLayout(grid1) + + # Annular Ring value + self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) + self.ring_label.setToolTip( + _("The size of annular ring.\n" + "The copper sliver between the drill hole exterior\n" + "and the margin of the copper pad.") + ) + grid1.addWidget(self.ring_label, 0, 0, 1, 2) + + # Circular Annular Ring Value + self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular")) + self.circular_ring_label.setToolTip( + _("The size of annular ring for circular pads.") + ) + + self.circular_ring_entry = FCDoubleSpinner() + self.circular_ring_entry.set_precision(self.decimals) + self.circular_ring_entry.set_range(0.0000, 9999.9999) + + grid1.addWidget(self.circular_ring_label, 1, 0) + grid1.addWidget(self.circular_ring_entry, 1, 1) + + # Oblong Annular Ring Value + self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong")) + self.oblong_ring_label.setToolTip( + _("The size of annular ring for oblong pads.") + ) + + self.oblong_ring_entry = FCDoubleSpinner() + self.oblong_ring_entry.set_precision(self.decimals) + self.oblong_ring_entry.set_range(0.0000, 9999.9999) + + grid1.addWidget(self.oblong_ring_label, 2, 0) + grid1.addWidget(self.oblong_ring_entry, 2, 1) + + # Square Annular Ring Value + self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square")) + self.square_ring_label.setToolTip( + _("The size of annular ring for square pads.") + ) + + self.square_ring_entry = FCDoubleSpinner() + self.square_ring_entry.set_precision(self.decimals) + self.square_ring_entry.set_range(0.0000, 9999.9999) + + grid1.addWidget(self.square_ring_label, 3, 0) + grid1.addWidget(self.square_ring_entry, 3, 1) + + # Rectangular Annular Ring Value + self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular")) + self.rectangular_ring_label.setToolTip( + _("The size of annular ring for rectangular pads.") + ) + + self.rectangular_ring_entry = FCDoubleSpinner() + self.rectangular_ring_entry.set_precision(self.decimals) + self.rectangular_ring_entry.set_range(0.0000, 9999.9999) + + grid1.addWidget(self.rectangular_ring_label, 4, 0) + grid1.addWidget(self.rectangular_ring_entry, 4, 1) + + # Others Annular Ring Value + self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others")) + self.other_ring_label.setToolTip( + _("The size of annular ring for other pads.") + ) + + self.other_ring_entry = FCDoubleSpinner() + self.other_ring_entry.set_precision(self.decimals) + self.other_ring_entry.set_range(0.0000, 9999.9999) + + grid1.addWidget(self.other_ring_label, 5, 0) + grid1.addWidget(self.other_ring_entry, 5, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 11, 0, 1, 2) + + # Proportional value + self.prop_label = QtWidgets.QLabel('%s' % _("Proportional Diameter")) + grid0.addWidget(self.prop_label, 12, 0, 1, 2) + + # Diameter value + self.factor_entry = FCDoubleSpinner(suffix='%') + self.factor_entry.set_precision(self.decimals) + self.factor_entry.set_range(0.0000, 100.0000) + self.factor_entry.setSingleStep(0.1) + + self.factor_label = QtWidgets.QLabel('%s:' % _("Value")) + self.factor_label.setToolTip( + _("Proportional Diameter.\n" + "The drill diameter will be a fraction of the pad size.") + ) + + grid0.addWidget(self.factor_label, 13, 0) + grid0.addWidget(self.factor_entry, 13, 1) + + separator_line3 = QtWidgets.QFrame() + separator_line3.setFrameShape(QtWidgets.QFrame.HLine) + separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line3, 14, 0, 1, 2) + + # Buttons + self.punch_object_button = QtWidgets.QPushButton(_("Punch Gerber")) + self.punch_object_button.setToolTip( + _("Create a Gerber object from the selected object, within\n" + "the specified box.") + ) + self.punch_object_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.punch_object_button) + + self.layout.addStretch() + + # ## Reset Tool + self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.reset_button) + + self.units = self.app.defaults['units'] + + # ## Signals + + self.method_punch.activated_custom.connect(self.on_method) + self.reset_button.clicked.connect(self.set_tool_ui) + + def run(self, toggle=True): + self.app.report_usage("ToolPunchGerber()") + + if toggle: + # if the splitter is hidden, display it, else hide it but only if the current widget is the same + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + else: + try: + if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName: + # if tab is populated with the tool but it does not have the focus, focus on it + if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab: + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + else: + self.app.ui.splitter.setSizes([0, 1]) + except AttributeError: + pass + else: + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + FlatCAMTool.run(self) + + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Punch Tool")) + + def install(self, icon=None, separator=None, **kwargs): + FlatCAMTool.install(self, icon, separator, shortcut='ALT+H', **kwargs) + + def set_tool_ui(self): + self.reset_fields() + + self.method_punch.set_value('exc') + + def on_method(self, val): + self.exc_label.setEnabled(False) + self.exc_combo.setEnabled(False) + self.fixed_label.setEnabled(False) + self.dia_label.setEnabled(False) + self.dia_entry.setEnabled(False) + self.ring_frame.setEnabled(False) + self.prop_label.setEnabled(False) + self.factor_label.setEnabled(False) + self.factor_entry.setEnabled(False) + + if val == 'exc': + self.exc_label.setEnabled(True) + self.exc_combo.setEnabled(True) + elif val == 'fixed': + self.fixed_label.setEnabled(True) + self.dia_label.setEnabled(True) + self.dia_entry.setEnabled(True) + elif val == 'ring': + self.ring_frame.setEnabled(True) + elif val == 'prop': + self.prop_label.setEnabled(True) + self.factor_label.setEnabled(True) + self.factor_entry.setEnabled(True) + + def reset_fields(self): + self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index a593e6c4..b503ca38 100644 --- a/flatcamTools/__init__.py +++ b/flatcamTools/__init__.py @@ -38,3 +38,4 @@ from flatcamTools.ToolSolderPaste import SolderPaste from flatcamTools.ToolSub import ToolSub from flatcamTools.ToolTransform import ToolTransform +from flatcamTools.ToolPunchGerber import ToolPunchGerber diff --git a/preprocessors/Toolchange_manual.py b/preprocessors/Toolchange_manual.py index dc7a304d..ef583a7c 100644 --- a/preprocessors/Toolchange_manual.py +++ b/preprocessors/Toolchange_manual.py @@ -119,6 +119,7 @@ M0 G00 Z{z_toolchange} (MSG, Now the tool can be tightened more securely.) M0 +(MSG, Drilling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills}) """.format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange), y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange), z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange), @@ -139,6 +140,7 @@ M0 G00 Z{z_toolchange} (MSG, Now the tool can be tightened more securely.) M0 +(MSG, Milling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills}) """.format( z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange), tool=int(p.tool), diff --git a/share/punch16.png b/share/punch16.png new file mode 100644 index 0000000000000000000000000000000000000000..65c2581087206249e9a21e88c4d510da661ad720 GIT binary patch literal 551 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`%C|gc+x5^GO2**-JcqUD=;7O7NPh)T;>`01AEeba4#PIA40tG@QyEbfVu4jt_*nxNsgL}qHzshJ{A-TnB^^{u<3Z#D5xHOh36 zd%v%Sz16@Et*namy>P@9den$^;><;7O&{tELB9d$78~xPD zFLHi+M^D`0OTJzQ_oQ!EZ11vbmJd$8ly_wE!7%CSC*u7~ddfkYQ`Q0lK()j*q9i4; zB-JXpC>2OC7#SED=o%R68kvL`8e5qfTN#^a8yHv_81!^bI)$PkH$NpatrE9}zgM)% yfEpx0HU#IVm6RtIr81P4m+NKbWfvzW7NqLs7p2dBXCnnv#Ng@b=d#Wzp$Py-JjYuA literal 0 HcmV?d00001 diff --git a/share/punch32.png b/share/punch32.png new file mode 100644 index 0000000000000000000000000000000000000000..b3c130ba19b5f210fe256bfd6f5e78bd3cdf4a56 GIT binary patch literal 839 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*`F{<@S5^1nR``OlaA7emy0kz^$EhkvU>Rp7 zOJ>3PCdX?EQXMIG*y8$bJ&xbO<~unr?uXxBG1X(1Rxw7}KA$4(ZV2pX ze%F?JH_}{frz+c7PT{!4=l5tDhnp_r^5S<}x~kV_rNyF{g^xE)V!R}D|7hHai>t21 zY3p7{yuhrVBV@khT*fDxqSDL_fgzLMzuWm}t^BMn)@r`p?M0KBxxQF68_mC>{_NV1 zv~;D@X>Ze(J8jv$GU&{v1=efxSXAS_g~S!POndE?Gs~zr-ur=Gg{J-0nL2-0o1dS< zeaCXW=n|(tD{JQZv8)T6zGy>-(q1X1@+;Lx69rX&HU0M7+kfhi(zNbdVmoSot)219 z*=3=Vq^H1Y-c?+8@&Z?@i*4rZ?)G2xC4QAbM(l&KjwtJmJB~cR^8K8c;nT*|g29#> zf20Wg{eSE6<0Zx`j1ofAR;1lCyskXEmUaD}jYctS1wrnQTGtsEd`$9{KTzra^p#lzv6Z2Twt<0_fkC8k%RCefx%nxXX_dG&Xyn-_0X0a1YzWRzD=AMb mN@XZ7FW1Y=%Pvk%EJ)SMFG`>N&PEETh{4m<&t;ucLK6VOOi9ZC literal 0 HcmV?d00001