| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224 |
- # ##########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://flatcam.org #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- # ##########################################################
- # ##########################################################
- # File modified by: Marius Stanciu #
- # ##########################################################
- from copy import deepcopy
- from io import StringIO
- from datetime import datetime
- from AppEditors.FlatCAMTextEditor import TextEditor
- from AppObjects.FlatCAMObj import *
- from camlib import CNCjob
- import os
- import sys
- import math
- import gettext
- import AppTranslation as fcTranslate
- import builtins
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- class CNCJobObject(FlatCAMObj, CNCjob):
- """
- Represents G-Code.
- """
- optionChanged = QtCore.pyqtSignal(str)
- ui_type = CNCObjectUI
- def __init__(self, name, units="in", kind="generic", z_move=0.1,
- feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0,
- spindlespeed=None):
- log.debug("Creating CNCJob object...")
- self.decimals = self.app.decimals
- CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
- feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
- spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"]))
- FlatCAMObj.__init__(self, name)
- self.kind = "cncjob"
- self.options.update({
- "plot": True,
- "tooldia": 0.03937, # 0.4mm in inches
- "append": "",
- "prepend": "",
- "dwell": False,
- "dwelltime": 1,
- "type": 'Geometry',
- "toolchange_macro": '',
- "toolchange_macro_enable": False
- })
- '''
- This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the
- diameter of the tools and the value is another dict that will hold the data under the following form:
- {tooldia: {
- 'tooluid': 1,
- 'offset': 'Path',
- 'type_item': 'Rough',
- 'tool_type': 'C1',
- 'data': {} # a dict to hold the parameters
- 'gcode': "" # a string with the actual GCODE
- 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry
- (cut or move)
- 'solid_geometry': []
- },
- ...
- }
- It is populated in the GeometryObject.mtool_gen_cncjob()
- BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
- '''
- self.cnc_tools = {}
- '''
- This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the
- diameter of the tools and the value is another dict that will hold the data under the following form:
- {tooldia: {
- 'tool': int,
- 'nr_drills': int,
- 'nr_slots': int,
- 'offset': float,
- 'data': {} # a dict to hold the parameters
- 'gcode': "" # a string with the actual GCODE
- 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move)
- 'solid_geometry': []
- },
- ...
- }
- It is populated in the ExcellonObject.on_create_cncjob_click() but actually
- it's done in camlib.CNCJob.generate_from_excellon_by_tool()
- BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ...
- '''
- self.exc_cnc_tools = {}
- # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the
- # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects.
- self.special_group = None
- # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
- # (like the one in the TCL Command), False
- self.multitool = False
- # determine if the GCode was generated out of a Excellon object or a Geometry object
- self.origin_kind = None
- # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled
- gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))'
- self.g_x_re = re.compile(gcodex_re_string)
- gcodey_re_string = r'(?=.*(Y[-\+]?\d*\.\d*))'
- self.g_y_re = re.compile(gcodey_re_string)
- gcodez_re_string = r'(?=.*(Z[-\+]?\d*\.\d*))'
- self.g_z_re = re.compile(gcodez_re_string)
- gcodef_re_string = r'(?=.*(F[-\+]?\d*\.\d*))'
- self.g_f_re = re.compile(gcodef_re_string)
- gcodet_re_string = r'(?=.*(\=\s*[-\+]?\d*\.\d*))'
- self.g_t_re = re.compile(gcodet_re_string)
- gcodenr_re_string = r'([+-]?\d*\.\d+)'
- self.g_nr_re = re.compile(gcodenr_re_string)
- # Attributes to be included in serialization
- # Always append to it because it carries contents
- # from predecessors.
- self.ser_attrs += ['options', 'kind', 'origin_kind', 'cnc_tools', 'exc_cnc_tools', 'multitool']
- if self.app.is_legacy is False:
- self.text_col = self.app.plotcanvas.new_text_collection()
- self.text_col.enabled = True
- self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
- self.gcode_editor_tab = None
- self.units_found = self.app.defaults['units']
- def build_ui(self):
- self.ui_disconnect()
- FlatCAMObj.build_ui(self)
- self.units = self.app.defaults['units'].upper()
- # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it
- self.ui.cnc_tools_table.hide()
- if self.cnc_tools:
- self.ui.cnc_tools_table.show()
- self.build_cnc_tools_table()
- self.ui.exc_cnc_tools_table.hide()
- if self.exc_cnc_tools:
- self.ui.exc_cnc_tools_table.show()
- self.build_excellon_cnc_tools()
- #
- self.ui_connect()
- def build_cnc_tools_table(self):
- tool_idx = 0
- n = len(self.cnc_tools)
- self.ui.cnc_tools_table.setRowCount(n)
- for dia_key, dia_value in self.cnc_tools.items():
- tool_idx += 1
- row_no = tool_idx - 1
- t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
- # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
- self.ui.cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id
- # Make sure that the tool diameter when in MM is with no more than 2 decimals.
- # There are no tool bits in MM with more than 2 decimals diameter.
- # For INCH the decimals should be no more than 4. There are no tools under 10mils.
- dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['tooldia'])))
- offset_txt = list(str(dia_value['offset']))
- offset_txt[0] = offset_txt[0].upper()
- offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt))
- type_item = QtWidgets.QTableWidgetItem(str(dia_value['type']))
- tool_type_item = QtWidgets.QTableWidgetItem(str(dia_value['tool_type']))
- t_id.setFlags(QtCore.Qt.ItemIsEnabled)
- dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
- offset_item.setFlags(QtCore.Qt.ItemIsEnabled)
- type_item.setFlags(QtCore.Qt.ItemIsEnabled)
- tool_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
- # hack so the checkbox stay centered in the table cell
- # used this:
- # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
- # plot_item = QtWidgets.QWidget()
- # checkbox = FCCheckBox()
- # checkbox.setCheckState(QtCore.Qt.Checked)
- # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
- # qhboxlayout.addWidget(checkbox)
- # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
- # qhboxlayout.setContentsMargins(0, 0, 0, 0)
- plot_item = FCCheckBox()
- plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
- tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_key))
- if self.ui.plot_cb.isChecked():
- plot_item.setChecked(True)
- self.ui.cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter
- self.ui.cnc_tools_table.setItem(row_no, 2, offset_item) # Offset
- self.ui.cnc_tools_table.setItem(row_no, 3, type_item) # Toolpath Type
- self.ui.cnc_tools_table.setItem(row_no, 4, tool_type_item) # Tool Type
- # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
- self.ui.cnc_tools_table.setItem(row_no, 5, tool_uid_item) # Tool unique ID)
- self.ui.cnc_tools_table.setCellWidget(row_no, 6, plot_item)
- # make the diameter column editable
- # for row in range(tool_idx):
- # self.ui.cnc_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable |
- # QtCore.Qt.ItemIsEnabled)
- for row in range(tool_idx):
- self.ui.cnc_tools_table.item(row, 0).setFlags(
- self.ui.cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
- self.ui.cnc_tools_table.resizeColumnsToContents()
- self.ui.cnc_tools_table.resizeRowsToContents()
- vertical_header = self.ui.cnc_tools_table.verticalHeader()
- # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
- vertical_header.hide()
- self.ui.cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- horizontal_header = self.ui.cnc_tools_table.horizontalHeader()
- horizontal_header.setMinimumSectionSize(10)
- horizontal_header.setDefaultSectionSize(70)
- horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
- horizontal_header.resizeSection(0, 20)
- horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
- horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
- horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
- horizontal_header.resizeSection(4, 40)
- horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
- horizontal_header.resizeSection(4, 17)
- # horizontal_header.setStretchLastSection(True)
- self.ui.cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.ui.cnc_tools_table.setColumnWidth(0, 20)
- self.ui.cnc_tools_table.setColumnWidth(4, 40)
- self.ui.cnc_tools_table.setColumnWidth(6, 17)
- # self.ui.geo_tools_table.setSortingEnabled(True)
- self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight())
- self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight())
- def build_excellon_cnc_tools(self):
- tool_idx = 0
- n = len(self.exc_cnc_tools)
- self.ui.exc_cnc_tools_table.setRowCount(n)
- for tooldia_key, dia_value in self.exc_cnc_tools.items():
- tool_idx += 1
- row_no = tool_idx - 1
- t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
- dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia_key)))
- nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_drills']))
- nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_slots']))
- cutz_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['offset_z']) + self.z_cut))
- t_id.setFlags(QtCore.Qt.ItemIsEnabled)
- dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
- nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled)
- nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled)
- cutz_item.setFlags(QtCore.Qt.ItemIsEnabled)
- # hack so the checkbox stay centered in the table cell
- # used this:
- # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
- # plot_item = QtWidgets.QWidget()
- # checkbox = FCCheckBox()
- # checkbox.setCheckState(QtCore.Qt.Checked)
- # qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
- # qhboxlayout.addWidget(checkbox)
- # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
- # qhboxlayout.setContentsMargins(0, 0, 0, 0)
- plot_item = FCCheckBox()
- plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
- tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool']))
- if self.ui.plot_cb.isChecked():
- plot_item.setChecked(True)
- # TODO until the feature of individual plot for an Excellon tool is implemented
- plot_item.setDisabled(True)
- self.ui.exc_cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id
- self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter
- self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item) # Nr of drills
- self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item) # Nr of slots
- # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
- self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item) # Tool unique ID)
- self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item)
- self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item)
- for row in range(tool_idx):
- self.ui.exc_cnc_tools_table.item(row, 0).setFlags(
- self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
- self.ui.exc_cnc_tools_table.resizeColumnsToContents()
- self.ui.exc_cnc_tools_table.resizeRowsToContents()
- vertical_header = self.ui.exc_cnc_tools_table.verticalHeader()
- vertical_header.hide()
- self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader()
- horizontal_header.setMinimumSectionSize(10)
- horizontal_header.setDefaultSectionSize(70)
- horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
- horizontal_header.resizeSection(0, 20)
- horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
- horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
- horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
- horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents)
- horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
- # horizontal_header.setStretchLastSection(True)
- self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.ui.exc_cnc_tools_table.setColumnWidth(0, 20)
- self.ui.exc_cnc_tools_table.setColumnWidth(6, 17)
- self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight())
- self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight())
- def set_ui(self, ui):
- FlatCAMObj.set_ui(self, ui)
- log.debug("FlatCAMCNCJob.set_ui()")
- assert isinstance(self.ui, CNCObjectUI), \
- "Expected a CNCObjectUI, got %s" % type(self.ui)
- self.units = self.app.defaults['units'].upper()
- self.units_found = self.app.defaults['units']
- # this signal has to be connected to it's slot before the defaults are populated
- # the decision done in the slot has to override the default value set below
- self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
- self.form_fields.update({
- "plot": self.ui.plot_cb,
- "tooldia": self.ui.tooldia_entry,
- "append": self.ui.append_text,
- "prepend": self.ui.prepend_text,
- "toolchange_macro": self.ui.toolchange_text,
- "toolchange_macro_enable": self.ui.toolchange_cb
- })
- # Fill form fields only on object create
- self.to_form()
- # this means that the object that created this CNCJob was an Excellon or Geometry
- try:
- if self.travel_distance:
- self.ui.t_distance_label.show()
- self.ui.t_distance_entry.setVisible(True)
- self.ui.t_distance_entry.setDisabled(True)
- self.ui.t_distance_entry.set_value('%.*f' % (self.decimals, float(self.travel_distance)))
- self.ui.units_label.setText(str(self.units).lower())
- self.ui.units_label.setDisabled(True)
- self.ui.t_time_label.show()
- self.ui.t_time_entry.setVisible(True)
- self.ui.t_time_entry.setDisabled(True)
- # if time is more than 1 then we have minutes, else we have seconds
- if self.routing_time > 1:
- self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(self.routing_time))))
- self.ui.units_time_label.setText('min')
- else:
- time_r = self.routing_time * 60
- self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(time_r))))
- self.ui.units_time_label.setText('sec')
- self.ui.units_time_label.setDisabled(True)
- except AttributeError:
- pass
- if self.multitool is False:
- self.ui.tooldia_entry.show()
- self.ui.updateplot_button.show()
- else:
- self.ui.tooldia_entry.hide()
- self.ui.updateplot_button.hide()
- # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
- self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
- try:
- self.ui.annotation_cb.stateChanged.disconnect(self.on_annotation_change)
- except (TypeError, AttributeError):
- pass
- self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
- # set if to display text annotations
- self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
- # Show/Hide Advanced Options
- if self.app.defaults["global_app_level"] == 'b':
- self.ui.level.setText(_(
- '<span style="color:green;"><b>Basic</b></span>'
- ))
- self.ui.cnc_frame.hide()
- else:
- self.ui.level.setText(_(
- '<span style="color:red;"><b>Advanced</b></span>'
- ))
- self.ui.cnc_frame.show()
- self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
- self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
- self.ui.modify_gcode_button.clicked.connect(self.on_edit_code_click)
- self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters)
- self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change)
- def on_cnc_custom_parameters(self, signal_text):
- if signal_text == 'Parameters':
- return
- else:
- self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text)
- def ui_connect(self):
- for row in range(self.ui.cnc_tools_table.rowCount()):
- self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table)
- self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
- def ui_disconnect(self):
- for row in range(self.ui.cnc_tools_table.rowCount()):
- self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table)
- try:
- self.ui.plot_cb.stateChanged.disconnect(self.on_plot_cb_click)
- except (TypeError, AttributeError):
- pass
- def on_updateplot_button_click(self, *args):
- """
- Callback for the "Updata Plot" button. Reads the form for updates
- and plots the object.
- """
- self.read_form()
- self.on_plot_kind_change()
- def on_plot_kind_change(self):
- kind = self.ui.cncplot_method_combo.get_value()
- def worker_task():
- with self.app.proc_container.new(_("Plotting...")):
- self.plot(kind=kind)
- self.app.worker_task.emit({'fcn': worker_task, 'params': []})
- def on_exportgcode_button_click(self, *args):
- """
- Handler activated by a button clicked when exporting GCode.
- :param args:
- :return:
- """
- self.app.defaults.report_usage("cncjob_on_exportgcode_button")
- self.read_form()
- name = self.app.collection.get_active().options['name']
- save_gcode = False
- if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name:
- _filter_ = "RML1 Files .rol (*.rol);;All Files (*.*)"
- elif 'hpgl' in self.pp_geometry_name:
- _filter_ = "HPGL Files .plt (*.plt);;All Files (*.*)"
- else:
- save_gcode = True
- _filter_ = self.app.defaults['cncjob_save_filters']
- try:
- dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
- filename, _f = FCFileSaveDialog.get_saved_filename(
- caption=_("Export Machine Code ..."),
- directory=dir_file_to_save,
- ext_filter=_filter_
- )
- except TypeError:
- filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), ext_filter=_filter_)
- filename = str(filename)
- if filename == '':
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ..."))
- return
- else:
- if save_gcode is True:
- used_extension = filename.rpartition('.')[2]
- self.update_filters(last_ext=used_extension, filter_string='cncjob_save_filters')
- new_name = os.path.split(str(filename))[1].rpartition('.')[0]
- self.ui.name_entry.set_value(new_name)
- self.on_name_activate(silent=True)
- preamble = str(self.ui.prepend_text.get_value())
- postamble = str(self.ui.append_text.get_value())
- gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
- if gc == 'fail':
- return
- if self.app.defaults["global_open_style"] is False:
- self.app.file_opened.emit("gcode", filename)
- self.app.file_saved.emit("gcode", filename)
- self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename))
- def on_edit_code_click(self, *args):
- """
- Handler activated by a button clicked when editing GCode.
- :param args:
- :return:
- """
- self.app.proc_container.view.set_busy(_("Loading..."))
- preamble = str(self.ui.prepend_text.get_value())
- postamble = str(self.ui.append_text.get_value())
- gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
- if gco == 'fail':
- return
- else:
- self.app.gcode_edited = gco
- self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True)
- # add the tab if it was closed
- self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor"))
- self.gcode_editor_tab.setObjectName('code_editor_tab')
- # delete the absolute and relative position and messages in the infobar
- self.app.ui.position_label.setText("")
- # self.app.ui.rel_position_label.setText("")
- # first clear previous text in text editor (if any)
- self.gcode_editor_tab.code_editor.clear()
- self.gcode_editor_tab.code_editor.setReadOnly(False)
- self.gcode_editor_tab.code_editor.completer_enable = False
- self.gcode_editor_tab.buttonRun.hide()
- # Switch plot_area to CNCJob tab
- self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
- self.gcode_editor_tab.t_frame.hide()
- # then append the text from GCode to the text editor
- try:
- self.gcode_editor_tab.code_editor.setPlainText(self.app.gcode_edited.getvalue())
- # for line in self.app.gcode_edited:
- # QtWidgets.QApplication.processEvents()
- #
- # proc_line = str(line).strip('\n')
- # self.gcode_editor_tab.code_editor.append(proc_line)
- except Exception as e:
- log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e))
- self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e)))
- return
- self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
- self.gcode_editor_tab.handleTextChanged()
- self.gcode_editor_tab.t_frame.show()
- self.app.proc_container.view.set_idle()
- self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
- def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None):
- """
- Will create a header to be added to all GCode files generated by FlatCAM
- :param comment_start_symbol: A symbol to be used as the first symbol in a comment
- :param comment_stop_symbol: A symbol to be used as the last symbol in a comment
- :return: A string with a GCode header
- """
- log.debug("FlatCAMCNCJob.gcode_header()")
- time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
- marlin = False
- hpgl = False
- probe_pp = False
- start_comment = comment_start_symbol if comment_start_symbol is not None else '('
- stop_comment = comment_stop_symbol if comment_stop_symbol is not None else ')'
- try:
- for key in self.cnc_tools:
- ppg = self.cnc_tools[key]['data']['ppname_g']
- if 'marlin' in ppg.lower() or 'repetier' in ppg.lower():
- marlin = True
- break
- if ppg == 'hpgl':
- hpgl = True
- break
- if "toolchange_probe" in ppg.lower():
- probe_pp = True
- break
- except KeyError:
- # log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e))
- pass
- try:
- if 'marlin' in self.options['ppname_e'].lower() or 'repetier' in self.options['ppname_e'].lower():
- marlin = True
- except KeyError:
- # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
- pass
- try:
- if "toolchange_probe" in self.options['ppname_e'].lower():
- probe_pp = True
- except KeyError:
- # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
- pass
- if marlin is True:
- gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \
- (str(self.app.version), str(self.app.version_date)) + '\n'
- gcode += ';Name: ' + str(self.options['name']) + '\n'
- gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n'
- # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
- # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
- gcode += ';Units: ' + self.units.upper() + '\n' + "\n"
- gcode += ';Created on ' + time_str + '\n' + '\n'
- elif hpgl is True:
- gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s' % \
- (str(self.app.version), str(self.app.version_date)) + '";\n'
- gcode += 'CO "Name: ' + str(self.options['name']) + '";\n'
- gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n'
- # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
- # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
- gcode += 'CO "Units: ' + self.units.upper() + '";\n'
- gcode += 'CO "Created on ' + time_str + '";\n'
- elif probe_pp is True:
- gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
- (str(self.app.version), str(self.app.version_date)) + '\n'
- gcode += '(This GCode tool change is done by using a Probe.)\n' \
- '(Make sure that before you start the job you first do a rough zero for Z axis.)\n' \
- '(This means that you need to zero the CNC axis and then jog to the toolchange X, Y location,)\n' \
- '(mount the probe and adjust the Z so more or less the probe tip touch the plate. ' \
- 'Then zero the Z axis.)\n' + '\n'
- gcode += '(Name: ' + str(self.options['name']) + ')\n'
- gcode += '(Type: ' + "G-code from " + str(self.options['type']) + ')\n'
- # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
- # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
- gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
- gcode += '(Created on ' + time_str + ')\n' + '\n'
- else:
- gcode = '%sG-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s%s\n' % \
- (start_comment, str(self.app.version), str(self.app.version_date), stop_comment) + '\n'
- gcode += '%sName: ' % start_comment + str(self.options['name']) + '%s\n' % stop_comment
- gcode += '%sType: ' % start_comment + "G-code from " + str(self.options['type']) + '%s\n' % stop_comment
- # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
- # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
- gcode += '%sUnits: ' % start_comment + self.units.upper() + '%s\n' % stop_comment + "\n"
- gcode += '%sCreated on ' % start_comment + time_str + '%s\n' % stop_comment + '\n'
- return gcode
- @staticmethod
- def gcode_footer(end_command=None):
- """
- Will add the M02 to the end of GCode, if requested.
- :param end_command: 'M02' or 'M30' - String
- :return:
- """
- if end_command:
- return end_command
- else:
- return 'M02'
- def export_gcode(self, filename=None, preamble='', postamble='', to_file=False, from_tcl=False):
- """
- This will save the GCode from the Gcode object to a file on the OS filesystem
- :param filename: filename for the GCode file
- :param preamble: a custom Gcode block to be added at the beginning of the Gcode file
- :param postamble: a custom Gcode block to be added at the end of the Gcode file
- :param to_file: if False then no actual file is saved but the app will know that a file was created
- :param from_tcl: True if run from Tcl Shell
- :return: None
- """
- # gcode = ''
- # roland = False
- # hpgl = False
- # isel_icp = False
- include_header = True
- try:
- if self.special_group:
- self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' %
- (_("This CNCJob object can't be processed because it is a"),
- str(self.special_group),
- _("CNCJob object")))
- return 'fail'
- except AttributeError:
- pass
- # if this dict is not empty then the object is a Geometry object
- if self.cnc_tools:
- first_key = next(iter(self.cnc_tools))
- include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header
- # if this dict is not empty then the object is an Excellon object
- if self.exc_cnc_tools:
- first_key = next(iter(self.exc_cnc_tools))
- include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header
- # # detect if using Roland preprocessor
- # try:
- # for key in self.cnc_tools:
- # if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20':
- # roland = True
- # break
- # except Exception:
- # try:
- # for key in self.cnc_tools:
- # if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20':
- # roland = True
- # break
- # except Exception:
- # pass
- #
- # # detect if using HPGL preprocessor
- # try:
- # for key in self.cnc_tools:
- # if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
- # hpgl = True
- # break
- # except Exception:
- # try:
- # for key in self.cnc_tools:
- # if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl':
- # hpgl = True
- # break
- # except Exception:
- # pass
- #
- # # detect if using ISEL_ICP_CNC preprocessor
- # try:
- # for key in self.cnc_tools:
- # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper():
- # isel_icp = True
- # break
- # except Exception:
- # try:
- # for key in self.cnc_tools:
- # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper():
- # isel_icp = True
- # break
- # except Exception:
- # pass
- # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor
- # if roland is False and hpgl is False and isel_icp is False:
- # gcode = self.gcode_header()
- # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor
- # that has the include_header attribute set as False, add it for every other preprocessor
- # if include_header:
- # gcode = self.gcode_header()
- # else:
- # gcode = ''
- # # detect if using multi-tool and make the Gcode summation correctly for each case
- # if self.multitool is True:
- # for tooluid_key in self.cnc_tools:
- # for key, value in self.cnc_tools[tooluid_key].items():
- # if key == 'gcode':
- # gcode += value
- # break
- # else:
- # gcode += self.gcode
- # if roland is True:
- # g = preamble + gcode + postamble
- # elif hpgl is True:
- # g = self.gcode_header() + preamble + gcode + postamble
- # else:
- # # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
- # g_idx = gcode.rfind('G20')
- #
- # # if it did not find 'G20' then search for 'G21'
- # if g_idx == -1:
- # g_idx = gcode.rfind('G21')
- #
- # # if it did not find 'G20' and it did not find 'G21' then there is an error and return
- # # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command
- # if g_idx == -1 and isel_icp is False:
- # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
- # return
- #
- # footer = self.app.defaults['cncjob_footer']
- # end_gcode = self.gcode_footer() if footer is True else ''
- # g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode
- gcode = ''
- if include_header is False:
- g = preamble
- # detect if using multi-tool and make the Gcode summation correctly for each case
- if self.multitool is True:
- for tooluid_key in self.cnc_tools:
- for key, value in self.cnc_tools[tooluid_key].items():
- if key == 'gcode':
- gcode += value
- break
- else:
- gcode += self.gcode
- g = g + gcode + postamble
- else:
- # search for the GCode beginning which is usually a G20 or G21
- # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
- # g_idx = gcode.rfind('G20')
- #
- # # if it did not find 'G20' then search for 'G21'
- # if g_idx == -1:
- # g_idx = gcode.rfind('G21')
- #
- # # if it did not find 'G20' and it did not find 'G21' then there is an error and return
- # if g_idx == -1:
- # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
- # return
- # detect if using multi-tool and make the Gcode summation correctly for each case
- if self.multitool is True:
- for tooluid_key in self.cnc_tools:
- for key, value in self.cnc_tools[tooluid_key].items():
- if key == 'gcode':
- gcode += value
- break
- else:
- gcode += self.gcode
- end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else ''
- # detect if using a HPGL preprocessor
- hpgl = False
- if self.cnc_tools:
- for key in self.cnc_tools:
- if 'ppname_g' in self.cnc_tools[key]['data']:
- if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']:
- hpgl = True
- break
- elif self.exc_cnc_tools:
- for key in self.cnc_tools:
- if 'ppname_e' in self.cnc_tools[key]['data']:
- if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']:
- hpgl = True
- break
- if hpgl:
- processed_gcode = ''
- pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
- for gline in gcode.splitlines():
- match = pa_re.search(gline)
- if match:
- x_int = int(float(match.group(1)))
- y_int = int(float(match.group(2)))
- new_line = 'PA%d,%d;\n' % (x_int, y_int)
- processed_gcode += new_line
- else:
- processed_gcode += gline + '\n'
- gcode = processed_gcode
- g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode
- else:
- try:
- g_idx = gcode.index('G94')
- g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \
- gcode[(g_idx + 3):] + postamble + end_gcode
- except ValueError:
- self.app.inform.emit('[ERROR_NOTCL] %s' %
- _("G-code does not have a G94 code and we will not include the code in the "
- "'Prepend to GCode' text box"))
- g = self.gcode_header() + '\n' + gcode + postamble + end_gcode
- # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
- if self.ui.toolchange_cb.get_value() is True:
- # match = self.re_toolchange.search(g)
- if 'M6' in g:
- m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value())
- if m6_code is None or m6_code == '':
- self.app.inform.emit(
- '[ERROR_NOTCL] %s' % _("Cancelled. The Toolchange Custom code is enabled but it's empty.")
- )
- return 'fail'
- g = g.replace('M6', m6_code)
- self.app.inform.emit('[success] %s' % _("Toolchange G-code was replaced by a custom code."))
- lines = StringIO(g)
- # Write
- if filename is not None:
- try:
- force_windows_line_endings = self.app.defaults['cncjob_line_ending']
- if force_windows_line_endings and sys.platform != 'win32':
- with open(filename, 'w', newline='\r\n') as f:
- for line in lines:
- f.write(line)
- else:
- with open(filename, 'w') as f:
- for line in lines:
- f.write(line)
- except FileNotFoundError:
- self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory"))
- return
- except PermissionError:
- self.app.inform.emit(
- '[WARNING] %s' % _("Permission denied, saving not possible.\n"
- "Most likely another app is holding the file open and not accessible.")
- )
- return 'fail'
- elif to_file is False:
- # Just for adding it to the recent files list.
- if self.app.defaults["global_open_style"] is False:
- self.app.file_opened.emit("cncjob", filename)
- self.app.file_saved.emit("cncjob", filename)
- self.app.inform.emit('[success] %s: %s' % (_("Saved to"), filename))
- else:
- return lines
- def on_toolchange_custom_clicked(self, signal):
- """
- Handler for clicking toolchange custom.
- :param signal:
- :return:
- """
- try:
- if 'toolchange_custom' not in str(self.options['ppname_e']).lower():
- if self.ui.toolchange_cb.get_value():
- self.ui.toolchange_cb.set_value(False)
- self.app.inform.emit('[WARNING_NOTCL] %s' %
- _("The used preprocessor file has to have in it's name: 'toolchange_custom'"))
- except KeyError:
- try:
- for key in self.cnc_tools:
- ppg = self.cnc_tools[key]['data']['ppname_g']
- if 'toolchange_custom' not in str(ppg).lower():
- if self.ui.toolchange_cb.get_value():
- self.ui.toolchange_cb.set_value(False)
- self.app.inform.emit('[WARNING_NOTCL] %s' %
- _("The used preprocessor file has to have in it's name: "
- "'toolchange_custom'"))
- except KeyError:
- self.app.inform.emit('[ERROR] %s' % _("There is no preprocessor file."))
- def get_gcode(self, preamble='', postamble=''):
- """
- We need this to be able to get_gcode separately for shell command export_gcode
- :param preamble: Extra GCode added to the beginning of the GCode
- :param postamble: Extra GCode added at the end of the GCode
- :return: The modified GCode
- """
- return preamble + '\n' + self.gcode + "\n" + postamble
- def get_svg(self):
- # we need this to be able get_svg separately for shell command export_svg
- pass
- def on_plot_cb_click(self, *args):
- """
- Handler for clicking on the Plot checkbox.
- :param args:
- :return:
- """
- if self.muted_ui:
- return
- kind = self.ui.cncplot_method_combo.get_value()
- self.plot(kind=kind)
- self.read_form_item('plot')
- self.ui_disconnect()
- cb_flag = self.ui.plot_cb.isChecked()
- for row in range(self.ui.cnc_tools_table.rowCount()):
- table_cb = self.ui.cnc_tools_table.cellWidget(row, 6)
- if cb_flag:
- table_cb.setChecked(True)
- else:
- table_cb.setChecked(False)
- self.ui_connect()
- def on_plot_cb_click_table(self):
- """
- Handler for clicking the plot checkboxes added into a Table on each row. Purpose: toggle visibility for the
- tool/aperture found on that row.
- :return:
- """
- # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked)
- self.ui_disconnect()
- # cw = self.sender()
- # cw_index = self.ui.cnc_tools_table.indexAt(cw.pos())
- # cw_row = cw_index.row()
- kind = self.ui.cncplot_method_combo.get_value()
- self.shapes.clear(update=True)
- for tooluid_key in self.cnc_tools:
- tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
- gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
- # tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text())
- for r in range(self.ui.cnc_tools_table.rowCount()):
- if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key):
- if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked():
- self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed, kind=kind)
- self.shapes.redraw()
- # make sure that the general plot is disabled if one of the row plot's are disabled and
- # if all the row plot's are enabled also enable the general plot checkbox
- cb_cnt = 0
- total_row = self.ui.cnc_tools_table.rowCount()
- for row in range(total_row):
- if self.ui.cnc_tools_table.cellWidget(row, 6).isChecked():
- cb_cnt += 1
- else:
- cb_cnt -= 1
- if cb_cnt < total_row:
- self.ui.plot_cb.setChecked(False)
- else:
- self.ui.plot_cb.setChecked(True)
- self.ui_connect()
- def plot(self, visible=None, kind='all'):
- """
- # Does all the required setup and returns False
- # if the 'ptint' option is set to False.
- :param visible: Boolean to decide if the object will be plotted as visible or disabled on canvas
- :param kind: String. Can be "all" or "travel" or "cut". For CNCJob plotting
- :return: None
- """
- if not FlatCAMObj.plot(self):
- return
- visible = visible if visible else self.options['plot']
- if self.app.is_legacy is False:
- if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
- self.text_col.enabled = True
- else:
- self.text_col.enabled = False
- self.annotation.redraw()
- try:
- if self.multitool is False: # single tool usage
- try:
- dia_plot = float(self.options["tooldia"])
- except ValueError:
- # we may have a tuple with only one element and a comma
- dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
- self.plot2(tooldia=dia_plot, obj=self, visible=visible, kind=kind)
- else:
- # multiple tools usage
- if self.cnc_tools:
- for tooluid_key in self.cnc_tools:
- tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
- gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
- self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
- # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
- # I do this so the travel lines thickness will reflect the tool diameter
- # may work only for objects created within the app and not Gcode imported from elsewhere for which we
- # don't know the origin
- if self.origin_kind == "excellon":
- if self.exc_cnc_tools:
- for tooldia_key in self.exc_cnc_tools:
- tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
- # gcode_parsed = self.exc_cnc_tools[tooldia_key]['gcode_parsed']
- gcode_parsed = self.gcode_parsed
- self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
- self.shapes.redraw()
- except (ObjectDeleted, AttributeError):
- self.shapes.clear(update=True)
- if self.app.is_legacy is False:
- self.annotation.clear(update=True)
- def on_annotation_change(self):
- """
- Handler for toggling the annotation display by clicking a checkbox.
- :return:
- """
- if self.app.is_legacy is False:
- if self.ui.annotation_cb.get_value():
- self.text_col.enabled = True
- else:
- self.text_col.enabled = False
- # kind = self.ui.cncplot_method_combo.get_value()
- # self.plot(kind=kind)
- self.annotation.redraw()
- else:
- kind = self.ui.cncplot_method_combo.get_value()
- self.plot(kind=kind)
- def convert_units(self, units):
- """
- Units conversion used by the CNCJob objects.
- :param units: Can be "MM" or "IN"
- :return:
- """
- log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")
- factor = CNCjob.convert_units(self, units)
- self.options["tooldia"] = float(self.options["tooldia"]) * factor
- param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
- 'endz', 'toolchangez']
- temp_tools_dict = {}
- tool_dia_copy = {}
- data_copy = {}
- for tooluid_key, tooluid_value in self.cnc_tools.items():
- for dia_key, dia_value in tooluid_value.items():
- if dia_key == 'tooldia':
- dia_value *= factor
- dia_value = float('%.*f' % (self.decimals, dia_value))
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'offset':
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'offset_value':
- dia_value *= factor
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'type':
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'tool_type':
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'data':
- for data_key, data_value in dia_value.items():
- # convert the form fields that are convertible
- for param in param_list:
- if data_key == param and data_value is not None:
- data_copy[data_key] = data_value * factor
- # copy the other dict entries that are not convertible
- if data_key not in param_list:
- data_copy[data_key] = data_value
- tool_dia_copy[dia_key] = deepcopy(data_copy)
- data_copy.clear()
- if dia_key == 'gcode':
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'gcode_parsed':
- tool_dia_copy[dia_key] = dia_value
- if dia_key == 'solid_geometry':
- tool_dia_copy[dia_key] = dia_value
- # if dia_key == 'solid_geometry':
- # tool_dia_copy[dia_key] = affinity.scale(dia_value, xfact=factor, origin=(0, 0))
- # if dia_key == 'gcode_parsed':
- # for g in dia_value:
- # g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0))
- #
- # tool_dia_copy['gcode_parsed'] = deepcopy(dia_value)
- # tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value])
- temp_tools_dict.update({
- tooluid_key: deepcopy(tool_dia_copy)
- })
- tool_dia_copy.clear()
- self.cnc_tools.clear()
- self.cnc_tools = deepcopy(temp_tools_dict)
|