| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749 |
- # ##########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # File Author: Marius Adrian Stanciu (c) #
- # Date: 10/25/2019 #
- # MIT Licence #
- # ##########################################################
- from PyQt5 import QtWidgets, QtCore
- import FlatCAMApp
- from FlatCAMTool import FlatCAMTool
- from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
- from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon
- import shapely.geometry.base as base
- from shapely.ops import cascaded_union, unary_union
- from shapely.geometry import Polygon, MultiPolygon
- from shapely.geometry import box as box
- import logging
- from copy import deepcopy
- import numpy as np
- from collections import Iterable
- import gettext
- import FlatCAMTranslation as fcTranslate
- import builtins
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- log = logging.getLogger('base')
- class ToolCopperFill(FlatCAMTool):
- toolName = _("Copper Fill Tool")
- def __init__(self, app):
- FlatCAMTool.__init__(self, app)
- self.app = app
- self.canvas = self.app.plotcanvas
- self.decimals = 4
- self.units = ''
- # ## Title
- title_label = QtWidgets.QLabel("%s" % self.toolName)
- title_label.setStyleSheet("""
- QLabel
- {
- font-size: 16px;
- font-weight: bold;
- }
- """)
- self.layout.addWidget(title_label)
- self.layout.addWidget(QtWidgets.QLabel(''))
- # ## Grid Layout
- i_grid_lay = QtWidgets.QGridLayout()
- self.layout.addLayout(i_grid_lay)
- i_grid_lay.setColumnStretch(0, 0)
- i_grid_lay.setColumnStretch(1, 1)
- self.grb_object_combo = QtWidgets.QComboBox()
- self.grb_object_combo.setModel(self.app.collection)
- self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
- self.grb_object_combo.setCurrentIndex(1)
- self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
- self.grbobj_label.setToolTip(
- _("Gerber Object to which will be added a copper fill.")
- )
- i_grid_lay.addWidget(self.grbobj_label, 0, 0)
- i_grid_lay.addWidget(self.grb_object_combo, 0, 1, 1, 2)
- i_grid_lay.addWidget(QtWidgets.QLabel(''), 1, 0)
- # ## Grid Layout
- grid_lay = QtWidgets.QGridLayout()
- self.layout.addLayout(grid_lay)
- grid_lay.setColumnStretch(0, 0)
- grid_lay.setColumnStretch(1, 1)
- self.copper_fill_label = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
- self.copper_fill_label.setToolTip(
- _("Parameters used for this tool.")
- )
- grid_lay.addWidget(self.copper_fill_label, 0, 0, 1, 2)
- # CLEARANCE #
- self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
- self.clearance_label.setToolTip(
- _("This set the distance between the copper fill components\n"
- "(the polygon fill may be split in multiple polygons)\n"
- "and the copper traces in the Gerber file.")
- )
- self.clearance_entry = FCDoubleSpinner()
- self.clearance_entry.setMinimum(0.00001)
- self.clearance_entry.set_precision(self.decimals)
- self.clearance_entry.setSingleStep(0.1)
- grid_lay.addWidget(self.clearance_label, 1, 0)
- grid_lay.addWidget(self.clearance_entry, 1, 1)
- # MARGIN #
- self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
- self.margin_label.setToolTip(
- _("Bounding box margin.")
- )
- self.margin_entry = FCDoubleSpinner()
- self.margin_entry.setMinimum(0.0)
- self.margin_entry.set_precision(self.decimals)
- self.margin_entry.setSingleStep(0.1)
- grid_lay.addWidget(self.margin_label, 2, 0)
- grid_lay.addWidget(self.margin_entry, 2, 1)
- # Reference #
- self.reference_radio = RadioSet([
- {'label': _('Itself'), 'value': 'itself'},
- {"label": _("Area Selection"), "value": "area"},
- {'label': _("Reference Object"), 'value': 'box'}
- ], orientation='vertical', stretch=False)
- self.reference_label = QtWidgets.QLabel(_("Reference:"))
- self.reference_label.setToolTip(
- _("- 'Itself' - the copper fill extent is based on the object that is copper cleared.\n "
- "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
- "- 'Reference Object' - will do copper filling within the area specified by another object.")
- )
- grid_lay.addWidget(self.reference_label, 3, 0)
- grid_lay.addWidget(self.reference_radio, 3, 1)
- self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
- self.box_combo_type_label.setToolTip(
- _("The type of FlatCAM object to be used as copper filling reference.\n"
- "It can be Gerber, Excellon or Geometry.")
- )
- self.box_combo_type = QtWidgets.QComboBox()
- self.box_combo_type.addItem(_("Reference Gerber"))
- self.box_combo_type.addItem(_("Reference Excellon"))
- self.box_combo_type.addItem(_("Reference Geometry"))
- grid_lay.addWidget(self.box_combo_type_label, 4, 0)
- grid_lay.addWidget(self.box_combo_type, 4, 1)
- self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
- self.box_combo_label.setToolTip(
- _("The FlatCAM object to be used as non copper clearing reference.")
- )
- self.box_combo = QtWidgets.QComboBox()
- self.box_combo.setModel(self.app.collection)
- self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
- self.box_combo.setCurrentIndex(1)
- grid_lay.addWidget(self.box_combo_label, 5, 0)
- grid_lay.addWidget(self.box_combo, 5, 1)
- self.box_combo.hide()
- self.box_combo_label.hide()
- self.box_combo_type.hide()
- self.box_combo_type_label.hide()
- # Bounding Box Type #
- self.bbox_type_radio = RadioSet([
- {'label': _('Rectangular'), 'value': 'rect'},
- {"label": _("Minimal"), "value": "min"}
- ], stretch=False)
- self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
- self.bbox_type_label.setToolTip(
- _("- 'Rectangular' - the bounding box will be of rectangular shape.\n "
- "- 'Minimal' - the bounding box will be the convex hull shape.")
- )
- grid_lay.addWidget(self.bbox_type_label, 6, 0)
- grid_lay.addWidget(self.bbox_type_radio, 6, 1)
- self.bbox_type_label.hide()
- self.bbox_type_radio.hide()
- # ## Insert Copper Fill
- self.fill_button = QtWidgets.QPushButton(_("Insert Copper Fill"))
- self.fill_button.setToolTip(
- _("Will add a polygon (may be split in multiple parts)\n"
- "that will surround the actual Gerber traces at a certain distance.")
- )
- self.layout.addWidget(self.fill_button)
- self.layout.addStretch()
- # Objects involved in Copper filling
- self.grb_object = None
- self.ref_obj = None
- self.sel_rect = list()
- # Events ID
- self.mr = None
- self.mm = None
- # Mouse cursor positions
- self.mouse_is_dragging = False
- self.cursor_pos = (0, 0)
- self.first_click = False
- self.area_method = False
- # Tool properties
- self.clearance_val = None
- self.margin_val = None
- self.geo_steps_per_circle = 128
- # SIGNALS
- self.fill_button.clicked.connect(self.execute)
- self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
- self.reference_radio.group_toggle_fn = self.on_toggle_reference
- def run(self, toggle=True):
- self.app.report_usage("ToolCopperFill()")
- 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, _("Copper Fill Tool"))
- def install(self, icon=None, separator=None, **kwargs):
- FlatCAMTool.install(self, icon, separator, shortcut='ALT+F', **kwargs)
- def set_tool_ui(self):
- self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
- self.clearance_entry.set_value(float(self.app.defaults["tools_copperfill_clearance"]))
- self.margin_entry.set_value(float(self.app.defaults["tools_copperfill_margin"]))
- self.reference_radio.set_value(self.app.defaults["tools_copperfill_reference"])
- self.bbox_type_radio.set_value(self.app.defaults["tools_copperfill_box_type"])
- self.geo_steps_per_circle = int(self.app.defaults["tools_copperfill_circle_steps"])
- self.area_method = False
- def on_combo_box_type(self):
- obj_type = self.box_combo_type.currentIndex()
- self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
- self.box_combo.setCurrentIndex(0)
- def on_toggle_reference(self):
- if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
- self.box_combo.hide()
- self.box_combo_label.hide()
- self.box_combo_type.hide()
- self.box_combo_type_label.hide()
- else:
- self.box_combo.show()
- self.box_combo_label.show()
- self.box_combo_type.show()
- self.box_combo_type_label.show()
- if self.reference_radio.get_value() == "itself":
- self.bbox_type_label.show()
- self.bbox_type_radio.show()
- else:
- self.bbox_type_label.hide()
- self.bbox_type_radio.hide()
- def execute(self):
- self.app.call_source = "copperfill_tool"
- self.clearance_val = self.clearance_entry.get_value()
- self.margin_val = self.margin_entry.get_value()
- reference_method = self.reference_radio.get_value()
- # get the Gerber object on which the Copper fill will be inserted
- selection_index = self.grb_object_combo.currentIndex()
- model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
- try:
- self.grb_object = model_index.internalPointer().obj
- except Exception as e:
- log.debug("ToolCopperFill.execute() --> %s" % str(e))
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
- return 'fail'
- if reference_method == 'itself':
- bound_obj_name = self.grb_object_combo.currentText()
- # Get reference object.
- try:
- self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
- except Exception as e:
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(e)))
- return "Could not retrieve object: %s" % self.obj_name
- self.on_copper_fill(
- fill_obj=self.grb_object,
- c_val=self.clearance_val,
- margin=self.margin_val
- )
- elif reference_method == 'area':
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
- self.area_method = True
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.app.mp)
- self.app.plotcanvas.graph_event_disconnect(self.app.mm)
- self.app.plotcanvas.graph_event_disconnect(self.app.mr)
- self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
- self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
- elif reference_method == 'box':
- bound_obj_name = self.box_combo.currentText()
- # Get reference object.
- try:
- self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
- except Exception as e:
- self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), bound_obj_name))
- return "Could not retrieve object: %s. Error: %s" % (bound_obj_name, str(e))
- self.on_copper_fill(
- fill_obj=self.grb_object,
- ref_obj=self.ref_obj,
- c_val=self.clearance_val,
- margin=self.margin_val
- )
- # To be called after clicking on the plot.
- def on_mouse_release(self, event):
- if self.app.is_legacy is False:
- event_pos = event.pos
- # event_is_dragging = event.is_dragging
- right_button = 2
- else:
- event_pos = (event.xdata, event.ydata)
- # event_is_dragging = self.app.plotcanvas.is_dragging
- right_button = 3
- event_pos = self.app.plotcanvas.translate_coords(event_pos)
- # do clear area only for left mouse clicks
- if event.button == 1:
- if self.first_click is False:
- self.first_click = True
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the filling area."))
- self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
- if self.app.grid_status() is True:
- self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
- else:
- self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
- self.app.delete_selection_shape()
- if self.app.grid_status() is True:
- curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
- else:
- curr_pos = (event_pos[0], event_pos[1])
- x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
- x1, y1 = curr_pos[0], curr_pos[1]
- pt1 = (x0, y0)
- pt2 = (x1, y0)
- pt3 = (x1, y1)
- pt4 = (x0, y1)
- new_rectangle = Polygon([pt1, pt2, pt3, pt4])
- self.sel_rect.append(new_rectangle)
- # add a temporary shape on canvas
- self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
- self.first_click = False
- return
- elif event.button == right_button and self.mouse_is_dragging is False:
- self.area_method = False
- self.first_click = False
- self.delete_tool_selection_shape()
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.mr)
- self.app.plotcanvas.graph_event_disconnect(self.mm)
- self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
- self.app.on_mouse_click_over_plot)
- self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
- self.app.on_mouse_move_over_plot)
- self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
- self.app.on_mouse_click_release_over_plot)
- if len(self.sel_rect) == 0:
- return
- self.sel_rect = cascaded_union(self.sel_rect)
- if not isinstance(self.sel_rect, Iterable):
- self.sel_rect = [self.sel_rect]
- self.on_copper_fill(
- fill_obj=self.grb_object,
- ref_obj=self.sel_rect,
- c_val=self.clearance_val,
- margin=self.margin_val
- )
- # called on mouse move
- def on_mouse_move(self, event):
- if self.app.is_legacy is False:
- event_pos = event.pos
- event_is_dragging = event.is_dragging
- # right_button = 2
- else:
- event_pos = (event.xdata, event.ydata)
- event_is_dragging = self.app.plotcanvas.is_dragging
- # right_button = 3
- curr_pos = self.app.plotcanvas.translate_coords(event_pos)
- # detect mouse dragging motion
- if event_is_dragging is True:
- self.mouse_is_dragging = True
- else:
- self.mouse_is_dragging = False
- # update the cursor position
- if self.app.grid_status() is True:
- # Update cursor
- curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
- self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
- symbol='++', edge_color=self.app.cursor_color_3D,
- size=self.app.defaults["global_cursor_size"])
- # update the positions on status bar
- self.app.ui.position_label.setText(" <b>X</b>: %.4f "
- "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
- if self.cursor_pos is None:
- self.cursor_pos = (0, 0)
- dx = curr_pos[0] - float(self.cursor_pos[0])
- dy = curr_pos[1] - float(self.cursor_pos[1])
- self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
- "%.4f " % (dx, dy))
- # draw the utility geometry
- if self.first_click:
- self.app.delete_selection_shape()
- self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
- coords=(curr_pos[0], curr_pos[1]))
- def on_copper_fill(self, fill_obj, ref_obj=None, c_val=None, margin=None, run_threaded=True):
- """
- :param fill_obj:
- :param ref_obj:
- :param c_val:
- :param margin:
- :param run_threaded:
- :return:
- """
- if run_threaded:
- proc = self.app.proc_container.new('%s ...' % _("Copper filling"))
- else:
- self.app.proc_container.view.set_busy('%s ...' % _("Copper filling"))
- QtWidgets.QApplication.processEvents()
- # #####################################################################
- # ####### Read the parameters #########################################
- # #####################################################################
- log.debug("Copper Filling Tool started. Reading parameters.")
- self.app.inform.emit(_("Copper Filling Tool started. Reading parameters."))
- ref_selected = self.reference_radio.get_value()
- if c_val is None:
- c_val = float(self.app.defaults["tools_copperfill_clearance"])
- if margin is None:
- margin = float(self.app.defaults["tools_copperfill_margin"])
- # make sure that the source object solid geometry is an Iterable
- if not isinstance(self.grb_object.solid_geometry, Iterable):
- self.grb_object.solid_geometry = [self.grb_object.solid_geometry]
- # #########################################################################################
- # Prepare isolation polygon. This will create the clearance over the Gerber features ######
- # #########################################################################################
- log.debug("Copper Filling Tool. Preparing isolation polygons.")
- self.app.inform.emit(_("Copper Filling Tool. Preparing isolation polygons."))
- # variables to display the percentage of work done
- geo_len = 0
- try:
- for pol in self.grb_object.solid_geometry:
- geo_len += 1
- except TypeError:
- geo_len = 1
- old_disp_number = 0
- pol_nr = 0
- clearance_geometry = []
- try:
- for pol in self.grb_object.solid_geometry:
- if self.app.abort_flag:
- # graceful abort requested by the user
- raise FlatCAMApp.GracefulException
- clearance_geometry.append(
- pol.buffer(c_val, int(int(self.geo_steps_per_circle) / 4))
- )
- pol_nr += 1
- disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
- if old_disp_number < disp_number <= 100:
- self.app.proc_container.update_view_text(' %s ... %d%%' %
- (_("Buffering"), int(disp_number)))
- old_disp_number = disp_number
- except TypeError:
- # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
- # MultiPolygon (not an iterable)
- clearance_geometry.append(
- self.grb_object.solid_geometry.buffer(c_val, int(int(self.geo_steps_per_circle) / 4))
- )
- self.app.proc_container.update_view_text(' %s' % _("Buffering"))
- clearance_geometry = unary_union(clearance_geometry)
- # #########################################################################################
- # Prepare the area to fill with copper. ###################################################
- # #########################################################################################
- log.debug("Copper Filling Tool. Preparing areas to fill with copper.")
- self.app.inform.emit(_("Copper Filling Tool. Preparing areas to fill with copper."))
- try:
- if ref_obj is None or ref_obj == 'itself':
- working_obj = fill_obj
- else:
- working_obj = ref_obj
- except Exception as e:
- log.debug("ToolCopperFIll.on_copper_fill() --> %s" % str(e))
- return 'fail'
- bounding_box = None
- if ref_selected == 'itself':
- geo_n = working_obj.solid_geometry
- try:
- if self.bbox_type_radio.get_value() == 'min':
- if isinstance(geo_n, MultiPolygon):
- env_obj = geo_n.convex_hull
- elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
- (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
- env_obj = cascaded_union(geo_n)
- else:
- env_obj = cascaded_union(geo_n)
- env_obj = env_obj.convex_hull
- else:
- if isinstance(geo_n, Polygon) or \
- (isinstance(geo_n, list) and len(geo_n) == 1) or \
- (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1):
- env_obj = geo_n.buffer(0, join_style=base.JOIN_STYLE.mitre).exterior
- elif isinstance(geo_n, MultiPolygon):
- x0, y0, x1, y1 = geo_n.bounds
- geo = box(x0, y0, x1, y1)
- env_obj = geo.buffer(0, join_style=base.JOIN_STYLE.mitre)
- else:
- self.app.inform.emit(
- '[ERROR_NOTCL] %s: %s' % (_("Geometry not supported for bounding box"), type(geo_n))
- )
- return 'fail'
- bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
- except Exception as e:
- log.debug("ToolCopperFIll.on_copper_fill() 'itself' --> %s" % str(e))
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
- return 'fail'
- elif ref_selected == 'area':
- geo_n = cascaded_union(working_obj)
- try:
- __ = iter(geo_n)
- except Exception as e:
- log.debug("ToolCopperFIll.on_copper_fill() 'area' --> %s" % str(e))
- geo_n = [geo_n]
- geo_buff_list = []
- for poly in geo_n:
- if self.app.abort_flag:
- # graceful abort requested by the user
- raise FlatCAMApp.GracefulException
- geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
- bounding_box = cascaded_union(geo_buff_list)
- elif ref_selected == 'box':
- geo_n = working_obj.solid_geometry
- if isinstance(working_obj, FlatCAMGeometry):
- try:
- __ = iter(geo_n)
- except Exception as e:
- log.debug("ToolCopperFIll.on_copper_fill() 'box' --> %s" % str(e))
- geo_n = [geo_n]
- geo_buff_list = []
- for poly in geo_n:
- if self.app.abort_flag:
- # graceful abort requested by the user
- raise FlatCAMApp.GracefulException
- geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
- bounding_box = cascaded_union(geo_buff_list)
- elif isinstance(working_obj, FlatCAMGerber):
- geo_n = cascaded_union(geo_n).convex_hull
- bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n)
- bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
- else:
- self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
- return 'fail'
- log.debug("Copper Filling Tool. Finished creating areas to fill with copper.")
- self.app.inform.emit(_("Copper Filling Tool. Appending new geometry and buffering."))
- new_solid_geometry = bounding_box.difference(clearance_geometry)
- geo_list = self.grb_object.solid_geometry
- if isinstance(self.grb_object.solid_geometry, MultiPolygon):
- geo_list = list(self.grb_object.solid_geometry.geoms)
- if '0' not in self.grb_object.apertures:
- self.grb_object.apertures['0'] = dict()
- self.grb_object.apertures['0']['geometry'] = list()
- self.grb_object.apertures['0']['type'] = 'REG'
- self.grb_object.apertures['0']['size'] = 0.0
- try:
- for poly in new_solid_geometry:
- # append to the new solid geometry
- geo_list.append(poly)
- # append into the '0' aperture
- geo_elem = dict()
- geo_elem['solid'] = poly
- geo_elem['follow'] = poly.exterior
- self.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
- except TypeError:
- # append to the new solid geometry
- geo_list.append(new_solid_geometry)
- # append into the '0' aperture
- geo_elem = dict()
- geo_elem['solid'] = new_solid_geometry
- geo_elem['follow'] = new_solid_geometry.exterior
- self.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
- self.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
- # update the source file with the new geometry:
- self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'], filename=None,
- local_use=self.grb_object, use_thread=False)
- self.on_exit()
- self.app.inform.emit('[success] %s' % _("Copper Fill Tool done."))
- def replot(self, obj):
- def worker_task():
- with self.app.proc_container.new('%s...' % _("Plotting")):
- obj.plot()
- self.app.worker_task.emit({'fcn': worker_task, 'params': []})
- def on_exit(self):
- # plot the object
- self.replot(obj=self.grb_object)
- # update the bounding box values
- try:
- a, b, c, d = self.grb_object.bounds()
- self.grb_object.options['xmin'] = a
- self.grb_object.options['ymin'] = b
- self.grb_object.options['xmax'] = c
- self.grb_object.options['ymax'] = d
- except Exception as e:
- log.debug("ToolCopperFill.on_exit() bounds error --> %s" % str(e))
- # reset the variables
- self.grb_object = None
- self.ref_obj = None
- self.sel_rect = list()
- # Events ID
- self.mr = None
- self.mm = None
- # Mouse cursor positions
- self.mouse_is_dragging = False
- self.cursor_pos = (0, 0)
- self.first_click = False
- # if True it means we exited from tool in the middle of area adding therefore disconnect the events
- if self.area_method is True:
- self.app.delete_selection_shape()
- self.area_method = False
- if self.app.is_legacy is False:
- self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
- self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
- else:
- self.app.plotcanvas.graph_event_disconnect(self.mr)
- self.app.plotcanvas.graph_event_disconnect(self.mm)
- self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
- self.app.on_mouse_click_over_plot)
- self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
- self.app.on_mouse_move_over_plot)
- self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
- self.app.on_mouse_click_release_over_plot)
- self.app.call_source = "app"
- self.app.inform.emit('[success] %s' % _("Copper Fill Tool exit."))
|