From 0b50734578277ff26385418abd805b6022fbf12a Mon Sep 17 00:00:00 2001 From: Marius S Date: Tue, 17 Dec 2019 17:37:18 +0200 Subject: [PATCH 01/11] - fixed issue #347 - a Gerber generated by Sprint Layout with copper pour ON will not have rendered the copper pour --- README.md | 1 + flatcamParsers/ParseGerber.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04c46dd4..4521336d 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ CAD program, and create G-Code for Isolation routing. - added ability to save the Source File as PDF - fixed page size and added line breaks - more mods to generate_from_geometry_2() method - fixed bug saving the FlatCAM project saying the file is used by another application +- fixed issue #347 - a Gerber generated by Sprint Layout with copper pour ON will not have rendered the copper pour 16.12.2019 diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index 3b75d2d7..1615f000 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -465,11 +465,12 @@ class Gerber(Geometry): geo_dict['follow'] = geo_f geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - if not geo_s.is_empty: + if not geo_s.is_empty and geo_s.is_valid: if self.app.defaults['gerber_simplification']: poly_buffer.append(geo_s.simplify(s_tol)) else: poly_buffer.append(geo_s) + if self.is_lpc is True: geo_dict['clear'] = geo_s else: @@ -1411,7 +1412,14 @@ class Gerber(Geometry): if current_polarity == 'D': self.app.inform.emit('%s' % _("Gerber processing. Applying Gerber polarity.")) if new_poly.is_valid: - self.solid_geometry = self.solid_geometry.union(new_poly) + # self.solid_geometry = self.solid_geometry.union(new_poly) + # FIX for issue #347 - Sprint Layout generate strange Gerber files when the copper pour is enabled + # it use a filled bounding box polygon to which add clear polygons (negative) to isolate the copper + # features + candidate_geo = list() + for p in self.solid_geometry.union(new_poly): + candidate_geo.append(p.buffer(-0.0000001)) + self.solid_geometry = candidate_geo else: # I do this so whenever the parsed geometry of the file is not valid (intersections) it is still # loaded. Instead of applying a union I add to a list of polygons. From e745f3f83640dcc8c6ba08af20a0c453325f695f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Dec 2019 03:14:17 +0200 Subject: [PATCH 02/11] - added new parameters to improve Gerber parsing - small optimizations in the Preferences UI --- FlatCAMApp.py | 5 ++ FlatCAMObj.py | 3 +- README.md | 5 ++ flatcamEditors/FlatCAMGrbEditor.py | 26 ++++++----- flatcamGUI/PreferencesUI.py | 73 ++++++++++++++++++++++++------ flatcamParsers/ParseGerber.py | 26 +++++++---- 6 files changed, 101 insertions(+), 37 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 22e9bdac..0804b646 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -511,6 +511,9 @@ class App(QtCore.QObject): "gerber_multicolored": False, "gerber_circle_steps": 64, "gerber_use_buffer_for_union": True, + "gerber_clean_apertures": True, + "gerber_extra_buffering": True, + "gerber_def_units": 'IN', "gerber_def_zeros": 'L', "gerber_save_filters": "Gerber File (*.gbr);;Gerber File (*.bot);;Gerber File (*.bsm);;" @@ -1121,6 +1124,8 @@ class App(QtCore.QObject): "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry, "gerber_def_units": self.ui.gerber_defaults_form.gerber_gen_group.gerber_units_radio, "gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio, + "gerber_clean_apertures": self.ui.gerber_defaults_form.gerber_gen_group.gerber_clean_cb, + "gerber_extra_buffering": self.ui.gerber_defaults_form.gerber_gen_group.gerber_extra_buffering, # Gerber Options "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry, diff --git a/FlatCAMObj.py b/FlatCAMObj.py index b658b63b..671678e4 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -5173,8 +5173,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if self.tools[tooluid_key]['solid_geometry'] is None: a += 1 if a == len(self.tools): - self.app.inform.emit('[ERROR_NOTCL] %s...' % - _('Cancelled. Empty file, it has no geometry')) + self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry')) return 'fail' for tooluid_key in list(tools_dict.keys()): diff --git a/README.md b/README.md index 4521336d..1f8a04b3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +18.12.2019 + +- added new parameters to improve Gerber parsing +- small optimizations in the Preferences UI + 17.12.2019 - more optimizations in NCC Tool diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 5233314c..1779e8c5 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -4594,19 +4594,21 @@ class FlatCAMGrbEditor(QtCore.QObject): self.shapes.clear(update=True) for storage in self.storage_dict: - for elem in self.storage_dict[storage]['geometry']: - if 'solid' in elem.geo: - geometric_data = elem.geo['solid'] - if geometric_data is None: - continue + # fix for apertures with now geometry inside + if 'geometry' in self.storage_dict[storage]: + for elem in self.storage_dict[storage]['geometry']: + if 'solid' in elem.geo: + geometric_data = elem.geo['solid'] + if geometric_data is None: + continue - if elem in self.selected: - self.plot_shape(geometry=geometric_data, - color=self.app.defaults['global_sel_draw_color'] + 'FF', - linewidth=2) - else: - self.plot_shape(geometry=geometric_data, - color=self.app.defaults['global_draw_color'] + 'FF') + if elem in self.selected: + self.plot_shape(geometry=geometric_data, + color=self.app.defaults['global_sel_draw_color'] + 'FF', + linewidth=2) + else: + self.plot_shape(geometry=geometric_data, + color=self.app.defaults['global_draw_color'] + 'FF') if self.utility: for elem in self.utility: diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index cb033589..6aaeabc3 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -1435,6 +1435,29 @@ class GerberGenPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.gerber_zeros_label, 5, 0) grid0.addWidget(self.gerber_zeros_radio, 5, 1, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 6, 0, 1, 3) + + # Apertures Cleaning + self.gerber_clean_cb = FCCheckBox(label='%s' % _('Clean Apertures')) + self.gerber_clean_cb.setToolTip( + _("Will remove apertures that do not have geometry\n" + "thus lowering the number of apertures in the Gerber object.") + ) + grid0.addWidget(self.gerber_clean_cb, 7, 0, 1, 3) + + # Apply Extra Buffering + self.gerber_extra_buffering = FCCheckBox(label='%s' % _('Polarity change buffer')) + self.gerber_extra_buffering.setToolTip( + _("Will apply extra buffering for the\n" + "solid geometry when we have polarity changes.\n" + "May help loading Gerber files that otherwise\n" + "do not load correctly.") + ) + grid0.addWidget(self.gerber_extra_buffering, 8, 0, 1, 3) + self.layout.addStretch() @@ -1528,6 +1551,11 @@ class GerberOptPrefGroupUI(OptionsGroupUI): ) grid0.addWidget(self.combine_passes_cb, 5, 0, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 6, 0, 1, 2) + # ## Clear non-copper regions self.clearcopper_label = QtWidgets.QLabel("%s:" % _("Non-copper regions")) self.clearcopper_label.setToolTip( @@ -1564,6 +1592,11 @@ class GerberOptPrefGroupUI(OptionsGroupUI): ) grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid1.addWidget(separator_line, 2, 0, 1, 2) + # ## Bounding box self.boundingbox_label = QtWidgets.QLabel('%s:' % _('Bounding Box')) self.layout.addWidget(self.boundingbox_label) @@ -1634,6 +1667,11 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): ) grid0.addWidget(self.aperture_table_visibility_cb, 1, 0, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 2, 0, 1, 2) + # Tool Type self.tool_type_label = QtWidgets.QLabel('%s' % _('Tool Type')) self.tool_type_label.setToolTip( @@ -1645,8 +1683,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'}, {'label': 'V-Shape', 'value': 'v'}]) - grid0.addWidget(self.tool_type_label, 2, 0) - grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2) + grid0.addWidget(self.tool_type_label, 3, 0) + grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2) # Tip Dia self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia')) @@ -1658,8 +1696,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.tipdia_spinner.set_range(-99.9999, 99.9999) self.tipdia_spinner.setSingleStep(0.1) self.tipdia_spinner.setWrapping(True) - grid0.addWidget(self.tipdialabel, 3, 0) - grid0.addWidget(self.tipdia_spinner, 3, 1, 1, 2) + grid0.addWidget(self.tipdialabel, 4, 0) + grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2) # Tip Angle self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle')) @@ -1671,8 +1709,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.tipangle_spinner.set_range(0, 180) self.tipangle_spinner.setSingleStep(5) self.tipangle_spinner.setWrapping(True) - grid0.addWidget(self.tipanglelabel, 4, 0) - grid0.addWidget(self.tipangle_spinner, 4, 1, 1, 2) + grid0.addWidget(self.tipanglelabel, 5, 0) + grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2) # Cut Z self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z')) @@ -1686,8 +1724,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.cutz_spinner.setSingleStep(0.1) self.cutz_spinner.setWrapping(True) - grid0.addWidget(self.cutzlabel, 5, 0) - grid0.addWidget(self.cutz_spinner, 5, 1, 1, 2) + grid0.addWidget(self.cutzlabel, 6, 0) + grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2) # Isolation Type self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type')) @@ -1705,8 +1743,13 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): {'label': _('Exterior'), 'value': 'ext'}, {'label': _('Interior'), 'value': 'int'}]) - grid0.addWidget(self.iso_type_label, 6, 0,) - grid0.addWidget(self.iso_type_radio, 6, 1, 1, 2) + grid0.addWidget(self.iso_type_label, 7, 0,) + grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 8, 0, 1, 2) # Buffering Type buffering_label = QtWidgets.QLabel('%s:' % _('Buffering')) @@ -1718,8 +1761,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): ) self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'}, {'label': _('Full'), 'value': 'full'}]) - grid0.addWidget(buffering_label, 7, 0) - grid0.addWidget(self.buffering_radio, 7, 1) + grid0.addWidget(buffering_label, 9, 0) + grid0.addWidget(self.buffering_radio, 9, 1) # Simplification self.simplify_cb = FCCheckBox(label=_('Simplify')) @@ -1728,7 +1771,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): "loaded with simplification having a set tolerance.\n" "<>: Don't change this unless you know what you are doing !!!") ) - grid0.addWidget(self.simplify_cb, 8, 0, 1, 2) + grid0.addWidget(self.simplify_cb, 10, 0, 1, 2) # Simplification tolerance self.simplification_tol_label = QtWidgets.QLabel(_('Tolerance')) @@ -1740,8 +1783,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.simplification_tol_spinner.setRange(0.00000, 0.01000) self.simplification_tol_spinner.setSingleStep(0.0001) - grid0.addWidget(self.simplification_tol_label, 9, 0) - grid0.addWidget(self.simplification_tol_spinner, 9, 1) + grid0.addWidget(self.simplification_tol_label, 11, 0) + grid0.addWidget(self.simplification_tol_spinner, 11, 1) self.ois_simplif = OptionalInputSection( self.simplify_cb, [ diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index 1615f000..f8829c5c 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -72,6 +72,8 @@ class Gerber(Geometry): # "use_buffer_for_union": True # } + app = None + def __init__(self, steps_per_circle=None): """ The constructor takes no parameters. Use ``gerber.parse_files()`` @@ -1412,14 +1414,7 @@ class Gerber(Geometry): if current_polarity == 'D': self.app.inform.emit('%s' % _("Gerber processing. Applying Gerber polarity.")) if new_poly.is_valid: - # self.solid_geometry = self.solid_geometry.union(new_poly) - # FIX for issue #347 - Sprint Layout generate strange Gerber files when the copper pour is enabled - # it use a filled bounding box polygon to which add clear polygons (negative) to isolate the copper - # features - candidate_geo = list() - for p in self.solid_geometry.union(new_poly): - candidate_geo.append(p.buffer(-0.0000001)) - self.solid_geometry = candidate_geo + self.solid_geometry = self.solid_geometry.union(new_poly) else: # I do this so whenever the parsed geometry of the file is not valid (intersections) it is still # loaded. Instead of applying a union I add to a list of polygons. @@ -1438,6 +1433,15 @@ class Gerber(Geometry): self.solid_geometry = final_poly + # FIX for issue #347 - Sprint Layout generate strange Gerber files when the copper pour is enabled + # it use a filled bounding box polygon to which add clear polygons (negative) to isolate the copper + # features + if self.app.defaults['gerber_extra_buffering']: + candidate_geo = list() + for p in self.solid_geometry: + candidate_geo.append(p.buffer(0.0000001)) + self.solid_geometry = candidate_geo + # try: # self.solid_geometry = self.solid_geometry.union(new_poly) # except Exception as e: @@ -1450,6 +1454,12 @@ class Gerber(Geometry): else: self.solid_geometry = self.solid_geometry.difference(new_poly) + if self.app.defaults['gerber_clean_apertures']: + # clean the Gerber file of apertures with no geometry + for apid, apvalue in list(self.apertures.items()): + if 'geometry' not in apvalue: + self.apertures.pop(apid) + # init this for the following operations self.conversion_done = False except Exception as err: From 1f33220bab3654ce78e189858193358343bb728f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Dec 2019 04:01:25 +0200 Subject: [PATCH 03/11] - the Jump To function reference is now saving it's last used value - added the ability to use the Jump To method in the Gerber Editor --- FlatCAMApp.py | 9 +++-- README.md | 2 ++ flatcamEditors/FlatCAMGeoEditor.py | 1 - flatcamEditors/FlatCAMGrbEditor.py | 54 ++++++++++++++++++++++++------ flatcamGUI/GUIElements.py | 16 ++++----- 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 0804b646..b363fbce 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -415,6 +415,9 @@ class App(QtCore.QObject): "global_serial": 0, "global_stats": dict(), "global_tabs_detachable": True, + "global_jump_ref": 'abs', + + # General "global_graphic_engine": '3D', "global_app_level": 'b', "global_portable": False, @@ -1678,6 +1681,7 @@ class App(QtCore.QObject): self.mr = None self.mdc = None self.mp_zc = None + self.kp = None # Matplotlib axis self.axes = None @@ -7384,7 +7388,8 @@ class App(QtCore.QObject): dia_box = DialogBoxRadio(title=_("Jump to ..."), label=_("Enter the coordinates in format X,Y:"), icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'), - initial_text=dia_box_location) + initial_text=dia_box_location, + reference=self.defaults['global_jump_ref']) if dia_box.ok is True: try: @@ -7398,7 +7403,7 @@ class App(QtCore.QObject): rel_x = self.mouse[0] + location[0] rel_y = self.mouse[1] + location[1] location = (rel_x, rel_y) - + self.defaults['global_jump_ref'] = dia_box.reference except Exception: return else: diff --git a/README.md b/README.md index 1f8a04b3..d809b995 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing. - added new parameters to improve Gerber parsing - small optimizations in the Preferences UI +- the Jump To function reference is now saving it's last used value +- added the ability to use the Jump To method in the Gerber Editor 17.12.2019 diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index aad21d96..d70828f8 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -1880,7 +1880,6 @@ class DrawTool(object): return "" def on_key(self, key): - # Jump to coords if key == QtCore.Qt.Key_J or key == 'J': self.draw_app.app.on_jump_to() diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 1779e8c5..d05ae8fe 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -139,7 +139,9 @@ class DrawTool(object): return "" def on_key(self, key): - return None + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.draw_app.app.on_jump_to() def utility_geometry(self, data=None): return None @@ -874,9 +876,11 @@ class FCRegion(FCShapeTool): except Exception as e: log.debug("FlatCAMGrbEditor.FCRegion --> %s" % str(e)) - self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero.png')) + self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + self.draw_app.app.inform.emit(_('Corner Mode 1: 45 degrees ...')) self.start_msg = _("Click on 1st point ...") @@ -1064,8 +1068,10 @@ class FCRegion(FCShapeTool): self.geometry = DrawToolShape(new_geo_el) self.draw_app.in_action = False self.complete = True - self.draw_app.app.inform.emit('[success] %s' % - _("Done.")) + + self.draw_app.app.jump_signal.disconnect() + + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def clean_up(self): self.draw_app.selected = [] @@ -1073,6 +1079,10 @@ class FCRegion(FCShapeTool): self.draw_app.plot_all() def on_key(self, key): + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + if key == 'Backspace' or key == QtCore.Qt.Key_Backspace: if len(self.points) > 0: if self.draw_app.bend_mode == 5: @@ -1148,9 +1158,12 @@ class FCTrack(FCRegion): except Exception as e: log.debug("FlatCAMGrbEditor.FCTrack.__init__() --> %s" % str(e)) - self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_path%s.png' % self.draw_app.bend_mode)) + self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + + '/aero_path%s.png' % self.draw_app.bend_mode)) QtGui.QGuiApplication.setOverrideCursor(self.cursor) + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...')) def make(self): @@ -1168,8 +1181,10 @@ class FCTrack(FCRegion): self.draw_app.in_action = False self.complete = True - self.draw_app.app.inform.emit('[success] %s' % - _("Done.")) + + self.draw_app.app.jump_signal.disconnect() + + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def clean_up(self): self.draw_app.selected = [] @@ -1287,6 +1302,10 @@ class FCTrack(FCRegion): self.draw_app.draw_utility_geometry(geo=geo) return _("Backtracked one point ...") + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + if key == 'T' or key == QtCore.Qt.Key_T: try: QtGui.QGuiApplication.restoreOverrideCursor() @@ -1396,6 +1415,8 @@ class FCDisc(FCShapeTool): self.draw_app.app.inform.emit(_("Click on Center point ...")) + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + self.steps_per_circ = self.draw_app.app.defaults["gerber_circle_steps"] def click(self, point): @@ -1442,8 +1463,10 @@ class FCDisc(FCShapeTool): self.draw_app.in_action = False self.complete = True - self.draw_app.app.inform.emit('[success] %s' % - _("Done.")) + + self.draw_app.app.jump_signal.disconnect() + + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def clean_up(self): self.draw_app.selected = [] @@ -1490,6 +1513,7 @@ class FCSemiDisc(FCShapeTool): self.storage_obj = self.draw_app.storage_dict['0']['geometry'] self.steps_per_circ = self.draw_app.app.defaults["gerber_circle_steps"] + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) def click(self, point): self.points.append(point) @@ -1523,6 +1547,10 @@ class FCSemiDisc(FCShapeTool): self.direction = 'cw' if self.direction == 'ccw' else 'ccw' return '%s: %s' % (_('Direction'), self.direction.upper()) + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + if key == 'M' or key == QtCore.Qt.Key_M: # delete the possible points made before this action; we want to start anew self.points = [] @@ -1700,8 +1728,10 @@ class FCSemiDisc(FCShapeTool): self.draw_app.in_action = False self.complete = True - self.draw_app.app.inform.emit('[success] %s' % - _("Done.")) + + self.draw_app.app.jump_signal.disconnect() + + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def clean_up(self): self.draw_app.selected = [] @@ -4517,6 +4547,8 @@ class FlatCAMGrbEditor(QtCore.QObject): self.snap_x = x self.snap_y = y + self.app.mouse = [x, y] + # update the position label in the infobar since the APP mouse event handlers are disconnected self.app.ui.position_label.setText("    X: %.4f   " "Y: %.4f" % (x, y)) diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index 96fa793d..f0d7df81 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -2239,7 +2239,7 @@ class Dialog_box(QtWidgets.QWidget): class DialogBoxRadio(QtWidgets.QDialog): - def __init__(self, title=None, label=None, icon=None, initial_text=None): + def __init__(self, title=None, label=None, icon=None, initial_text=None, reference='abs'): """ :param title: string with the window title @@ -2258,11 +2258,6 @@ class DialogBoxRadio(QtWidgets.QDialog): self.form = QtWidgets.QFormLayout(self) - self.form.addRow(QtWidgets.QLabel('')) - - self.wdg_label = QtWidgets.QLabel('%s' % str(label)) - self.form.addRow(self.wdg_label) - self.ref_label = QtWidgets.QLabel('%s:' % _("Reference")) self.ref_label.setToolTip( _("The reference can be:\n" @@ -2273,10 +2268,15 @@ class DialogBoxRadio(QtWidgets.QDialog): {"label": _("Abs"), "value": "abs"}, {"label": _("Relative"), "value": "rel"} ], orientation='horizontal', stretch=False) - self.ref_radio.set_value('abs') + self.ref_radio.set_value(reference) self.form.addRow(self.ref_label, self.ref_radio) - self.loc_label = QtWidgets.QLabel('%s:' % _("Location")) + self.form.addRow(QtWidgets.QLabel('')) + + self.wdg_label = QtWidgets.QLabel('%s' % str(label)) + self.form.addRow(self.wdg_label) + + self.loc_label = QtWidgets.QLabel('%s:' % _("Location")) self.loc_label.setToolTip( _("The Location value is a tuple (x,y).\n" "If the reference is Absolute then the Jump will be at the position (x,y).\n" From a8d4c592c2eec95c1029f65ec474b33a56b10735 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Dec 2019 04:26:20 +0200 Subject: [PATCH 04/11] - improved the loading of Config File by using the advanced code editor --- FlatCAMApp.py | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index b363fbce..5b460a7f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -9940,7 +9940,7 @@ class App(QtCore.QObject): def init_code_editor(self, name): - self.text_editor_tab = TextEditor(app=self) + self.text_editor_tab = TextEditor(app=self, plain_text=True) # add the tab if it was closed self.ui.plot_tab_area.addTab(self.text_editor_tab, '%s' % name) diff --git a/README.md b/README.md index d809b995..7848c4f0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing. - small optimizations in the Preferences UI - the Jump To function reference is now saving it's last used value - added the ability to use the Jump To method in the Gerber Editor +- improved the loading of Config File by using the advanced code editor 17.12.2019 From 4efc453b841990fca0b393bf40d86fa6316e1db8 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Dec 2019 17:53:41 +0200 Subject: [PATCH 05/11] - fixed a bug in the new feature 'extra buffering' - fixed the creation of CNCJob objects out of multigeo Geometry objects (objects with multiple tools) - optimized the NCC Tool --- FlatCAM.py | 8 +++++++ FlatCAMObj.py | 4 +--- README.md | 3 +++ flatcamParsers/ParseGerber.py | 7 ++++-- flatcamTools/ToolNonCopperClear.py | 37 +++++++++++++++++++----------- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/FlatCAM.py b/FlatCAM.py index 191d5fbe..02c56c9d 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -47,6 +47,14 @@ if __name__ == '__main__': else: os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0" + # if hdpi_support == 2: + # tst_screen = QtWidgets.QApplication(sys.argv) + # if tst_screen.screens()[0].geometry().width() > 1930 or tst_screen.screens()[1].geometry().width() > 1930: + # QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + # del tst_screen + # else: + # QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling, False) + app = QtWidgets.QApplication(sys.argv) # apply style diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 671678e4..2574232b 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -5291,10 +5291,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if use_thread: # To be run in separate thread - # The idea is that if there is a solid_geometry in the file "root" then most likely thare are no - # separate solid_geometry in the self.tools dictionary def job_thread(app_obj): - if self.solid_geometry: + if self.multigeo is False: with self.app.proc_container.new(_("Generating CNC Code")): if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail': app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) diff --git a/README.md b/README.md index 7848c4f0..277cbfdf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ CAD program, and create G-Code for Isolation routing. - the Jump To function reference is now saving it's last used value - added the ability to use the Jump To method in the Gerber Editor - improved the loading of Config File by using the advanced code editor +- fixed a bug in the new feature 'extra buffering' +- fixed the creation of CNCJob objects out of multigeo Geometry objects (objects with multiple tools) +- optimized the NCC Tool 17.12.2019 diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index f8829c5c..b9bc504d 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -1438,8 +1438,11 @@ class Gerber(Geometry): # features if self.app.defaults['gerber_extra_buffering']: candidate_geo = list() - for p in self.solid_geometry: - candidate_geo.append(p.buffer(0.0000001)) + try: + for p in self.solid_geometry: + candidate_geo.append(p.buffer(0.0000001)) + except TypeError: + candidate_geo.append(self.solid_geometry.buffer(0.0000001)) self.solid_geometry = candidate_geo # try: diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index a5f874b4..2579169d 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -1723,11 +1723,9 @@ class NonCopperClear(FlatCAMTool, Gerber): sol_geo = cascaded_union(isolated_geo) if has_offset is True: - app_obj.inform.emit('[WARNING_NOTCL] %s ...' % - _("Buffering")) + app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering")) sol_geo = sol_geo.buffer(distance=ncc_offset) - app_obj.inform.emit('[success] %s ...' % - _("Buffering finished")) + app_obj.inform.emit('[success] %s ...' % _("Buffering finished")) empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box) if empty == 'fail': return 'fail' @@ -1760,6 +1758,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("NCC Tool. Finished calculation of 'empty' area.") self.app.inform.emit(_("NCC Tool. Finished calculation of 'empty' area.")) + # COPPER CLEARING # cp = None for tool in sorted_tools: log.debug("Starting geometry processing for tool: %s" % str(tool)) @@ -1916,17 +1915,27 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.defaults["tools_ncc_plotting"] == 'progressive': self.temp_shapes.clear(update=True) - # delete tools with empty geometry - keys_to_delete = [] - # look for keys in the tools_storage dict that have 'solid_geometry' values empty - for uid in tools_storage: - # if the solid_geometry (type=list) is empty - if not tools_storage[uid]['solid_geometry']: - keys_to_delete.append(uid) + # # delete tools with empty geometry + # keys_to_delete = [] + # # look for keys in the tools_storage dict that have 'solid_geometry' values empty + # for uid in tools_storage: + # # if the solid_geometry (type=list) is empty + # if not tools_storage[uid]['solid_geometry']: + # keys_to_delete.append(uid) + # + # # actual delete of keys from the tools_storage dict + # for k in keys_to_delete: + # tools_storage.pop(k, None) - # actual delete of keys from the tools_storage dict - for k in keys_to_delete: - tools_storage.pop(k, None) + # delete tools with empty geometry + # look for keys in the tools_storage dict that have 'solid_geometry' values empty + for uid, uid_val in list(tools_storage.items()): + try: + # if the solid_geometry (type=list) is empty + if not uid_val['solid_geometry']: + tools_storage.pop(uid, None) + except KeyError: + tools_storage.pop(uid, None) geo_obj.options["cnctooldia"] = str(tool) From db9b2599cba977b783fe967d5046a156b02a4acf Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 19 Dec 2019 02:28:09 +0200 Subject: [PATCH 06/11] - fixed a typo in a app string --- FlatCAMApp.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 5b460a7f..25aab7d5 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -4170,16 +4170,10 @@ class App(QtCore.QObject): try: return_value = initialize(obj, self) except Exception as e: - msg = '[ERROR_NOTCL] %s' % \ - _("An internal error has ocurred. See shell.\n") + msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n") msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e)) msg += traceback.format_exc() self.inform.emit(msg) - - # if str(e) == "Empty Geometry": - # self.inform.emit("[ERROR_NOTCL] ) - # else: - # self.inform.emit("[ERROR] Object (%s) failed because: %s" % (kind, str(e))) return "fail" t2 = time.time() @@ -11271,6 +11265,7 @@ class App(QtCore.QObject): # Project options self.options.update(d['options']) + self.project_filename = filename # for some reason, setting ui_title does not work when this method is called from Tcl Shell @@ -11283,6 +11278,7 @@ class App(QtCore.QObject): for obj in d['objs']: def obj_init(obj_inst, app_inst): + obj_inst.from_dict(obj) App.log.debug("Recreating from opened project an %s object: %s" % From 04d30fb1b4bae0265b372c39c117abd1d58d5e91 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 19 Dec 2019 04:12:06 +0200 Subject: [PATCH 07/11] - in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid --- README.md | 4 + flatcamTools/ToolDblSided.py | 144 ++++++++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 277cbfdf..4ff18938 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +19.12.2019 + +- in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid + 18.12.2019 - added new parameters to improve Gerber parsing diff --git a/flatcamTools/ToolDblSided.py b/flatcamTools/ToolDblSided.py index 95392751..a0e00d27 100644 --- a/flatcamTools/ToolDblSided.py +++ b/flatcamTools/ToolDblSided.py @@ -2,9 +2,11 @@ from PyQt5 import QtWidgets, QtCore from FlatCAMTool import FlatCAMTool -from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry +from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry +from numpy import Inf + from shapely.geometry import Point from shapely import affinity @@ -219,6 +221,11 @@ class DblSidedTool(FlatCAMTool): self.box_combo.hide() self.box_combo_type.hide() + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid_lay2.addWidget(separator_line, 12, 0, 1, 2) + # ## Alignment holes self.ah_label = QtWidgets.QLabel("%s:" % _('Alignment Drill Coordinates')) self.ah_label.setToolTip( @@ -272,6 +279,7 @@ class DblSidedTool(FlatCAMTool): # Drill diameter value self.drill_dia = FCDoubleSpinner() self.drill_dia.set_precision(self.decimals) + self.drill_dia.set_range(0.0000, 9999.9999) self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia")) self.dd_label.setToolTip( @@ -296,6 +304,100 @@ class DblSidedTool(FlatCAMTool): """) self.layout.addWidget(self.create_alignment_hole_button) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.layout.addWidget(separator_line) + + grid1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid1) + grid1.setColumnStretch(0, 0) + grid1.setColumnStretch(1, 1) + + # Xmin value + self.xmin_entry = FCDoubleSpinner() + self.xmin_entry.set_precision(self.decimals) + self.xmin_entry.set_range(-9999.9999, 9999.9999) + + self.xmin_label = QtWidgets.QLabel('%s:' % _("X min")) + self.xmin_label.setToolTip( + _("Minimum location.") + ) + self.xmin_entry.setReadOnly(True) + + grid1.addWidget(self.xmin_label, 1, 0) + grid1.addWidget(self.xmin_entry, 1, 1) + + # Ymin value + self.ymin_entry = FCDoubleSpinner() + self.ymin_entry.set_precision(self.decimals) + self.ymin_entry.set_range(-9999.9999, 9999.9999) + + self.ymin_label = QtWidgets.QLabel('%s:' % _("Y min")) + self.ymin_label.setToolTip( + _("Minimum location.") + ) + self.ymin_entry.setReadOnly(True) + + grid1.addWidget(self.ymin_label, 2, 0) + grid1.addWidget(self.ymin_entry, 2, 1) + + # Xmax value + self.xmax_entry = FCDoubleSpinner() + self.xmax_entry.set_precision(self.decimals) + self.xmax_entry.set_range(-9999.9999, 9999.9999) + + self.xmax_label = QtWidgets.QLabel('%s:' % _("X max")) + self.xmax_label.setToolTip( + _("Maximum location.") + ) + self.xmax_entry.setReadOnly(True) + + grid1.addWidget(self.xmax_label, 3, 0) + grid1.addWidget(self.xmax_entry, 3, 1) + + # Ymax value + self.ymax_entry = FCDoubleSpinner() + self.ymax_entry.set_precision(self.decimals) + self.ymax_entry.set_range(-9999.9999, 9999.9999) + + self.ymax_label = QtWidgets.QLabel('%s:' % _("Y max")) + self.ymax_label.setToolTip( + _("Maximum location.") + ) + self.ymax_entry.setReadOnly(True) + + grid1.addWidget(self.ymax_label, 4, 0) + grid1.addWidget(self.ymax_entry, 4, 1) + + # Center point value + self.center_entry = FCEntry() + + self.center_label = QtWidgets.QLabel('%s:' % _("Centroid")) + self.center_label.setToolTip( + _("The center point location for the rectangular\n" + "bounding shape. Centroid. Format is (x, y).") + ) + self.center_entry.setReadOnly(True) + + grid1.addWidget(self.center_label, 5, 0) + grid1.addWidget(self.center_entry, 5, 1) + + # Calculate Bounding box + self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounding Box")) + self.calculate_bb_button.setToolTip( + _("Calculate the enveloping rectangular shape coordinates,\n" + "for the selection of objects.\n" + "The envelope shape is parallel with the X, Y axis.") + ) + self.calculate_bb_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.calculate_bb_button) + self.layout.addStretch() # ## Reset Tool @@ -312,18 +414,19 @@ class DblSidedTool(FlatCAMTool): self.layout.addWidget(self.reset_button) # ## Signals - self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes) self.mirror_gerber_button.clicked.connect(self.on_mirror_gerber) self.mirror_exc_button.clicked.connect(self.on_mirror_exc) self.mirror_geo_button.clicked.connect(self.on_mirror_geo) self.add_point_button.clicked.connect(self.on_point_add) self.add_drill_point_button.clicked.connect(self.on_drill_add) self.reset_button.clicked.connect(self.reset_fields) - self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type) self.axis_location.group_toggle_fn = self.on_toggle_pointbox + self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes) + self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates) + self.drill_values = "" def install(self, icon=None, separator=None, **kwargs): @@ -589,6 +692,41 @@ class DblSidedTool(FlatCAMTool): self.box_combo_type.show() self.add_point_button.setDisabled(True) + def on_bbox_coordinates(self): + + xmin = Inf + ymin = Inf + xmax = -Inf + ymax = -Inf + + obj_list = self.app.collection.get_selected() + + if not obj_list: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected...")) + return + + for obj in obj_list: + try: + gxmin, gymin, gxmax, gymax = obj.bounds() + xmin = min([xmin, gxmin]) + ymin = min([ymin, gymin]) + xmax = max([xmax, gxmax]) + ymax = max([ymax, gymax]) + except Exception as e: + log.warning("DEV WARNING: Tried to get bounds of empty geometry in DblSidedTool. %s" % str(e)) + + self.xmin_entry.set_value(xmin) + self.ymin_entry.set_value(ymin) + self.xmax_entry.set_value(xmax) + self.ymax_entry.set_value(ymax) + cx = '%.*f' % (self.decimals, (((xmax - xmin) / 2.0) + xmin)) + cy = '%.*f' % (self.decimals, (((ymax - ymin) / 2.0) + ymin)) + val_txt = '(%s, %s)' % (cx, cy) + + self.center_entry.set_value(val_txt) + self.axis_location.set_value('point') + self.point_entry.set_value(val_txt) + def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) From 80f1d30a2825f108e8a9388818f1586357f715dd Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 19 Dec 2019 16:26:19 +0200 Subject: [PATCH 08/11] - added Preferences values for PDF margins when saving text in Code Editor as PDF - when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values --- FlatCAMApp.py | 79 +++++++++++++++++++++++------ README.md | 3 ++ flatcamEditors/FlatCAMTextEditor.py | 23 ++++++--- flatcamGUI/PreferencesUI.py | 70 +++++++++++++++++++++++++ flatcamTools/ToolDblSided.py | 20 +++++--- 5 files changed, 167 insertions(+), 28 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 25aab7d5..569e0b8e 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -416,6 +416,10 @@ class App(QtCore.QObject): "global_stats": dict(), "global_tabs_detachable": True, "global_jump_ref": 'abs', + "global_tpdf_tmargin": 15.0, + "global_tpdf_bmargin": 10.0, + "global_tpdf_lmargin": 20.0, + "global_tpdf_rmargin": 20.0, # General "global_graphic_engine": '3D', @@ -978,6 +982,10 @@ class App(QtCore.QObject): self.current_units = self.defaults['units'] + # store here the current self.defaults so it can be restored if Preferences changes are cancelled + self.current_defaults = dict() + self.current_defaults.update(self.defaults) + # ############################################################################# # ##################### CREATE MULTIPROCESSING POOL ########################### # ############################################################################# @@ -1085,6 +1093,11 @@ class App(QtCore.QObject): "global_bookmarks_limit": self.ui.general_defaults_form.general_app_group.bm_limit_spinner, "global_machinist_setting": self.ui.general_defaults_form.general_app_group.machinist_cb, + "global_tpdf_tmargin": self.ui.general_defaults_form.general_app_group.tmargin_entry, + "global_tpdf_bmargin": self.ui.general_defaults_form.general_app_group.bmargin_entry, + "global_tpdf_lmargin": self.ui.general_defaults_form.general_app_group.lmargin_entry, + "global_tpdf_rmargin": self.ui.general_defaults_form.general_app_group.rmargin_entry, + # General GUI Preferences "global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_entry, "global_gridy": self.ui.general_defaults_form.general_gui_group.gridy_entry, @@ -2940,17 +2953,25 @@ class App(QtCore.QObject): except Exception as e: log.debug("App.defaults_read_form() --> %s" % str(e)) - def defaults_write_form(self, factor=None, fl_units=None): + def defaults_write_form(self, factor=None, fl_units=None, source_dict=None): """ Will set the values for all the GUI elements in Preferences GUI based on the values found in the self.defaults dictionary. :param factor: will apply a factor to the values that written in the GUI elements :param fl_units: current measuring units in FlatCAM: Metric or Inch + :param source_dict: the repository of options, usually is the self.defaults :return: None """ - for option in self.defaults: - self.defaults_write_form_field(option, factor=factor, units=fl_units) + + options_storage = self.defaults if source_dict is None else source_dict + + for option in options_storage: + if source_dict: + self.defaults_write_form_field(option, factor=factor, units=fl_units, defaults_dict=source_dict) + else: + self.defaults_write_form_field(option, factor=factor, units=fl_units) + # try: # self.defaults_form_fields[option].set_value(self.defaults[option]) # except KeyError: @@ -2958,7 +2979,7 @@ class App(QtCore.QObject): # # TODO: Rethink this? # pass - def defaults_write_form_field(self, field, factor=None, units=None): + def defaults_write_form_field(self, field, factor=None, units=None, defaults_dict=None): """ Basically it is the worker in the self.defaults_write_form() @@ -2967,21 +2988,23 @@ class App(QtCore.QObject): :param units: current FLatCAM measuring units :return: None, it updates GUI elements """ + + def_dict = self.defaults if defaults_dict is None else defaults_dict try: if factor is None: if units is None: - self.defaults_form_fields[field].set_value(self.defaults[field]) + self.defaults_form_fields[field].set_value(def_dict[field]) elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value(self.defaults[field]) + self.defaults_form_fields[field].set_value(def_dict[field]) elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value(self.defaults[field]) + self.defaults_form_fields[field].set_value(def_dict[field]) else: if units is None: - self.defaults_form_fields[field].set_value(self.defaults[field] * factor) + self.defaults_form_fields[field].set_value(def_dict[field] * factor) elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value((self.defaults[field] * factor)) + self.defaults_form_fields[field].set_value((def_dict[field] * factor)) elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value((self.defaults[field] * factor)) + self.defaults_form_fields[field].set_value((def_dict[field] * factor)) except KeyError: # self.log.debug("defaults_write_form(): No field for: %s" % option) # TODO: Rethink this? @@ -3891,6 +3914,10 @@ class App(QtCore.QObject): _("Failed to parse defaults file.")) return self.defaults.update(defaults_from_file) + # update the dict that is used to restore the values in the defaults form if Cancel is clicked in the + # Preferences window + self.current_defaults.update(defaults_from_file) + self.on_preferences_edited() self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename)) @@ -5764,14 +5791,15 @@ class App(QtCore.QObject): "tools_cr_trace_size_val", "tools_cr_c2c_val", "tools_cr_c2o_val", "tools_cr_s2s_val", "tools_cr_s2sm_val", "tools_cr_s2o_val", "tools_cr_sm2sm_val", "tools_cr_ri_val", "tools_cr_h2h_val", "tools_cr_dh_val", "tools_fiducials_dia", "tools_fiducials_margin", - "tools_fiducials_mode", "tools_fiducials_second_pos", "tools_fiducials_type", "tools_fiducials_line_thickness", "tools_copper_thieving_clearance", "tools_copper_thieving_margin", "tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing", "tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing", "tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing", "tools_copper_thieving_rb_margin", "tools_copper_thieving_rb_thickness", - 'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance"] + + 'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance", + 'global_tpdf_bmargin', 'global_tpdf_tmargin', 'global_tpdf_rmargin', 'global_tpdf_lmargin'] def scale_defaults(sfactor): for dim in dimensions: @@ -5796,6 +5824,7 @@ class App(QtCore.QObject): tools_diameters = [eval(a) for a in tools_string if a != ''] except Exception as e: log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) + continue self.defaults['geometry_cnctooldia'] = '' for t in range(len(tools_diameters)): @@ -5808,6 +5837,7 @@ class App(QtCore.QObject): ncctools = [eval(a) for a in tools_string if a != ''] except Exception as e: log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) + continue self.defaults['tools_ncctools'] = '' for t in range(len(ncctools)): @@ -5820,6 +5850,7 @@ class App(QtCore.QObject): sptools = [eval(a) for a in tools_string if a != ''] except Exception as e: log.debug("App.on_toggle_units().scale_options() --> %s" % str(e)) + continue self.defaults['tools_solderpaste_tools'] = "" for t in range(len(sptools)): @@ -5839,6 +5870,7 @@ class App(QtCore.QObject): val = float(self.defaults[dim]) * sfactor except Exception as e: log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) + continue self.defaults[dim] = float('%.*f' % (self.decimals, val)) else: @@ -5847,6 +5879,7 @@ class App(QtCore.QObject): val = float(self.defaults[dim]) * sfactor except Exception as e: log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) + continue self.defaults[dim] = float('%.*f' % (self.decimals, val)) else: @@ -5855,7 +5888,8 @@ class App(QtCore.QObject): try: val = float(self.defaults[dim]) * sfactor except Exception as e: - log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e)) + log.debug('App.on_toggle_units().scale_defaults() --> Value: %s %s' % (str(dim), str(e))) + continue self.defaults[dim] = val @@ -7029,6 +7063,9 @@ class App(QtCore.QObject): self.inform.emit('%s' % _("Preferences applied.")) + # make sure we update the self.current_defaults dict used to undo changes to self.defaults + self.current_defaults.update(self.defaults) + if save_to_file: self.save_defaults(silent=False) # load the defaults so they are updated into the app @@ -7067,7 +7104,18 @@ class App(QtCore.QObject): except TypeError: pass - self.defaults_write_form() + try: + self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect() + except (TypeError, AttributeError): + pass + self.defaults_write_form(source_dict=self.current_defaults) + self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( + lambda: self.on_toggle_units(no_pref=False)) + self.defaults.update(self.current_defaults) + + # shared_items = {k: self.defaults[k] for k in self.defaults if k in self.current_defaults and + # self.defaults[k] == self.current_defaults[k]} + # print(len(self.defaults), len(shared_items)) # Preferences save, update the color of the Preferences Tab text for idx in range(self.ui.plot_tab_area.count()): @@ -7797,8 +7845,7 @@ class App(QtCore.QObject): pass def on_preferences_edited(self): - self.inform.emit('[WARNING_NOTCL] %s' % - _("Preferences edited but not saved.")) + self.inform.emit('[WARNING_NOTCL] %s' % _("Preferences edited but not saved.")) for idx in range(self.ui.plot_tab_area.count()): if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): diff --git a/README.md b/README.md index 4ff18938..61efc7ca 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ CAD program, and create G-Code for Isolation routing. 19.12.2019 - in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid +- in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string +- added Preferences values for PDF margins when saving text in Code Editor as PDF +- when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values 18.12.2019 diff --git a/flatcamEditors/FlatCAMTextEditor.py b/flatcamEditors/FlatCAMTextEditor.py index d850729a..a8d9eff7 100644 --- a/flatcamEditors/FlatCAMTextEditor.py +++ b/flatcamEditors/FlatCAMTextEditor.py @@ -196,7 +196,7 @@ class TextEditor(QtWidgets.QWidget): _filter_ = filt else: _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \ - "All Files (*.*)" + "PDF Files (*.pdf);;All Files (*.*)" if name: obj_name = name @@ -206,7 +206,7 @@ class TextEditor(QtWidgets.QWidget): except AttributeError: obj_name = 'file' if filt is None: - _filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)" + _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)" try: filename = str(QtWidgets.QFileDialog.getSaveFileName( @@ -237,13 +237,24 @@ class TextEditor(QtWidgets.QWidget): styleH = styles['Heading1'] story = [] + if self.app.defaults['units'].lower() == 'mm': + bmargin = self.app.defaults['global_tpdf_bmargin'] * mm + tmargin = self.app.defaults['global_tpdf_tmargin'] * mm + rmargin = self.app.defaults['global_tpdf_rmargin'] * mm + lmargin = self.app.defaults['global_tpdf_lmargin'] * mm + else: + bmargin = self.app.defaults['global_tpdf_bmargin'] * inch + tmargin = self.app.defaults['global_tpdf_tmargin'] * inch + rmargin = self.app.defaults['global_tpdf_rmargin'] * inch + lmargin = self.app.defaults['global_tpdf_lmargin'] * inch + doc = SimpleDocTemplate( filename, pagesize=page_size, - bottomMargin=0.4 * inch, - topMargin=0.6 * inch, - rightMargin=0.8 * inch, - leftMargin=0.8 * inch) + bottomMargin=bmargin, + topMargin=tmargin, + rightMargin=rmargin, + leftMargin=lmargin) P = Paragraph(lined_gcode, styleN) story.append(P) diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index 6aaeabc3..6b34a72c 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -1330,6 +1330,76 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.machinist_cb, 21, 0, 1, 2) + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.layout.addWidget(separator_line) + + self.layout.addWidget(QtWidgets.QLabel('')) + + grid1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid1) + grid1.setColumnStretch(0, 0) + grid1.setColumnStretch(1, 1) + + self.pdf_param_label = QtWidgets.QLabel('%s:' % _("Text to PDF parameters")) + self.pdf_param_label.setToolTip( + _("Used when saving text in Code Editor or in FlatCAM Document objects.") + ) + grid1.addWidget(self.pdf_param_label, 0, 0, 1, 2) + + # Top Margin value + self.tmargin_entry = FCDoubleSpinner() + self.tmargin_entry.set_precision(self.decimals) + self.tmargin_entry.set_range(0.0000, 9999.9999) + + self.tmargin_label = QtWidgets.QLabel('%s:' % _("Top Margin")) + self.tmargin_label.setToolTip( + _("Distance between text body and the top of the PDF file.") + ) + + grid1.addWidget(self.tmargin_label, 1, 0) + grid1.addWidget(self.tmargin_entry, 1, 1) + + # Bottom Margin value + self.bmargin_entry = FCDoubleSpinner() + self.bmargin_entry.set_precision(self.decimals) + self.bmargin_entry.set_range(0.0000, 9999.9999) + + self.bmargin_label = QtWidgets.QLabel('%s:' % _("Bottom Margin")) + self.bmargin_label.setToolTip( + _("Distance between text body and the bottom of the PDF file.") + ) + + grid1.addWidget(self.bmargin_label, 2, 0) + grid1.addWidget(self.bmargin_entry, 2, 1) + + # Left Margin value + self.lmargin_entry = FCDoubleSpinner() + self.lmargin_entry.set_precision(self.decimals) + self.lmargin_entry.set_range(0.0000, 9999.9999) + + self.lmargin_label = QtWidgets.QLabel('%s:' % _("Left Margin")) + self.lmargin_label.setToolTip( + _("Distance between text body and the left of the PDF file.") + ) + + grid1.addWidget(self.lmargin_label, 3, 0) + grid1.addWidget(self.lmargin_entry, 3, 1) + + # Right Margin value + self.rmargin_entry = FCDoubleSpinner() + self.rmargin_entry.set_precision(self.decimals) + self.rmargin_entry.set_range(0.0000, 9999.9999) + + self.rmargin_label = QtWidgets.QLabel('%s:' % _("Right Margin")) + self.rmargin_label.setToolTip( + _("Distance between text body and the right of the PDF file.") + ) + + grid1.addWidget(self.rmargin_label, 4, 0) + grid1.addWidget(self.rmargin_entry, 4, 1) + self.layout.addStretch() if sys.platform != 'win32': diff --git a/flatcamTools/ToolDblSided.py b/flatcamTools/ToolDblSided.py index a0e00d27..3308c159 100644 --- a/flatcamTools/ToolDblSided.py +++ b/flatcamTools/ToolDblSided.py @@ -281,13 +281,12 @@ class DblSidedTool(FlatCAMTool): self.drill_dia.set_precision(self.decimals) self.drill_dia.set_range(0.0000, 9999.9999) - self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia")) - self.dd_label.setToolTip( + self.drill_dia.setToolTip( _("Diameter of the drill for the " "alignment holes.") ) - grid0.addWidget(self.dd_label, 1, 0) - grid0.addWidget(self.drill_dia, 1, 1) + + grid0.addWidget(self.drill_dia, 1, 0, 1, 2) # ## Buttons self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object")) @@ -309,6 +308,8 @@ class DblSidedTool(FlatCAMTool): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) self.layout.addWidget(separator_line) + self.layout.addWidget(QtWidgets.QLabel('')) + grid1 = QtWidgets.QGridLayout() self.layout.addLayout(grid1) grid1.setColumnStretch(0, 0) @@ -384,7 +385,7 @@ class DblSidedTool(FlatCAMTool): grid1.addWidget(self.center_entry, 5, 1) # Calculate Bounding box - self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounding Box")) + self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounds Values")) self.calculate_bb_button.setToolTip( _("Calculate the enveloping rectangular shape coordinates,\n" "for the selection of objects.\n" @@ -419,7 +420,6 @@ class DblSidedTool(FlatCAMTool): self.mirror_geo_button.clicked.connect(self.on_mirror_geo) self.add_point_button.clicked.connect(self.on_point_add) self.add_drill_point_button.clicked.connect(self.on_drill_add) - self.reset_button.clicked.connect(self.reset_fields) self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type) self.axis_location.group_toggle_fn = self.on_toggle_pointbox @@ -427,6 +427,8 @@ class DblSidedTool(FlatCAMTool): self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes) self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates) + self.reset_button.clicked.connect(self.set_tool_ui) + self.drill_values = "" def install(self, icon=None, separator=None, **kwargs): @@ -469,6 +471,12 @@ class DblSidedTool(FlatCAMTool): self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"]) self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"]) + self.xmin_entry.set_value(0.0) + self.ymin_entry.set_value(0.0) + self.xmax_entry.set_value(0.0) + self.ymax_entry.set_value(0.0) + self.center_entry.set_value('') + 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())) From 284d500073c480a5a5fffdd954e49514014c3fa9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 19 Dec 2019 17:15:41 +0200 Subject: [PATCH 09/11] - starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection - added shortcut key CTRL+P for printing to PDF method --- FlatCAMApp.py | 27 +++++++++++++++++++-------- README.md | 2 ++ flatcamGUI/FlatCAMGUI.py | 36 +++++++++++++++++++++--------------- share/printer16.png | Bin 0 -> 559 bytes share/printer32.png | Bin 0 -> 818 bytes 5 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 share/printer16.png create mode 100644 share/printer32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 569e0b8e..ff4cb5d0 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1813,10 +1813,11 @@ class App(QtCore.QObject): self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf) + self.ui.menufile_print.triggered.connect(lambda: self.on_file_save_objects_pdf(use_thread=True)) + self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject) self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas) self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True)) - self.ui.menufilesave_object_pdf.triggered.connect(self.on_file_save_object_pdf) self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults) self.ui.menufileexportpref.triggered.connect(self.on_export_preferences) @@ -10322,19 +10323,26 @@ class App(QtCore.QObject): self.set_ui_title(name=self.project_filename) self.should_we_save = False - def on_file_save_object_pdf(self, use_thread=True): + def on_file_save_objects_pdf(self, use_thread=True): self.date = str(datetime.today()).rpartition('.')[0] self.date = ''.join(c for c in self.date if c not in ':-') self.date = self.date.replace(' ', '_') try: - obj_active = self.collection.get_active() - obj_name = _(str(obj_active.options['name'])) + obj_selection = self.collection.get_selected() + if len(obj_selection) == 1: + obj_name = str(obj_selection[0].options['name']) + else: + obj_name = _("FlatCAM objects print") except AttributeError as err: log.debug("App.on_file_save_object_pdf() --> %s" % str(err)) self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected.")) return + if not obj_selection: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected.")) + return + filter_ = "PDF File (*.PDF);; All Files (*.*)" try: filename, _f = QtWidgets.QFileDialog.getSaveFileName( @@ -10354,17 +10362,20 @@ class App(QtCore.QObject): return if use_thread is True: - self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name]}) + self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name, obj_selection]}) else: - self.save_pdf(filename, obj_name) + self.save_pdf(filename, obj_name, obj_selection) # self.save_project(filename) if self.defaults["global_open_style"] is False: self.file_opened.emit("pdf", filename) self.file_saved.emit("pdf", filename) - def save_pdf(self, file_name, obj_name): - self.film_tool.export_positive(obj_name=obj_name, box_name=obj_name, filename=file_name, ftype='pdf') + def save_pdf(self, file_name, obj_name, obj_selection): + if len(obj_selection) == 1: + self.film_tool.export_positive(obj_name=obj_name, box_name=obj_name, filename=file_name, ftype='pdf') + else: + self.inform.emit('[WARNING_NOTCL] %s' % _("Multiple objects print not implemented yet.")) def export_svg(self, obj_name, filename, scale_stroke_factor=0.00): """ diff --git a/README.md b/README.md index 61efc7ca..fdd73dba 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ CAD program, and create G-Code for Isolation routing. - in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string - added Preferences values for PDF margins when saving text in Code Editor as PDF - when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values +- starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection +- added shortcut key CTRL+P for printing to PDF method 18.12.2019 diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 9d451575..fc75f8a5 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -240,6 +240,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Separator self.menufile.addSeparator() + self.menufile_print = QtWidgets.QAction( + QtGui.QIcon(self.app.resource_location + '/printer32.png'), '%s\tCTRL+P' % _('Print (PDF)')) + self.menufile.addAction(self.menufile_print) self.menufile_save = self.menufile.addMenu(QtGui.QIcon(self.app.resource_location + '/save_as.png'), _('Save')) @@ -260,11 +263,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.menufile_save.addSeparator() - # Save Object PDF - self.menufilesave_object_pdf = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/pdf32.png'), - _('Save Object as PDF ...'), self) - self.menufile_save.addAction(self.menufilesave_object_pdf) - # Separator self.menufile.addSeparator() @@ -1381,18 +1379,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow): CTRL+G  %s - - CTRL+N -  %s - CTRL+M  %s + + CTRL+N +  %s + CTRL+O  %s + + CTRL+P +  %s + CTRL+Q  %s @@ -1579,8 +1581,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # CTRL section _("Select All"), _("Copy Obj"), _("Open Tools Database"), - _("Open Excellon File"), _("Open Gerber File"), _("New Project"), _("Distance Tool"), - _("Open Project"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"), + _("Open Excellon File"), _("Open Gerber File"), _("Distance Tool"), _("New Project"), + _("Open Project"), _("Print (PDF)"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"), # SHIFT section _("Copy Obj_Name"), @@ -2671,18 +2673,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_G: self.app.on_fileopengerber() - # Create New Project - if key == QtCore.Qt.Key_N: - self.app.on_file_new_click() - # Distance Tool if key == QtCore.Qt.Key_M: self.app.distance_tool.run() + # Create New Project + if key == QtCore.Qt.Key_N: + self.app.on_file_new_click() + # Open Project if key == QtCore.Qt.Key_O: self.app.on_file_openproject() + # Open Project + if key == QtCore.Qt.Key_P: + self.app.on_file_save_objects_pdf(use_thread=True) + # PDF Import if key == QtCore.Qt.Key_Q: self.app.pdf_tool.run() diff --git a/share/printer16.png b/share/printer16.png new file mode 100644 index 0000000000000000000000000000000000000000..1b98d2fbf639b8399767eb32aed9a1a0d93c728a GIT binary patch literal 559 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+S=2=l2i2uW020t)^0ba4#PINy5F*1IWDT)YyVkyI0I?b2rC)xBSn1y_WC4i_W5S-PTpl_D1)(H!P}}akQ?`f{Epotto@Zn#e%D z_{wCtyWeedymkhDlVbYZ_a>w21w*&r-M=3jbC1U*-Ec}>F!#FI(n}u=nmpO1n2s!+ zd;7z(o2ENoU0Y{-`Qex4w$mpW$to1|?hrmH@Z(#~q5cL%6WOTnfQe>Z`k}6zmf=x% zLzllb5$R2+7#p!^^C25YmQoo{Jej=i}dRlo6eaB|Et_t0SpM$ z64!{5l*E!$tK_0oAjM#0U}&jpXryas8DeN+Wo%((V4!Va03k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*&p)=^Qo%Ov7c83)WqQF;uzv_{OT27kB~%>V;|?o z_qsSLEYTDU>)z1frCKi4voL5~%f4w7Id^g`^4+Z??x>*R@^q1y;I_>jM?8JB7G4p1 z>9^E%hSNm7mXIDXzjJ5i2C~QeymPjC{`Wcd&uyRIo36XQ$MlbOAN!m{{>$u7BuY&m zlv*TdTAEm^DqP;d=B`H?nOWym|F%iQ;6nNAo=A zhc(2&GUcEVl97SN>88tA1aHfPt#{vkXvY^#-f4TU z$@adylFYj`({-!f6s_Q8BKjc@w^^G7%&ogwu{5Y}`L2>mCHL0pCxk9em?5xdYW0~J zlNQHJea`20xA6HM;}p+@ceAcDzX|O;z3&C@n=2Nt4B2Yq`8O;wnm*~>(bTzZ=C2M+ zyJCM}Mx9k*!;j6to=IEp-izbPX*-3{9+z&8>_rwG9lc z3=9@E2lk?9$jwj5OsmAL;qtwghCmIHARB`7(@M${i&7cN%ggmL^RkPR6AM!H@{7`E Tzq647Dq`?-^>bP0l+XkKMIlHN literal 0 HcmV?d00001 From f604cd461d708257d19220d503283e57c6f6c06c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 20 Dec 2019 01:12:02 +0200 Subject: [PATCH 10/11] - fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab) --- FlatCAMObj.py | 3 +++ README.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 2574232b..6029ca31 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -981,6 +981,9 @@ 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) + bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"])) if not self.options["noncopperrounded"]: bounding_box = bounding_box.envelope diff --git a/README.md b/README.md index fdd73dba..263a30fc 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +20.12.2019 + +- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab) + 19.12.2019 - in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid From 28bf1c3b5aa293e4bb7e4c02963c8fd445bf02cc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 20 Dec 2019 02:41:13 +0200 Subject: [PATCH 11/11] - Print function is now printing a PDF file for a selection of objects in the colors from canvas --- FlatCAMApp.py | 178 ++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 1 + 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index ff4cb5d0..d4d0e16e 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -31,6 +31,7 @@ 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 contextlib import contextmanager import gc @@ -10362,20 +10363,185 @@ class App(QtCore.QObject): return if use_thread is True: - self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name, obj_selection]}) + proc = self.proc_container.new(_("Printing PDF ... Please wait.")) + self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_selection]}) else: - self.save_pdf(filename, obj_name, obj_selection) + self.save_pdf(filename, obj_selection) # self.save_project(filename) if self.defaults["global_open_style"] is False: self.file_opened.emit("pdf", filename) self.file_saved.emit("pdf", filename) - def save_pdf(self, file_name, obj_name, obj_selection): - if len(obj_selection) == 1: - self.film_tool.export_positive(obj_name=obj_name, box_name=obj_name, filename=file_name, ftype='pdf') + def save_pdf(self, file_name, obj_selection): + + p_size = self.defaults['global_workspaceT'] + orientation = self.defaults['global_workspace_orientation'] + color = 'black' + transparency_level = 1.0 + + self.pagesize = dict() + self.pagesize.update( + { + 'Bounds': None, + 'A0': (841*mm, 1189*mm), + 'A1': (594*mm, 841*mm), + 'A2': (420*mm, 594*mm), + 'A3': (297*mm, 420*mm), + 'A4': (210*mm, 297*mm), + 'A5': (148*mm, 210*mm), + 'A6': (105*mm, 148*mm), + 'A7': (74*mm, 105*mm), + 'A8': (52*mm, 74*mm), + 'A9': (37*mm, 52*mm), + 'A10': (26*mm, 37*mm), + + 'B0': (1000*mm, 1414*mm), + 'B1': (707*mm, 1000*mm), + 'B2': (500*mm, 707*mm), + 'B3': (353*mm, 500*mm), + 'B4': (250*mm, 353*mm), + 'B5': (176*mm, 250*mm), + 'B6': (125*mm, 176*mm), + 'B7': (88*mm, 125*mm), + 'B8': (62*mm, 88*mm), + 'B9': (44*mm, 62*mm), + 'B10': (31*mm, 44*mm), + + 'C0': (917*mm, 1297*mm), + 'C1': (648*mm, 917*mm), + 'C2': (458*mm, 648*mm), + 'C3': (324*mm, 458*mm), + 'C4': (229*mm, 324*mm), + 'C5': (162*mm, 229*mm), + 'C6': (114*mm, 162*mm), + 'C7': (81*mm, 114*mm), + 'C8': (57*mm, 81*mm), + 'C9': (40*mm, 57*mm), + 'C10': (28*mm, 40*mm), + + # American paper sizes + 'LETTER': (8.5*inch, 11*inch), + 'LEGAL': (8.5*inch, 14*inch), + 'ELEVENSEVENTEEN': (11*inch, 17*inch), + + # From https://en.wikipedia.org/wiki/Paper_size + 'JUNIOR_LEGAL': (5*inch, 8*inch), + 'HALF_LETTER': (5.5*inch, 8*inch), + 'GOV_LETTER': (8*inch, 10.5*inch), + 'GOV_LEGAL': (8.5*inch, 13*inch), + 'LEDGER': (17*inch, 11*inch), + } + ) + + exported_svg = list() + for obj in obj_selection: + svg_obj = obj.export_svg(scale_stroke_factor=0.0, + scale_factor_x=None, scale_factor_y=None, + skew_factor_x=None, skew_factor_y=None, + mirror=None) + + if obj.kind.lower() == 'gerber': + color = self.defaults["global_plot_fill"][:-2] + elif obj.kind.lower() == 'excellon': + color = '#C40000' + elif obj.kind.lower() == 'geometry': + color = self.defaults["global_draw_color"] + + # Change the attributes of the exported SVG + # We don't need stroke-width + # We set opacity to maximum + # We set the colour to WHITE + root = ET.fromstring(svg_obj) + for child in root: + child.set('fill', str(color)) + child.set('opacity', str(transparency_level)) + child.set('stroke', str(color)) + + exported_svg.append(ET.tostring(root)) + + xmin = Inf + ymin = Inf + xmax = -Inf + ymax = -Inf + + for obj in obj_selection: + try: + gxmin, gymin, gxmax, gymax = obj.bounds() + xmin = min([xmin, gxmin]) + ymin = min([ymin, gymin]) + xmax = max([xmax, gxmax]) + ymax = max([ymax, gymax]) + except Exception as e: + log.warning("DEV WARNING: Tried to get bounds of empty geometry in App.save_pdf(). %s" % str(e)) + + # Determine bounding area for svg export + bounds = [xmin, ymin, xmax, ymax] + size = bounds[2] - bounds[0], bounds[3] - bounds[1] + + # This contain the measure units + uom = obj_selection[0].units.lower() + + # Define a boundary around SVG of about 1.0mm (~39mils) + if uom in "mm": + boundary = 1.0 else: - self.inform.emit('[WARNING_NOTCL] %s' % _("Multiple objects print not implemented yet.")) + boundary = 0.0393701 + + # Convert everything to strings for use in the xml doc + svgwidth = str(size[0] + (2 * boundary)) + svgheight = str(size[1] + (2 * boundary)) + minx = str(bounds[0] - boundary) + miny = str(bounds[1] + boundary + size[1]) + + # Add a SVG Header and footer to the svg output from shapely + # The transform flips the Y Axis so that everything renders + # properly within svg apps such as inkscape + svg_header = ' PDF output --> %s" % str(e)) + return 'fail' + + self.inform.emit('[success] %s: %s' % (_("PDF file saved to"), file_name)) def export_svg(self, obj_name, filename, scale_stroke_factor=0.00): """ diff --git a/README.md b/README.md index 263a30fc..d0e5efe1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing. 20.12.2019 - fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab) +- Print function is now printing a PDF file for a selection of objects in the colors from canvas 19.12.2019