diff --git a/FlatCAMApp.py b/FlatCAMApp.py index a27bbca4..4b3e3990 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -373,6 +373,12 @@ class App(QtCore.QObject): "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry, "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb, + # Gerber Export + "gerber_exp_units": self.ui.gerber_defaults_form.gerber_exp_group.gerber_units_radio, + "gerber_exp_integer": self.ui.gerber_defaults_form.gerber_exp_group.format_whole_entry, + "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry, + "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio, + # Excellon General "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb, "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb, @@ -682,6 +688,12 @@ class App(QtCore.QObject): "gerber_aperture_buffer_factor": 0.0, "gerber_follow": False, + # Gerber Export + "gerber_exp_units": 'IN', + "gerber_exp_integer": 2, + "gerber_exp_decimals": 4, + "gerber_exp_zeros": 'L', + # Excellon General "excellon_plot": True, "excellon_solid": True, @@ -1299,7 +1311,7 @@ class App(QtCore.QObject): self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg) self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng) self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon) - + self.ui.menufileexportgerber.triggered.connect(self.on_file_exportgerber) self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf) @@ -6167,7 +6179,7 @@ class App(QtCore.QObject): def on_file_exportexcellon(self): """ - Callback for menu item File->Export SVG. + Callback for menu item File->Export->Excellon. :return: None """ @@ -6204,6 +6216,45 @@ class App(QtCore.QObject): self.export_excellon(name, filename) self.file_saved.emit("Excellon", filename) + def on_file_exportgerber(self): + """ + Callback for menu item File->Export->Gerber. + + :return: None + """ + self.report_usage("on_file_exportgerber") + App.log.debug("on_file_exportgerber()") + + obj = self.collection.get_active() + if obj is None: + self.inform.emit(_("[WARNING_NOTCL] No object selected. Please Select an Gerber object to export.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, FlatCAMGerber): + self.inform.emit(_("[ERROR_NOTCL] Failed. Only Gerber objects can be saved as Gerber files...")) + return + + name = self.collection.get_active().options["name"] + + filter = "Gerber File (*.GBR);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getSaveFileName( + caption=_("Export Gerber"), + directory=self.get_last_save_folder() + '/' + name, + filter=filter) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Gerber"), filter=filter) + + filename = str(filename) + + if filename == "": + self.inform.emit(_("[WARNING_NOTCL] Export Gerber cancelled.")) + return + else: + self.export_gerber(name, filename) + self.file_saved.emit("Gerber", filename) + def on_file_exportdxf(self): """ Callback for menu item File->Export DXF. @@ -7005,6 +7056,121 @@ class App(QtCore.QObject): self.inform.emit(_('[ERROR_NOTCL] Could not export Excellon file.')) return + def export_gerber(self, obj_name, filename, use_thread=True): + """ + Exports a Gerber Object to an Gerber file. + + :param filename: Path to the Gerber file to save to. + :return: + """ + self.report_usage("export_gerber()") + + if filename is None: + filename = self.defaults["global_last_save_folder"] + + self.log.debug("export_gerber()") + + try: + obj = self.collection.get_by_name(str(obj_name)) + except: + # TODO: The return behavior has not been established... should raise exception? + return "Could not retrieve object: %s" % obj_name + + # updated units + gunits = self.defaults["gerber_exp_units"] + gwhole = self.defaults["gerber_exp_integer"] + gfract = self.defaults["gerber_exp_decimals"] + gzeros = self.defaults["gerber_exp_zeros"] + + fc_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() + if fc_units == 'MM': + factor = 1 if gunits == 'MM' else 0.03937 + else: + factor = 25.4 if gunits == 'MM' else 1 + + def make_gerber(): + try: + time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + + header = 'G04*\n' + header += ';GERBER GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ + (str(self.version), str(self.version_date)) + + header += ';Filename: %s' % str(obj_name) + '\n' + header += ';Created on : %s' % time_str + '\n' + header += '%%FS%sAX%s%sY%s%s*%%\n' % (gzeros, gwhole, gfract, gwhole, gfract) + + # gerber_code = obj.export_gerber(gwhole, gfract, form='ndec', e_zeros=gzeros, factor=factor) + + for apid in obj.apertures: + if obj.apertures[apid]['type'] == 'C': + header += "%ADD{apid}{type},{size}*%\n".format(apid=str(apid), + type='C', + size=obj.apertures[apid]['size']) + elif obj.apertures[apid]['type'] == 'R': + header += "%ADD{apid}{type},{width}X{height}*%\n".format( + apid=str(apid), + type='R', + width=obj.apertures[apid]['width'], + height=obj.apertures[apid]['height'] + ) + elif obj.apertures[apid]['type'] == 'O': + header += "%ADD{apid}{type},{width}X{height}*%\n".format( + apid=str(apid), + type='O', + width=obj.apertures[apid]['width'], + height=obj.apertures[apid]['height'] + ) + + header += '\n' + header += "%MO{units}*%\n".format(units=gunits) + header += "G04*\n" + if gunits == 'IN': + header += 'G71*\n' + else: + header += 'G70*\n' + header += 'G75*\n' + + if gunits == 'IN': + header += 'G91*\n' + else: + header += 'G90*\n' + + header += 'G01*\n' + header += '%LPD*%\n' + + footer = 'M02*\n' + + exported_gerber = header + # exported_gerber += gerber_code + exported_gerber += footer + + with open(filename, 'w') as fp: + fp.write(exported_gerber) + + self.file_saved.emit("Gerber", filename) + self.inform.emit(_("[success] Gerber file exported to %s") % filename) + except Exception as e: + log.debug("App.export_gerber.make_gerber() --> %s" % str(e)) + return 'fail' + + if use_thread is True: + + with self.proc_container.new(_("Exporting Gerber")) as proc: + + def job_thread_exc(app_obj): + ret = make_gerber() + if ret == 'fail': + self.inform.emit(_('[ERROR_NOTCL] Could not export Gerber file.')) + return + + self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]}) + else: + ret = make_gerber() + if ret == 'fail': + self.inform.emit(_('[ERROR_NOTCL] Could not export Gerber file.')) + return + def export_dxf(self, obj_name, filename, use_thread=True): """ Exports a Geometry Object to an DXF file. diff --git a/README.md b/README.md index b81f75a8..b53e7275 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ CAD program, and create G-Code for Isolation routing. 7.05.2019 -- remade the Tool Paenlize GUI +- remade the Tool Panelize GUI +- work in Gerber Export: finished the header export 6.05.2019 diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index c1ed9edf..0cf0d8b9 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -183,6 +183,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ) self.menufileexport.addAction(self.menufileexportexcellon) + self.menufileexportgerber = QtWidgets.QAction(QtGui.QIcon('share/flatcam_icon32.png'), _('Export &Gerber ...'), + self) + self.menufileexportgerber.setToolTip( + _("Will export an Gerber Object as Gerber file,\n" + "the coordinates format, the file units and zeros\n" + "are set in Preferences -> Gerber Export.") + ) + self.menufileexport.addAction(self.menufileexportgerber) # Separator self.menufile.addSeparator() @@ -3137,11 +3145,17 @@ class GerberPreferencesUI(QtWidgets.QWidget): self.gerber_gen_group.setFixedWidth(250) self.gerber_opt_group = GerberOptPrefGroupUI() self.gerber_opt_group.setFixedWidth(230) + self.gerber_exp_group = GerberExpPrefGroupUI() + self.gerber_exp_group.setFixedWidth(230) self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI() self.gerber_adv_opt_group.setFixedWidth(200) + self.vlay = QtWidgets.QVBoxLayout() + self.vlay.addWidget(self.gerber_opt_group) + self.vlay.addWidget(self.gerber_exp_group) + self.layout.addWidget(self.gerber_gen_group) - self.layout.addWidget(self.gerber_opt_group) + self.layout.addLayout(self.vlay) self.layout.addWidget(self.gerber_adv_opt_group) self.layout.addStretch() @@ -4192,6 +4206,100 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): self.layout.addStretch() +class GerberExpPrefGroupUI(OptionsGroupUI): + + def __init__(self, parent=None): + super(GerberExpPrefGroupUI, self).__init__(self) + + self.setTitle(str(_("Gerber Export"))) + + # Plot options + self.export_options_label = QtWidgets.QLabel(_("Export Options:")) + self.export_options_label.setToolTip( + _("The parameters set here are used in the file exported\n" + "when using the File -> Export -> Export Gerber menu entry.") + ) + self.layout.addWidget(self.export_options_label) + + form = QtWidgets.QFormLayout() + self.layout.addLayout(form) + + # Gerber Units + self.gerber_units_label = QtWidgets.QLabel(_('Units:')) + self.gerber_units_label.setToolTip( + _("The units used in the Gerber file.") + ) + + self.gerber_units_radio = RadioSet([{'label': 'INCH', 'value': 'IN'}, + {'label': 'MM', 'value': 'MM'}]) + self.gerber_units_radio.setToolTip( + _("The units used in the Gerber file.") + ) + + form.addRow(self.gerber_units_label, self.gerber_units_radio) + + # Gerber format + self.digits_label = QtWidgets.QLabel(_("Int/Decimals:")) + self.digits_label.setToolTip( + _("The number of digits in the whole part of the number\n" + "and in the fractional part of the number.") + ) + + hlay1 = QtWidgets.QHBoxLayout() + + self.format_whole_entry = IntEntry() + self.format_whole_entry.setMaxLength(1) + self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight) + self.format_whole_entry.setFixedWidth(30) + self.format_whole_entry.setToolTip( + _("This numbers signify the number of digits in\n" + "the whole part of Gerber coordinates.") + ) + hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft) + + gerber_separator_label= QtWidgets.QLabel(':') + gerber_separator_label.setFixedWidth(5) + hlay1.addWidget(gerber_separator_label, QtCore.Qt.AlignLeft) + + self.format_dec_entry = IntEntry() + self.format_dec_entry.setMaxLength(1) + self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight) + self.format_dec_entry.setFixedWidth(30) + self.format_dec_entry.setToolTip( + _("This numbers signify the number of digits in\n" + "the decimal part of Gerber coordinates.") + ) + hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft) + hlay1.addStretch() + + form.addRow(self.digits_label, hlay1) + + # Gerber Zeros + self.zeros_label = QtWidgets.QLabel(_('Zeros:')) + self.zeros_label.setAlignment(QtCore.Qt.AlignLeft) + self.zeros_label.setToolTip( + _("This sets the type of Gerber zeros.\n" + "If LZ then Leading Zeros are removed and\n" + "Trailing Zeros are kept.\n" + "If TZ is checked then Trailing Zeros are removed\n" + "and Leading Zeros are kept.") + ) + + self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'}, + {'label': 'TZ', 'value': 'T'}]) + self.zeros_radio.setToolTip( + _("This sets the type of Gerber zeros.\n" + "If LZ then Leading Zeros are removed and\n" + "Trailing Zeros are kept.\n" + "If TZ is checked then Trailing Zeros are removed\n" + "and Leading Zeros are kept.") + ) + + form.addRow(self.zeros_label, self.zeros_radio) + + self.layout.addStretch() + + class ExcellonGenPrefGroupUI(OptionsGroupUI): def __init__(self, parent=None):