diff --git a/README.md b/README.md index 9e003511..bee864e1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ CAD program, and create G-Code for Isolation routing. - added a Cancel button in Tools DB when requesting to add a tool in the Geometry Tool Table - modified the default values for the METRIC system; the app now starts in the METRIC units since the majority of the world use the METRIC units system - small changes, updated the estimated release date +- Tool Copper Thieving - added pattern plating mask generation feature 28.11.2019 diff --git a/flatcamTools/ToolCopperThieving.py b/flatcamTools/ToolCopperThieving.py index 87d0b9fb..6320d92c 100644 --- a/flatcamTools/ToolCopperThieving.py +++ b/flatcamTools/ToolCopperThieving.py @@ -383,7 +383,56 @@ class ToolCopperThieving(FlatCAMTool): "at a certain distance.\n" "Required when doing holes pattern plating.") ) - self.layout.addWidget(self.rb_button) + grid_lay_1.addWidget(self.rb_button, 4, 0, 1, 2) + + separator_line_2 = QtWidgets.QFrame() + separator_line_2.setFrameShape(QtWidgets.QFrame.HLine) + separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + grid_lay_1.addWidget(separator_line_2, 5, 0, 1, 2) + + self.patern_mask_label = QtWidgets.QLabel('%s' % _('Pattern Plating Mask')) + self.patern_mask_label.setToolTip( + _("Generate a mask for pattern plating.") + ) + grid_lay_1.addWidget(self.patern_mask_label, 6, 0, 1, 2) + + self.sm_obj_label = QtWidgets.QLabel("%s:" % _("Select Soldermask object")) + self.sm_obj_label.setToolTip( + _("Gerber Object with the soldermask.\n" + "It will be used as a base for\n" + "the pattern plating mask.") + ) + + self.sm_object_combo = QtWidgets.QComboBox() + self.sm_object_combo.setModel(self.app.collection) + self.sm_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sm_object_combo.setCurrentIndex(1) + + grid_lay_1.addWidget(self.sm_obj_label, 7, 0, 1, 2) + grid_lay_1.addWidget(self.sm_object_combo, 8, 0, 1, 2) + + # Openings CLEARANCE # + self.clearance_ppm_label = QtWidgets.QLabel('%s:' % _("Clearance")) + self.clearance_ppm_label.setToolTip( + _("The distance between the possible copper thieving elements\n" + "and/or robber bar and the actual openings in the mask.") + ) + self.clearance_ppm_entry = FCDoubleSpinner() + self.clearance_ppm_entry.set_range(-9999.9999, 9999.9999) + self.clearance_ppm_entry.set_precision(self.decimals) + self.clearance_ppm_entry.setSingleStep(0.1) + + grid_lay_1.addWidget(self.clearance_ppm_label, 9, 0) + grid_lay_1.addWidget(self.clearance_ppm_entry, 9, 1) + + # ## Pattern Plating Mask + self.ppm_button = QtWidgets.QPushButton(_("Generate pattern plating mask")) + self.ppm_button.setToolTip( + _("Will add to the soldermask gerber geometry\n" + "the geometries of the copper thieving and/or\n" + "the robber bar if those were generated.") + ) + grid_lay_1.addWidget(self.ppm_button, 10, 0, 1, 2) self.layout.addStretch() @@ -391,6 +440,7 @@ class ToolCopperThieving(FlatCAMTool): self.grb_object = None self.ref_obj = None self.sel_rect = list() + self.sm_object = None # store the flattened geometry here: self.flat_geometry = list() @@ -411,6 +461,15 @@ class ToolCopperThieving(FlatCAMTool): self.margin_val = None self.geo_steps_per_circle = 128 + # Thieving geometry storage + self.new_solid_geometry = list() + + # Robber bar geometry storage + self.robber_geo = None + self.robber_line = None + + self.rb_thickness = None + # SIGNALS self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type) self.reference_radio.group_toggle_fn = self.on_toggle_reference @@ -418,6 +477,7 @@ class ToolCopperThieving(FlatCAMTool): self.fill_button.clicked.connect(self.execute) self.rb_button.clicked.connect(self.add_robber_bar) + self.ppm_button.clicked.connect(self.on_add_ppm) def run(self, toggle=True): self.app.report_usage("ToolCopperThieving()") @@ -471,6 +531,8 @@ class ToolCopperThieving(FlatCAMTool): # INIT SECTION self.area_method = False + self.robber_geo = None + self.new_solid_geometry = None def on_combo_box_type(self): obj_type = self.box_combo_type.currentIndex() @@ -527,7 +589,7 @@ class ToolCopperThieving(FlatCAMTool): def add_robber_bar(self): rb_margin = self.rb_margin_entry.get_value() - rb_thickness = self.rb_thickness_entry.get_value() + self.rb_thickness = self.rb_thickness_entry.get_value() # get the Gerber object on which the Robber bar will be inserted selection_index = self.grb_object_combo.currentIndex() @@ -545,23 +607,23 @@ class ToolCopperThieving(FlatCAMTool): except TypeError: outline_pol = MultiPolygon(self.grb_object.solid_geometry).envelope - rb_distance = rb_margin + (rb_thickness / 2.0) - robber_line = outline_pol.buffer(rb_distance).exterior + rb_distance = rb_margin + (self.rb_thickness / 2.0) + self.robber_line = outline_pol.buffer(rb_distance).exterior - robber_geo = robber_line.buffer(rb_thickness / 2.0) + self.robber_geo = self.robber_line.buffer(self.rb_thickness / 2.0) self.app.proc_container.update_view_text(' %s' % _("Append geometry")) aperture_found = None for ap_id, ap_val in self.grb_object.apertures.items(): - if ap_val['type'] == 'C' and ap_val['size'] == rb_thickness: + if ap_val['type'] == 'C' and ap_val['size'] == self.rb_thickness: aperture_found = ap_id break if aperture_found: geo_elem = dict() - geo_elem['solid'] = robber_geo - geo_elem['follow'] = robber_line + geo_elem['solid'] = self.robber_geo + geo_elem['follow'] = self.robber_line self.grb_object.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem)) else: ap_keys = list(self.grb_object.apertures.keys()) @@ -572,12 +634,12 @@ class ToolCopperThieving(FlatCAMTool): self.grb_object.apertures[new_apid] = dict() self.grb_object.apertures[new_apid]['type'] = 'C' - self.grb_object.apertures[new_apid]['size'] = rb_thickness + self.grb_object.apertures[new_apid]['size'] = self.rb_thickness self.grb_object.apertures[new_apid]['geometry'] = list() geo_elem = dict() - geo_elem['solid'] = robber_geo - geo_elem['follow'] = robber_line + geo_elem['solid'] = self.robber_geo + geo_elem['follow'] = self.robber_line self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem)) geo_obj = self.grb_object.solid_geometry @@ -585,12 +647,12 @@ class ToolCopperThieving(FlatCAMTool): s_list = list() for pol in geo_obj.geoms: s_list.append(pol) - s_list.append(robber_geo) + s_list.append(self.robber_geo) geo_obj = MultiPolygon(s_list) elif isinstance(geo_obj, list): - geo_obj.append(robber_geo) + geo_obj.append(self.robber_geo) elif isinstance(geo_obj, Polygon): - geo_obj = MultiPolygon([geo_obj, robber_geo]) + geo_obj = MultiPolygon([geo_obj, self.robber_geo]) self.grb_object.solid_geometry = geo_obj @@ -984,7 +1046,7 @@ class ToolCopperThieving(FlatCAMTool): # ########## Generate filling geometry. ################################################### # ######################################################################################### - new_solid_geometry = bounding_box.difference(clearance_geometry) + app_obj.new_solid_geometry = bounding_box.difference(clearance_geometry) # determine the bounding box polygon for the entire Gerber object to which we add copper thieving # if isinstance(geo_n, list): @@ -1037,9 +1099,9 @@ class ToolCopperThieving(FlatCAMTool): thieving_box_geo = affinity.translate(thieving_box_geo, xoff=dx, yoff=dy) try: - _it = iter(new_solid_geometry) + _it = iter(app_obj.new_solid_geometry) except TypeError: - new_solid_geometry = [new_solid_geometry] + app_obj.new_solid_geometry = [app_obj.new_solid_geometry] try: _it = iter(thieving_box_geo) @@ -1048,11 +1110,11 @@ class ToolCopperThieving(FlatCAMTool): thieving_geo = list() for dot_geo in thieving_box_geo: - for geo_t in new_solid_geometry: + for geo_t in app_obj.new_solid_geometry: if dot_geo.within(geo_t): thieving_geo.append(dot_geo) - new_solid_geometry = thieving_geo + app_obj.new_solid_geometry = thieving_geo if fill_type == 'line': half_thick_line = line_size / 2.0 @@ -1140,7 +1202,7 @@ class ToolCopperThieving(FlatCAMTool): rest_line = line_poly.difference(clearance_geometry) diff_lines_geo.append(rest_line) app_obj.flatten([outline_geometry, box_outline_geometry, diff_lines_geo]) - new_solid_geometry = app_obj.flat_geometry + app_obj.new_solid_geometry = app_obj.flat_geometry app_obj.app.proc_container.update_view_text(' %s' % _("Append geometry")) geo_list = app_obj.grb_object.solid_geometry @@ -1154,7 +1216,7 @@ class ToolCopperThieving(FlatCAMTool): app_obj.grb_object.apertures['0']['size'] = 0.0 try: - for poly in new_solid_geometry: + for poly in app_obj.new_solid_geometry: # append to the new solid geometry geo_list.append(poly) @@ -1165,12 +1227,12 @@ class ToolCopperThieving(FlatCAMTool): app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem)) except TypeError: # append to the new solid geometry - geo_list.append(new_solid_geometry) + geo_list.append(app_obj.new_solid_geometry) # append into the '0' aperture geo_elem = dict() - geo_elem['solid'] = new_solid_geometry - geo_elem['follow'] = new_solid_geometry.exterior + geo_elem['solid'] = app_obj.new_solid_geometry + geo_elem['follow'] = app_obj.new_solid_geometry.exterior app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem)) app_obj.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001) @@ -1190,6 +1252,116 @@ class ToolCopperThieving(FlatCAMTool): else: job_thread_thieving(self) + def on_add_ppm(self): + run_threaded = True + + if run_threaded: + proc = self.app.proc_container.new('%s ...' % _("P-Plating Mask")) + else: + QtWidgets.QApplication.processEvents() + + self.app.proc_container.view.set_busy('%s ...' % _("P-Plating Mask")) + + # get the Gerber object on which the Copper thieving will be inserted + selection_index = self.sm_object_combo.currentIndex() + model_index = self.app.collection.index(selection_index, 0, self.sm_object_combo.rootModelIndex()) + + try: + self.sm_object = model_index.internalPointer().obj + except Exception as e: + log.debug("ToolCopperThieving.on_add_ppm() --> %s" % str(e)) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) + return 'fail' + + ppm_clearance = self.clearance_ppm_entry.get_value() + + def job_thread_ppm(app_obj): + + app_obj.app.proc_container.update_view_text(' %s' % _("Append PP-M geometry")) + geo_list = app_obj.sm_object.solid_geometry + if isinstance(app_obj.sm_object.solid_geometry, MultiPolygon): + geo_list = list(app_obj.sm_object.solid_geometry.geoms) + + # if we have copper thieving geometry, add it + if app_obj.new_solid_geometry: + if '0' not in app_obj.sm_object.apertures: + app_obj.sm_object.apertures['0'] = dict() + app_obj.sm_object.apertures['0']['geometry'] = list() + app_obj.sm_object.apertures['0']['type'] = 'REG' + app_obj.sm_object.apertures['0']['size'] = 0.0 + + try: + for poly in app_obj.new_solid_geometry: + + poly_b = poly.buffer(ppm_clearance) + + # append to the new solid geometry + geo_list.append(poly_b) + + # append into the '0' aperture + geo_elem = dict() + geo_elem['solid'] = poly_b + geo_elem['follow'] = poly_b.exterior + app_obj.sm_object.apertures['0']['geometry'].append(deepcopy(geo_elem)) + except TypeError: + # append to the new solid geometry + geo_list.append(app_obj.new_solid_geometry.buffer(ppm_clearance)) + + # append into the '0' aperture + geo_elem = dict() + geo_elem['solid'] = app_obj.new_solid_geometry.buffer(ppm_clearance) + geo_elem['follow'] = app_obj.new_solid_geometry.buffer(ppm_clearance).exterior + app_obj.sm_object.apertures['0']['geometry'].append(deepcopy(geo_elem)) + + # if we have robber bar geometry, add it + if app_obj.robber_geo: + aperture_found = None + for ap_id, ap_val in app_obj.sm_object.apertures.items(): + if ap_val['type'] == 'C' and ap_val['size'] == app_obj.rb_thickness + ppm_clearance: + aperture_found = ap_id + break + + if aperture_found: + geo_elem = dict() + geo_elem['solid'] = app_obj.robber_geo + geo_elem['follow'] = app_obj.robber_line + app_obj.sm_object.apertures[aperture_found]['geometry'].append(deepcopy(geo_elem)) + else: + ap_keys = list(app_obj.sm_object.apertures.keys()) + if ap_keys: + new_apid = str(int(max(ap_keys)) + 1) + else: + new_apid = '10' + + app_obj.sm_object.apertures[new_apid] = dict() + app_obj.sm_object.apertures[new_apid]['type'] = 'C' + app_obj.sm_object.apertures[new_apid]['size'] = app_obj.rb_thickness + ppm_clearance + app_obj.sm_object.apertures[new_apid]['geometry'] = list() + + geo_elem = dict() + geo_elem['solid'] = app_obj.robber_geo.buffer(ppm_clearance) + geo_elem['follow'] = Polygon(app_obj.robber_line).buffer(ppm_clearance / 2.0).exterior + app_obj.sm_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem)) + + geo_list.append(app_obj.robber_geo.buffer(ppm_clearance)) + + app_obj.sm_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001) + + app_obj.app.proc_container.update_view_text(' %s' % _("Append source file")) + # update the source file with the new geometry: + app_obj.sm_object.source_file = app_obj.app.export_gerber(obj_name=app_obj.sm_object.options['name'], + filename=None, + local_use=app_obj.sm_object, + use_thread=False) + app_obj.app.proc_container.update_view_text(' %s' % '') + app_obj.on_exit() + app_obj.app.inform.emit('[success] %s' % _("Generating Pattern Plating Mask done.")) + + if run_threaded: + self.app.worker_task.emit({'fcn': job_thread_ppm, 'params': [self]}) + else: + job_thread_ppm(self) + def replot(self, obj): def worker_task(): with self.app.proc_container.new('%s...' % _("Plotting")): @@ -1198,8 +1370,12 @@ class ToolCopperThieving(FlatCAMTool): self.app.worker_task.emit({'fcn': worker_task, 'params': []}) def on_exit(self): - # plot the object - self.replot(obj=self.grb_object) + # plot the objects + if self.grb_object: + self.replot(obj=self.grb_object) + + if self.sm_object: + self.replot(obj=self.sm_object) # update the bounding box values try: @@ -1209,10 +1385,21 @@ class ToolCopperThieving(FlatCAMTool): self.grb_object.options['xmax'] = c self.grb_object.options['ymax'] = d except Exception as e: - log.debug("ToolCopperThieving.on_exit() bounds error --> %s" % str(e)) + log.debug("ToolCopperThieving.on_exit() bounds -> copper thieving Gerber error --> %s" % str(e)) + + # update the bounding box values + try: + a, b, c, d = self.sm_object.bounds() + self.sm_object.options['xmin'] = a + self.sm_object.options['ymin'] = b + self.sm_object.options['xmax'] = c + self.sm_object.options['ymax'] = d + except Exception as e: + log.debug("ToolCopperThieving.on_exit() bounds -> pattern plating mask error --> %s" % str(e)) # reset the variables self.grb_object = None + self.sm_object = None self.ref_obj = None self.sel_rect = list()