Merged in test_beta8.913 (pull request #142)

Test beta8.913
This commit is contained in:
Marius Stanciu 2019-04-13 14:29:07 +00:00
commit 9d508a6840
48 changed files with 33163 additions and 12509 deletions

File diff suppressed because it is too large Load Diff

View File

@ -178,7 +178,7 @@ class FlatCAMObj(QtCore.QObject):
self.muted_ui = False
def on_name_activate(self):
old_name = copy.copy(self.options["name"])
old_name = copy(self.options["name"])
new_name = self.ui.name_entry.get_value()
# update the SHELL auto-completer model data
@ -186,11 +186,12 @@ class FlatCAMObj(QtCore.QObject):
self.app.myKeywords.remove(old_name)
self.app.myKeywords.append(new_name)
self.app.shell._edit.set_model_data(self.app.myKeywords)
self.app.ui.code_editor.set_model_data(self.app.myKeywords)
except:
log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list")
self.options["name"] = self.ui.name_entry.get_value()
self.app.inform.emit(_("[success]Name changed from {old} to {new}").format(old=old_name, new=new_name))
self.app.inform.emit(_("[success] Name changed from {old} to {new}").format(old=old_name, new=new_name))
def on_offset_button_click(self):
self.app.report_usage("obj_on_offset_button")
@ -410,18 +411,27 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
except:
log.warning("Failed to copy option.", option)
for geos in grb.solid_geometry:
grb_final.solid_geometry.append(geos)
grb_final.follow_geometry.append(geos)
try:
for geos in grb.solid_geometry:
grb_final.solid_geometry.append(geos)
grb_final.follow_geometry.append(geos)
except TypeError:
grb_final.solid_geometry.append(grb.solid_geometry)
grb_final.follow_geometry.append(grb.solid_geometry)
for ap in grb.apertures:
if ap not in grb_final.apertures:
grb_final.apertures[ap] = grb.apertures[ap]
else:
if 'solid_geometry' not in grb_final.apertures[ap]:
grb_final.apertures[ap]['solid_geometry'] = []
for geo in grb.apertures[ap]['solid_geometry']:
grb_final.apertures[ap]['solid_geometry'].append(geo)
# create a list of integers out of the grb.apertures keys and find the max of that value
# then, the aperture duplicate is assigned an id value incremented with 1,
# and finally made string because the apertures dict keys are strings
max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
grb_final.apertures[max_ap] = {}
grb_final.apertures[max_ap]['solid_geometry'] = []
for k, v in grb.apertures[ap].items():
grb_final.apertures[max_ap][k] = v
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
@ -448,8 +458,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"bboxmargin": 0.0,
"bboxrounded": False,
"aperture_display": False,
"aperture_scale_factor": 1.0,
"aperture_buffer_factor": 0.0,
"follow": False
})
@ -501,8 +509,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb,
"aperture_display": self.ui.aperture_table_visibility_cb,
"aperture_scale_factor": self.ui.scale_aperture_entry,
"aperture_buffer_factor": self.ui.buffer_aperture_entry,
"follow": self.ui.follow_cb
})
@ -522,9 +528,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
self.ui.scale_aperture_button.clicked.connect(self.on_scale_aperture_click)
self.ui.buffer_aperture_button.clicked.connect(self.on_buffer_aperture_click)
self.ui.new_grb_button.clicked.connect(self.on_new_modified_gerber)
# Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b':
@ -896,7 +899,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
for g in geo_obj.solid_geometry:
if g:
app_obj.inform.emit(_(
"[success]Isolation geometry created: %s"
"[success] Isolation geometry created: %s"
) % geo_obj.options["name"])
break
else:
@ -951,7 +954,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
for g in geo_obj.solid_geometry:
if g:
app_obj.inform.emit(_(
"[success]Isolation geometry created: %s"
"[success] Isolation geometry created: %s"
) % geo_obj.options["name"])
break
else:
@ -989,30 +992,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def on_aperture_table_visibility_change(self):
if self.ui.aperture_table_visibility_cb.isChecked():
self.ui.apertures_table.setVisible(True)
self.ui.scale_aperture_label.setVisible(True)
self.ui.scale_aperture_entry.setVisible(True)
self.ui.scale_aperture_button.setVisible(True)
self.ui.buffer_aperture_label.setVisible(True)
self.ui.buffer_aperture_entry.setVisible(True)
self.ui.buffer_aperture_button.setVisible(True)
self.ui.new_grb_label.setVisible(True)
self.ui.new_grb_button.setVisible(True)
self.ui.mark_all_cb.setVisible(True)
self.ui.mark_all_cb.setChecked(False)
else:
self.ui.apertures_table.setVisible(False)
self.ui.scale_aperture_label.setVisible(False)
self.ui.scale_aperture_entry.setVisible(False)
self.ui.scale_aperture_button.setVisible(False)
self.ui.buffer_aperture_label.setVisible(False)
self.ui.buffer_aperture_entry.setVisible(False)
self.ui.buffer_aperture_button.setVisible(False)
self.ui.new_grb_label.setVisible(False)
self.ui.new_grb_button.setVisible(False)
self.ui.mark_all_cb.setVisible(False)
# on hide disable all mark plots
@ -1020,136 +1005,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.apertures_table.cellWidget(row, 5).set_value(False)
self.clear_plot_apertures()
def on_scale_aperture_click(self, signal):
try:
factor = self.ui.scale_aperture_entry.get_value()
except Exception as e:
log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
self.app.inform.emit(_(
"[ERROR_NOTCL] The aperture scale factor value is missing or wrong format."
))
return
def scale_recursion(geom):
if type(geom) == list or type(geom) is MultiPolygon:
geoms=list()
for local_geom in geom:
geoms.append(scale_recursion(local_geom))
return geoms
else:
return affinity.scale(geom, factor, factor, origin='center')
if not self.ui.apertures_table.selectedItems():
self.app.inform.emit(_(
"[WARNING_NOTCL] No aperture to scale. Select at least one aperture and try again."
))
return
for x in self.ui.apertures_table.selectedItems():
try:
apid = self.ui.apertures_table.item(x.row(), 1).text()
except Exception as e:
log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
self.apertures[apid]['solid_geometry'] = scale_recursion(self.apertures[apid]['solid_geometry'])
self.on_mark_cb_click_table()
def on_buffer_aperture_click(self, signal):
try:
buff_value = self.ui.buffer_aperture_entry.get_value()
except Exception as e:
log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
self.app.inform.emit(_(
"[ERROR_NOTCL] The aperture buffer value is missing or wrong format."
))
return
def buffer_recursion(geom):
if type(geom) == list or type(geom) is MultiPolygon:
geoms=list()
for local_geom in geom:
geoms.append(buffer_recursion(local_geom))
return geoms
else:
return geom.buffer(buff_value, join_style=2)
if not self.ui.apertures_table.selectedItems():
self.app.inform.emit(_(
"[WARNING_NOTCL] No aperture to scale. Select at least one aperture and try again."
))
return
for x in self.ui.apertures_table.selectedItems():
try:
apid = self.ui.apertures_table.item(x.row(), 1).text()
except Exception as e:
log.debug("FlatCAMGerber.on_scale_aperture_click() --> %s" % str(e))
self.apertures[apid]['solid_geometry'] = buffer_recursion(self.apertures[apid]['solid_geometry'])
self.on_mark_cb_click_table()
def on_new_modified_gerber(self, signal):
name = '%s_ap_mod' % str(self.options['name'])
apertures = deepcopy(self.apertures)
options = self.options
# geometry storage
poly_buff = []
# How the object should be initialized
def obj_init(gerber_obj, app_obj):
assert isinstance(gerber_obj, FlatCAMGerber), \
"Expected to initialize a FlatCAMGerber but got %s" % type(gerber_obj)
gerber_obj.source_file = self.source_file
gerber_obj.multigeo = False
gerber_obj.follow = False
gerber_obj.apertures = apertures
for option in options:
# we don't want to overwrite the new name and we don't want to share the 'plot' state
# because the new object should ve visible even if the source is not visible
if option != 'name' and option != 'plot':
gerber_obj.options[option] = options[option]
# regenerate solid_geometry
app_obj.log.debug("Creating new Gerber object. Joining %s polygons.")
# for ap in apertures:
# for geo in apertures[ap]['solid_geometry']:
# poly_buff.append(geo)
poly_buff = [geo for ap in apertures for geo in apertures[ap]['solid_geometry']]
# buffering the poly_buff
new_geo = MultiPolygon(poly_buff)
new_geo = new_geo.buffer(0.0000001)
new_geo = new_geo.buffer(-0.0000001)
gerber_obj.solid_geometry = new_geo
app_obj.log.debug("Finished creation of a new Gerber object. Polygons joined.")
log.debug("on_new_modified_gerber()")
with self.app.proc_container.new(_("Generating Gerber")) as proc:
self.app.progress.emit(10)
### Object creation ###
ret = self.app.new_object("gerber", name, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_(
'[ERROR_NOTCL] Cretion of Gerber failed.'
))
return
self.app.progress.emit(100)
# GUI feedback
self.app.inform.emit(_("[success] Created: %s") % name)
def convert_units(self, units):
"""
Converts the units of the object by scaling dimensions in all geometry
@ -1317,7 +1172,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
aperture = self.ui.apertures_table.item(row, 1).text()
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
else:
self.marked_rows.append(False)
@ -1354,7 +1209,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if mark_all:
for aperture in self.apertures:
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
else:
self.clear_plot_apertures()
@ -1955,7 +1810,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
self.ui.tools_table.currentItem().text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, use a number."
"[ERROR_NOTCL] Wrong value format entered, use a number."
))
self.ui.tools_table.currentItem().setText(str(self.tool_offset[dia]))
return
@ -2179,7 +2034,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
if len(tools) == 0:
self.app.inform.emit(_(
"[ERROR_NOTCL]Please select one or more tools from the list and try again."
"[ERROR_NOTCL] Please select one or more tools from the list and try again."
))
return False, "Error: No tools."
@ -2270,7 +2125,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
if len(tools) == 0:
self.app.inform.emit(_(
"[ERROR_NOTCL]Please select one or more tools from the list and try again."
"[ERROR_NOTCL] Please select one or more tools from the list and try again."
))
return False, "Error: No tools."
@ -2385,7 +2240,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
tools.append(self.ui.tools_table.item(0, 0).text())
else:
self.app.inform.emit(_(
"[ERROR_NOTCL]Please select one or more tools from the list and try again."
"[ERROR_NOTCL] Please select one or more tools from the list and try again."
))
return
@ -2409,6 +2264,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
### Add properties to the object
job_obj.origin_kind = 'excellon'
job_obj.options['Tools_in_use'] = tool_table_items
job_obj.options['type'] = 'Excellon'
job_obj.options['ppname_e'] = pp_excellon_name
@ -2443,7 +2300,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
'[ERROR_NOTCL] Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
))
try:
@ -2455,7 +2312,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
'[ERROR_NOTCL] Wrong value format for self.defaults["feedrate_probe"] '
'or self.options["feedrate_probe"]'
)
)
@ -2608,7 +2465,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# except TypeError: # Element is not iterable...
# self.add_shape(shape=element, color=color, visible=visible, layer=0)
def plot(self):
def plot(self, kind=None):
# Does all the required setup and returns False
# if the 'ptint' option is set to False.
@ -3218,7 +3075,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
)
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
)
)
@ -3439,7 +3296,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else:
change_message = False
self.app.inform.emit(_(
"[ERROR_NOTCL]Default Tool added. Wrong value format entered."
"[ERROR_NOTCL] Default Tool added. Wrong value format entered."
))
self.build_ui()
@ -3469,7 +3326,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.tools[int(max_uid)] = deepcopy(self.tools[tooluid_copy])
except AttributeError:
self.app.inform.emit(_(
"[WARNING_NOTCL]Failed. Select a tool to copy."
"[WARNING_NOTCL] Failed. Select a tool to copy."
))
self.build_ui()
return
@ -3479,7 +3336,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# self.ui.geo_tools_table.clearSelection()
else:
self.app.inform.emit(_(
"[WARNING_NOTCL]Failed. Select a tool to copy."
"[WARNING_NOTCL] Failed. Select a tool to copy."
))
self.build_ui()
return
@ -3524,7 +3381,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
d = float(self.ui.geo_tools_table.item(current_row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -3572,7 +3429,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
temp_tools.clear()
except AttributeError:
self.app.inform.emit(_(
"[WARNING_NOTCL]Failed. Select a tool to delete."
"[WARNING_NOTCL] Failed. Select a tool to delete."
))
self.build_ui()
return
@ -3582,7 +3439,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# self.ui.geo_tools_table.clearSelection()
else:
self.app.inform.emit(_(
"[WARNING_NOTCL]Failed. Select a tool to delete."
"[WARNING_NOTCL] Failed. Select a tool to delete."
))
self.build_ui()
return
@ -3711,7 +3568,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
vdia = float(self.ui.tipdia_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -3724,7 +3581,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
half_vangle = float(self.ui.tipangle_entry.get_value().replace(',', '.')) / 2
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -3841,7 +3698,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
)
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -4020,7 +3877,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
try:
if self.special_group:
self.app.inform.emit(_(
"[WARNING_NOTCL]This Geometry can't be processed because it is %s geometry."
"[WARNING_NOTCL] This Geometry can't be processed because it is %s geometry."
) % str(self.special_group))
return
except AttributeError:
@ -4037,7 +3894,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong Tool Dia value format entered, "
"[ERROR_NOTCL] Wrong Tool Dia value format entered, "
"use a number."
))
return
@ -4137,7 +3994,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
'[ERROR_NOTCL] Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
))
try:
@ -4149,7 +4006,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
'[ERROR_NOTCL] Wrong value format for self.defaults["feedrate_probe"] '
'or self.options["feedrate_probe"]'
))
@ -4249,7 +4106,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
)
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -4348,7 +4205,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
'[ERROR_NOTCL] Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
))
try:
@ -4360,7 +4217,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
'[ERROR_NOTCL] Wrong value format for self.defaults["feedrate_probe"] '
'or self.options["feedrate_probe"]'
))
@ -4372,7 +4229,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
a += 1
if a == len(self.tools):
self.app.inform.emit(_(
'[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...'
'[ERROR_NOTCL] Cancelled. Empty file, it has no geometry...'
))
return 'fail'
@ -4482,7 +4339,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
)
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -4552,12 +4409,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if self.solid_geometry:
with self.app.proc_container.new(_("Generating CNC Code")):
if app_obj.new_object("cncjob", outname, job_init_single_geometry) != 'fail':
app_obj.inform.emit("[success]CNCjob created: %s" % outname)
app_obj.inform.emit("[success] CNCjob created: %s" % outname)
app_obj.progress.emit(100)
else:
with self.app.proc_container.new(_("Generating CNC Code")):
if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
app_obj.inform.emit("[success]CNCjob created: %s" % outname)
app_obj.inform.emit("[success] CNCjob created: %s" % outname)
app_obj.progress.emit(100)
# Create a promise with the name
@ -4663,7 +4520,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
'[ERROR_NOTCL] Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]'
))
try:
@ -4675,7 +4532,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except ValueError:
self.app.inform.emit(
_(
'[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
'[ERROR_NOTCL] Wrong value format for self.defaults["feedrate_probe"] '
'or self.options["feedrate_probe"]'
))
@ -4703,7 +4560,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def job_thread(app_obj):
with self.app.proc_container.new(_("Generating CNC Code")):
app_obj.new_object("cncjob", outname, job_init)
app_obj.inform.emit("[success]CNCjob created: %s" % outname)
app_obj.inform.emit("[success] CNCjob created: %s" % outname)
app_obj.progress.emit(100)
# Create a promise with the name
@ -4764,7 +4621,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# else:
# self.solid_geometry = affinity.scale(self.solid_geometry, xfactor, yfactor,
# origin=(px, py))
# self.app.inform.emit("[success]Geometry Scale done.")
# self.app.inform.emit("[success] Geometry Scale done.")
def scale_recursion(geom):
if type(geom) == list:
@ -4782,7 +4639,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.solid_geometry=scale_recursion(self.solid_geometry)
self.app.inform.emit(_(
"[success]Geometry Scale done."
"[success] Geometry Scale done."
))
def offset(self, vect):
@ -4799,7 +4656,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
dx, dy = vect
except TypeError:
self.app.inform.emit(_(
"[ERROR_NOTCL]An (x,y) pair of values are needed. "
"[ERROR_NOTCL] An (x,y) pair of values are needed. "
"Probable you entered only one value in the Offset field."
))
return
@ -4819,7 +4676,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else:
self.solid_geometry=translate_recursion(self.solid_geometry)
self.app.inform.emit(_(
"[success]Geometry Offset done."
"[success] Geometry Offset done."
))
def convert_units(self, units):
@ -4888,7 +4745,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
)
except ValueError:
self.app.inform.emit(_(
"[ERROR_NOTCL]Wrong value format entered, "
"[ERROR_NOTCL] Wrong value format entered, "
"use a number."
))
return
@ -4948,11 +4805,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
except TypeError: # Element is not iterable...
self.add_shape(shape=element, color=color, visible=visible, layer=0)
def plot(self, visible=None):
def plot(self, visible=None, kind=None):
"""
Adds the object into collection.
Plot the object.
:return: None
:param visible: Controls if the added shape is visible of not
:param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, because
CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewrited
:return:
"""
# Does all the required setup and returns False
@ -5305,7 +5165,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
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_modifygcode_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)
@ -5372,7 +5232,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
if filename == '':
self.app.inform.emit(_(
"[WARNING_NOTCL]Export Machine Code cancelled ..."))
"[WARNING_NOTCL] Export Machine Code cancelled ..."))
return
preamble = str(self.ui.prepend_text.get_value())
@ -5385,7 +5245,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.app.file_saved.emit("gcode", filename)
self.app.inform.emit(_("[success] Machine Code file saved to: %s") % filename)
def on_modifygcode_button_click(self, *args):
def on_edit_code_click(self, *args):
preamble = str(self.ui.prepend_text.get_value())
postamble = str(self.ui.append_text.get_value())
gc = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
@ -5394,18 +5254,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
else:
self.app.gcode_edited = gc
# add the tab if it was closed
self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, _("Code Editor"))
# delete the absolute and relative position and messages in the infobar
self.app.ui.position_label.setText("")
self.app.ui.rel_position_label.setText("")
# Switch plot_area to CNCJob tab
self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
# first clear previous text in text editor (if any)
self.app.ui.code_editor.clear()
self.app.init_code_editor(name=_("Code Editor"))
self.app.ui.buttonOpen.clicked.connect(self.app.handleOpen)
self.app.ui.buttonSave.clicked.connect(self.app.handleSaveGCode)
# then append the text from GCode to the text editor
try:
@ -5413,8 +5264,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
proc_line = str(line).strip('\n')
self.app.ui.code_editor.append(proc_line)
except Exception as e:
log.debug('FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e))
self.app.inform.emit(_('[ERROR]FlatCAMCNNJob.on_modifygcode_button_click() -->%s') % str(e))
log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e))
self.app.inform.emit(_('[ERROR]FlatCAMCNNJob.on_edit_code_click() -->%s') % str(e))
return
self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
@ -5513,6 +5364,17 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
return gcode
def gcode_footer(self, end_command=None):
"""
: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):
gcode = ''
roland = False
@ -5520,7 +5382,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
try:
if self.special_group:
self.app.inform.emit(_("[WARNING_NOTCL]This CNCJob object can't be processed because "
self.app.inform.emit(_("[WARNING_NOTCL] This CNCJob object can't be processed because "
"it is a %s CNCJob object.") % str(self.special_group))
return 'fail'
except AttributeError:
@ -5577,7 +5439,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
))
return
g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble
g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + self.gcode_footer()
# 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:
@ -5711,7 +5573,6 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.ui.plot_cb.setChecked(True)
self.ui_connect()
def plot(self, visible=None, kind='all'):
# Does all the required setup and returns False

View File

@ -86,12 +86,14 @@ def on_language_apply_click(app, restart=False):
msgbox.setInformativeText("Are you sure do you want to change the current language to %s?" % name.capitalize())
msgbox.setWindowTitle("Apply Language ...")
msgbox.setWindowIcon(QtGui.QIcon('share/language32.png'))
msgbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes)
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
response = msgbox.exec_()
msgbox.setDefaultButton(bt_yes)
msgbox.exec_()
response = msgbox.clickedButton()
if response == QtWidgets.QMessageBox.Cancel:
if response == bt_no:
return
else:
settings = QSettings("Open Source", "FlatCAM")
@ -112,6 +114,12 @@ def apply_language(domain, lang=None):
name = settings.value('language')
else:
name = settings.value('English')
# in case the 'language' parameter is not in QSettings add it to QSettings and it's value is
# the default language, English
settings.setValue('language', 'English')
# This will write the setting to the platform specific storage.
del settings
else:
name = str(lang)

View File

@ -8,7 +8,7 @@ class WorkerStack(QtCore.QObject):
worker_task = QtCore.pyqtSignal(dict) # 'worker_name', 'func', 'params'
thread_exception = QtCore.pyqtSignal(object)
def __init__(self):
def __init__(self, workers_number):
super(WorkerStack, self).__init__()
self.workers = []
@ -16,7 +16,7 @@ class WorkerStack(QtCore.QObject):
self.load = {} # {'worker_name': tasks_count}
# Create workers crew
for i in range(0, 2):
for i in range(0, workers_number):
worker = Worker(self, 'Slogger-' + str(i))
thread = QtCore.QThread()

View File

@ -240,6 +240,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
# tasks know that they have to wait until available.
self.promises = set()
# same as above only for objects that are plotted
self.plot_promises = set()
self.app = app
### View
@ -275,6 +278,16 @@ class ObjectCollection(QtCore.QAbstractItemModel):
def has_promises(self):
return len(self.promises) > 0
def plot_promise(self, plot_obj_name):
self.plot_promises.add(plot_obj_name)
def plot_remove_promise(self, plot_obj_name):
if plot_obj_name in self.plot_promises:
self.plot_promises.remove(plot_obj_name)
def has_plot_promises(self):
return len(self.plot_promises) > 0
def on_mouse_down(self, event):
FlatCAMApp.App.log.debug("Mouse button pressed on list")
@ -394,6 +407,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.app.myKeywords.remove(old_name)
self.app.myKeywords.append(new_name)
self.app.shell._edit.set_model_data(self.app.myKeywords)
self.app.ui.code_editor.set_model_data(self.app.myKeywords)
except:
log.debug(
"setData() --> Could not remove the old object name from auto-completer model list")
@ -550,6 +564,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
try:
self.app.myKeywords.remove(name)
self.app.shell._edit.set_model_data(self.app.myKeywords)
self.app.ui.code_editor.set_model_data(self.app.myKeywords)
except:
log.debug(
"delete_active() --> Could not remove the old object name from auto-completer model list")

178
README.md
View File

@ -9,6 +9,184 @@ CAD program, and create G-Code for Isolation routing.
=================================================
13.04.2019
- updating the German translation
- Gerber Editor: added ability to change on the fly the aperture after one of the tools: Add Pad or Add Pad Array is activated
- Gerber Editor: if a tool is cancelled via key shortcut ESCAPE, the selection is now deleted and any other action require a new selection
- finished German translation (Google translated with some adjustments)
- final fix for issue #277. Previous fix was applied only for one case out of three.
- RELEASE 8.913
12.04.2019
- Gerber Editor: added support for Oblong type of aperture
- fixed an issue with automatically filled in aperture code when the edited Gerber file has no apertures; established an default with value 10 (according to Gerber specifications)
- fixed a bug in editing a blank Gerber object
- added handlers for the Gerber Editor context menu
- updated the translation template POT file and the EN PO/MO files
- Gerber Editor: added toggle effect to the Transform Tool
- Gerber Editor: added shortcut for Transform Tool and also toggle effect here, too
- updated the shortcut list with the Gerber Editor shortcut keys
- Gerber Editor: fixed error when adding an aperture with code value lower than the ones that already exists
- when adding an aperture with code '0' (zero) it will automatically be set with size zero and type: 'REG' (from region); here we store all the regions from a Gerber file, the ones without a declared aperture
- Gerber Editor: added support for Gerber polarity change commands (LPD, LPC)
- moved the polarity change processing from FlatCAMGrbEditor() class to camlib.Gerber().parse_lines()
- made optional the saving of an edited object. Now the user can cancel the changes to the object.
- replaced the standard buttons in the QMessageBox's used in the app with custom ones that can have text translated
- updated the POT translation file and the MO/PO files for English and Romanian language
11.04.2019
- changed the color of the marked apertures to the global_selection_color
- Gerber Editor: added Transformation Tool and Rotation key shortcut
- in all Editors, manually deactivating a button in the editor toolbar will automatically select the 'Select' button
- fixed Excellon Editor selection: when a tool is selected in Tools Table, all the drills belonging to that tool are selected. When a drill is selected on canvas, the associated tool will be selected without automatically selecting all other drills with same tool
- Gerber Editor: added Add Pad Array tool
- Gerber Editor: in Add Pad Array tool, if the pad is not circular type, for circular array the pad will be rotated to match the array angle
- Gerber Editor: fixed multiple selection with key modifier such that first click selects, second deselects
10.04.2019
- Gerber Editor: added Add Track and Add Region functions
- Gerber Editor: fixed key shortcuts
- fixed setting the Layout combobox in Preferences according to the current layout
- created menu links and shortcut keys for adding a new empty Gerber objects; on update of the edited Gerber, if the source object was an empty one (new blank one) this source obj will be deleted
- removed the old apertures editing from Gerber Obj selected tab
- Gerber Editor: added Add Pad (circular or rectangular type only)
- Gerber Editor: autoincrement aperture code when adding new apertures
- Gerber Editor: automatically calculate the size of the rectangular aperture
9.04.2019
- Gerber Editor: added buffer and scale tools
- Gerber Editor: working on aperture selection to show on Aperture Table
- Gerber Editor: finished the selection on canvas; should be used as an template for the other Editors
- Gerber Editor: finished the Copy, Aperture Add, Buffer, Scale, Move including the Utility geometry
- Trying to fix bug in Measurement Tool: the mouse events don't disconnect
- fixed above bug in Measurement Tool (but there is a TODO there)
7.04.2019
- default values for Jump To function is jumping to origin (0, 0)
6.04.2019
- fixed bug in Geometry Editor in buffer_int() function that created an Circular Reference Error when applying buffer interior on a geometry.
- fixed issue with not possible to close the app after a project save.
- preliminary Gerber Editor.on_aperture_delete()
- fixed 'circular reference' error when creating the new Gerber file in Gerber Editor
- preliminary Gerber Editor.on_aperture_add()
5.04.2019
- Gerber Editor: made geometry transfer (which is slow) to Editor to be multithreaded
- Gerber Editor: plotting process is showed in the status bar
- increased the number of workers in FlatCAM and made the number of workers customizable from Preferences
- WIP in Gerber Editor: geometry is no longer stored in a Rtree storage as it is not needed
- changed the way delayed plot is working in Gerber Editor to use a Qtimer instead of python threading module
- WIP in Gerber Editor
- fixed bug in saving the maximized state
- fixed bug in applying default language on first start
~~- on activating 'V' key shortcut (zoom fit) the mouse cursor is now jumping to origin (0, 0)~~
- fixed bug in saving toolbars state; the file was saved before setting the self.defaults['global_toolbar_view]
4.04.2019
- added support for Gerber format specification D (no zero suppression) - PCBWizard Gerber files support
- added support for Excellon file with no info about tool diameters - PCB Wizard Excellon file support
- modified the bogus diameters series for Excellon objects that do not have tool diameter info
- made Excellon Editor aware of the fact that the Excellon object that is edited has fake (bogus) tool diameters and therefore it will not sort the tools based on diameter but based on tool number
- fixed bug on Excellon Editor: when diameter is edited in Tools Table and the target diameter is already in the tool table, the drills from current tool are moved to the new tool (with new dia) - before it crashed
- fixed offset after editing drill diameters in Excellon Editor.
3.04.2019
- fixed plotting in Gerber Editor
- working on GUI in Gerber Editor
- added a Gcode end_command: default is M02
- modified the calling of the editor2object() slot function to fix an issue with updating geometry imported from SVG file, after edit
- working on Gerber Editor - added the key shortcuts: wip
- made saving of the project file non-blocking and also while saving the project file, if the user tries again to close the app while project file is being saved, the app will close only after saving is complete (the project file size is non zero)
- fixed the camlib.Geometry.import_svg() and camlib.Gerber.bounds() to work when importing SVG files as Gerber
31.03.2019
- fixed issue #281 by making generation of a convex shape for the freeform cutout in Tool Cutout a choice rather than the default
- fixed bug in Tool Cutout, now in manual cutout mode the gap size reflect the value set
- changed Measuring Tool to use the mouse click release instead of mouse click press; also fixed a bug when using the ESC key.
- fixed errors when the File -> New Project is initiated while an Editor is still active.
- the File->Exit action handler is now self.final_save()
- wip in Gerber editor
29.03.2019
- update the TCL keyword list
- fix on the Gerber parser that makes searching for '%%' char optional when doing regex search for mode, units or image polarity. This allow loading Gerber files generated by the ECAD software TCl4.4
- fix error in plotting Excellon when toggling units
- FlatCAM editors now are separated each in it's own file
- fixed TextTool in Geometry Editor so it will open the notebook on activation and close it after finishing text adding
- started to work on a Gerber Editor
- added a fix in the Excellon parser by allowing a comma in the tool definitions between the diameter and the rest
28.03.2019
- About 45% progress in German translation
- new feature: added ability to edit MultiGeo geometry (geometry from Paint Tool)
- changed all the info messages that are of type warning, error or success so they have a space added after the keyword
- changed the Romanian translation by adding more diacritics
- modified Gerber parser to copy the follow_geometry in the self.apertures
- modified the Properties Tool to show the number of elements in the follow_geometry for each aperture
- modified the copy functions to copy the follow_geometry and also the apertures if it's possible (only for Gerber objects)
27.03.2019
- added new feature: user can delete apertures in Advanced mode and then create a new FlatCAM Gerber object
- progress in German translation. About 27% done.
- fixed issue #278. Crash on name change in the Name field in the Selected Tab.
26.03.2019
- fixed an issue where the Geometry plot function protested that it does not have an parameter that is used by the CNCJob plot function. But both inherit from FaltCAMObj plot function which does not have that parameter so something may need to be changed. Until then I provided a phony keyboard parameter to make that function 'shut up'
- fixed bug: after using Paint Tool shortcut keys are disabled
- added CNCJob geometry for the holes created by the drills from Excellon objects
25.03.2019
- in the TCL completer if the word is already complete don't add it again but add a space
- added all the TCL keywords in the completer keyword list
- work in progress in German translation ~7%
- after any autocomplete in TCL completer, a space is added
- fixed an module import issue in NCC Tool
- minor change (optimization) of the CNCJob UI
- work in progress in German translation ~20%
22.03.2019
- fixed an error that created a situation that when saving a project with some of the CNCJob objects disabled, on project reload the CNCJob objects are no longer loaded
- fixed the Gerber.merge() function. When some of the Gerber files have apertures with same id, create a new aperture id for the object that is fused because each aperture id may hold different geometries.
- changed the autoname for saving Preferences, Project and PNG file
20.03.2019
- added autocomplete finish with ENTER key for the TCL Shell
- made sure that the autocomplete function works only for FlatCAM Scripts
- ESC key will trigger normal view if in full screen and the ESC key is pressed
- added an icon and title text for the Toggle Units QMessageBox
19.03.2019
- added autocomplete for Code editor;
- autocomplete in Code Editor is finished by hitting either TAB key or ENTER key
- fixed the Gerber.merge() to work for the case when one of the merged Gerber objects solid_geometry type is Polygon and not a list
18.03.2019
- added ability to create new scripts and open scripts in FlatCAM Script Editor
- the Code Editor tab name is changed according to the task; 'save' and 'open' buttons will have filters installed for the QOpenDialog fit to the task
- added ability to run a FlatCAM Tcl script by double-clicking on the file
- in Code Editor added shortcut combo key CTRL+SHIFT+V to function as a Special Paste that will replace the '\' char with '/' so the Windows paths will be pasted correctly for TCL Shell. Also doing SHIFT + LMB on the Paste in contextual menu is doing the same.
17.03.2019
- remade the layout in 2Sided Tool

485
camlib.py
View File

@ -621,7 +621,6 @@ class Geometry(object):
# flatten the self.solid_geometry list for import_svg() to import SVG as Gerber
self.solid_geometry = list(self.flatten_list(self.solid_geometry))
self.solid_geometry = cascaded_union(self.solid_geometry)
geos_text = getsvgtext(svg_root, object_type, units=units)
if geos_text is not None:
@ -632,7 +631,8 @@ class Geometry(object):
_, minimy, _, maximy = i.bounds
h2 = (maximy - minimy) * 0.5
geos_text_f.append(translate(scale(i, 1.0, -1.0, origin=(0, 0)), yoff=(h + h2)))
self.solid_geometry = [self.solid_geometry, geos_text_f]
if geos_text_f:
self.solid_geometry = self.solid_geometry + geos_text_f
def import_dxf(self, filename, object_type=None, units='MM'):
"""
@ -1384,7 +1384,7 @@ class Geometry(object):
self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = mirror_geom(self.solid_geometry)
self.app.inform.emit(_('[success]Object was mirrored ...'))
self.app.inform.emit(_('[success] Object was mirrored ...'))
except AttributeError:
self.app.inform.emit(_("[ERROR_NOTCL] Failed to mirror. No object selected"))
@ -1422,7 +1422,7 @@ class Geometry(object):
self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = rotate_geom(self.solid_geometry)
self.app.inform.emit(_('[success]Object was rotated ...'))
self.app.inform.emit(_('[success] Object was rotated ...'))
except AttributeError:
self.app.inform.emit(_("[ERROR_NOTCL] Failed to rotate. No object selected"))
@ -1458,7 +1458,7 @@ class Geometry(object):
self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = skew_geom(self.solid_geometry)
self.app.inform.emit(_('[success]Object was skewed ...'))
self.app.inform.emit(_('[success] Object was skewed ...'))
except AttributeError:
self.app.inform.emit(_("[ERROR_NOTCL] Failed to skew. No object selected"))
@ -1951,14 +1951,14 @@ class Gerber (Geometry):
#### Parser patterns ####
# FS - Format Specification
# The format of X and Y must be the same!
# L-omit leading zeros, T-omit trailing zeros
# L-omit leading zeros, T-omit trailing zeros, D-no zero supression
# A-absolute notation, I-incremental notation
self.fmt_re = re.compile(r'%FS([LT])([AI])X(\d)(\d)Y\d\d\*%$')
self.fmt_re = re.compile(r'%?FS([LTD])([AI])X(\d)(\d)Y\d\d\*%?$')
self.fmt_re_alt = re.compile(r'%FS([LT])([AI])X(\d)(\d)Y\d\d\*MO(IN|MM)\*%$')
self.fmt_re_orcad = re.compile(r'(G\d+)*\**%FS([LT])([AI]).*X(\d)(\d)Y\d\d\*%$')
# Mode (IN/MM)
self.mode_re = re.compile(r'^%MO(IN|MM)\*%$')
self.mode_re = re.compile(r'^%?MO(IN|MM)\*%?$')
# Comment G04|G4
self.comm_re = re.compile(r'^G0?4(.*)$')
@ -2013,7 +2013,7 @@ class Gerber (Geometry):
self.eof_re = re.compile(r'^M02\*')
# IP - Image polarity
self.pol_re = re.compile(r'^%IP(POS|NEG)\*%$')
self.pol_re = re.compile(r'^%?IP(POS|NEG)\*%?$')
# LP - Level polarity
self.lpol_re = re.compile(r'^%LP([DC])\*%$')
@ -2169,6 +2169,10 @@ class Gerber (Geometry):
# applying a union for every new polygon.
poly_buffer = []
# made True when the LPC command is encountered in Gerber parsing
# it allows adding data into the clear_geometry key of the self.apertures[aperture] dict
self.is_lpc = False
# store here the follow geometry
follow_buffer = []
@ -2242,15 +2246,27 @@ class Gerber (Geometry):
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['follow_geometry'] = []
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
path = [path[-1]]
@ -2259,10 +2275,12 @@ class Gerber (Geometry):
# TODO: Remove when bug fixed
if len(poly_buffer) > 0:
if current_polarity == 'D':
self.is_lpc = True
# self.follow_geometry = self.follow_geometry.union(cascaded_union(follow_buffer))
self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
else:
self.is_lpc = False
# self.follow_geometry = self.follow_geometry.difference(cascaded_union(follow_buffer))
self.solid_geometry = self.solid_geometry.difference(cascaded_union(poly_buffer))
@ -2284,8 +2302,8 @@ class Gerber (Geometry):
log.debug("Gerber format found. (%s) " % str(gline))
log.debug(
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros)" %
self.gerber_zeros)
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros, "
"D-no zero supression)" % self.gerber_zeros)
log.debug("Gerber format found. Coordinates type = %s (Absolute or Relative)" % absolute)
continue
@ -2308,8 +2326,8 @@ class Gerber (Geometry):
self.frac_digits = int(match.group(4))
log.debug("Gerber format found. (%s) " % str(gline))
log.debug(
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros)" %
self.gerber_zeros)
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros, "
"D-no zero suppression)" % self.gerber_zeros)
log.debug("Gerber format found. Coordinates type = %s (Absolute or Relative)" % absolute)
gerber_units = match.group(1)
@ -2332,8 +2350,8 @@ class Gerber (Geometry):
self.frac_digits = int(match.group(5))
log.debug("Gerber format found. (%s) " % str(gline))
log.debug(
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros)" %
self.gerber_zeros)
"Gerber format found. Gerber zeros = %s (L-omit leading zeros, T-omit trailing zeros, "
"D-no zerosuppressionn)" % self.gerber_zeros)
log.debug("Gerber format found. Coordinates type = %s (Absolute or Relative)" % absolute)
gerber_units = match.group(1)
@ -2415,11 +2433,18 @@ class Gerber (Geometry):
int(self.steps_per_circle))
if not flash.is_empty:
poly_buffer.append(flash)
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(flash)
else:
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
except IndexError:
log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline))
@ -2453,15 +2478,27 @@ class Gerber (Geometry):
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
path = [path[-1]]
@ -2478,15 +2515,27 @@ class Gerber (Geometry):
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
path = [path[-1]]
@ -2503,12 +2552,25 @@ class Gerber (Geometry):
if geo:
if not geo.is_empty:
follow_buffer.append(geo)
poly_buffer.append(geo)
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
continue
# Only one path defines region?
@ -2531,6 +2593,11 @@ class Gerber (Geometry):
region = Polygon()
if not region.is_empty:
follow_buffer.append(region)
try:
self.apertures[current_aperture]['follow_geometry'].append(region)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(region)
region = Polygon(path)
if not region.is_valid:
@ -2552,11 +2619,18 @@ class Gerber (Geometry):
self.apertures['0']['solid_geometry'] = []
used_aperture = '0'
try:
self.apertures[used_aperture]['solid_geometry'].append(region)
except KeyError:
self.apertures[used_aperture]['solid_geometry'] = []
self.apertures[used_aperture]['solid_geometry'].append(region)
if self.is_lpc is True:
try:
self.apertures[used_aperture]['clear_geometry'].append(region)
except KeyError:
self.apertures[used_aperture]['clear_geometry'] = []
self.apertures[used_aperture]['clear_geometry'].append(region)
else:
try:
self.apertures[used_aperture]['solid_geometry'].append(region)
except KeyError:
self.apertures[used_aperture]['solid_geometry'] = []
self.apertures[used_aperture]['solid_geometry'].append(region)
path = [[current_x, current_y]] # Start new path
continue
@ -2627,11 +2701,18 @@ class Gerber (Geometry):
geo = shply_box(minx, miny, maxx, maxy)
poly_buffer.append(geo)
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
except:
pass
last_path_aperture = current_aperture
@ -2670,10 +2751,20 @@ class Gerber (Geometry):
if self.apertures[last_path_aperture]["type"] != 'R':
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
except Exception as e:
log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
if making_region:
@ -2706,19 +2797,33 @@ class Gerber (Geometry):
if self.apertures[last_path_aperture]["type"] != 'R':
if not geo.is_empty:
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except Exception as e:
log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
# if linear_x or linear_y are None, ignore those
if linear_x is not None and linear_y is not None:
@ -2741,8 +2846,18 @@ class Gerber (Geometry):
try:
if self.apertures[last_path_aperture]["type"] != 'R':
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
except:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
width = self.apertures[last_path_aperture]["size"]
@ -2751,18 +2866,32 @@ class Gerber (Geometry):
try:
if self.apertures[last_path_aperture]["type"] != 'R':
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except:
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
# Reset path starting point
path = [[linear_x, linear_y]]
@ -2770,7 +2899,13 @@ class Gerber (Geometry):
# --- BUFFERED ---
# Draw the flash
# this treats the case when we are storing geometry as paths
follow_buffer.append(Point([linear_x, linear_y]))
geo_flash = Point([linear_x, linear_y])
follow_buffer.append(geo_flash)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo_flash)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo_flash)
# this treats the case when we are storing geometry as solids
flash = Gerber.create_flash_geometry(
@ -2780,11 +2915,18 @@ class Gerber (Geometry):
)
if not flash.is_empty:
poly_buffer.append(flash)
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(flash)
else:
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
# maybe those lines are not exactly needed but it is easier to read the program as those coordinates
# are used in case that circular interpolation is encountered within the Gerber file
@ -2869,16 +3011,28 @@ class Gerber (Geometry):
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
if not buffered.is_empty:
poly_buffer.append(buffered)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(buffered)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(buffered)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
current_x = circular_x
current_y = circular_y
@ -3000,20 +3154,46 @@ class Gerber (Geometry):
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
# first check if we have any clear_geometry (LPC) and if yes then we need to substract it
# from the apertures solid_geometry
temp_geo = []
for apid in self.apertures:
if 'clear_geometry' in self.apertures[apid]:
for clear_geo in self.apertures[apid]['clear_geometry']:
for solid_geo in self.apertures[apid]['solid_geometry']:
if solid_geo.intersects(clear_geo):
res_geo = clear_geo.symmetric_difference(solid_geo)
temp_geo.append(res_geo)
else:
temp_geo.append(solid_geo)
self.apertures[apid]['solid_geometry'] = deepcopy(temp_geo)
self.apertures[apid].pop('clear_geometry', None)
# --- Apply buffer ---
# this treats the case when we are storing geometry as paths
self.follow_geometry = follow_buffer
@ -3124,7 +3304,7 @@ class Gerber (Geometry):
:rtype : None
:return: None
"""
pass
# self.buffer_paths()
#
# self.fix_regions()
@ -3184,16 +3364,17 @@ class Gerber (Geometry):
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
else:
try:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
except Exception as e:
log.debug("camlib.Geometry.bounds() --> %s" % str(e))
return
if not k.is_empty:
try:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
except Exception as e:
log.debug("camlib.Gerber.bounds() --> %s" % str(e))
return
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
else:
# it's a Shapely object, return it's bounds
@ -3264,7 +3445,7 @@ class Gerber (Geometry):
except Exception as e:
log.debug('FlatCAMGeometry.scale() --> %s' % str(e))
self.app.inform.emit(_("[success]Gerber Scale done."))
self.app.inform.emit(_("[success] Gerber Scale done."))
## solid_geometry ???
@ -3297,7 +3478,7 @@ class Gerber (Geometry):
try:
dx, dy = vect
except TypeError:
self.app.inform.emit(_("[ERROR_NOTCL]An (x,y) pair of values are needed. "
self.app.inform.emit(_("[ERROR_NOTCL] An (x,y) pair of values are needed. "
"Probable you entered only one value in the Offset field."))
return
@ -3321,7 +3502,7 @@ class Gerber (Geometry):
except Exception as e:
log.debug('FlatCAMGeometry.offset() --> %s' % str(e))
self.app.inform.emit(_("[success]Gerber Offset done."))
self.app.inform.emit(_("[success] Gerber Offset done."))
def mirror(self, axis, point):
"""
@ -3371,7 +3552,6 @@ class Gerber (Geometry):
# self.solid_geometry = affinity.scale(self.solid_geometry,
# xscale, yscale, origin=(px, py))
def skew(self, angle_x, angle_y, point):
"""
Shear/Skew the geometries of an object by angles along x and y dimensions.
@ -3530,6 +3710,12 @@ class Excellon(Geometry):
self.zeros_found = self.zeros
self.units_found = self.units
# this will serve as a default if the Excellon file has no info regarding of tool diameters (this info may be
# in another file like for PCB WIzard ECAD software
self.toolless_diam = 1.0
# signal that the Excellon file has no tool diameter informations and the tools have bogus (random) diameter
self.diameterless = False
# Excellon format
self.excellon_format_upper_in = excellon_format_upper_in or self.defaults["excellon_format_upper_in"]
self.excellon_format_lower_in = excellon_format_lower_in or self.defaults["excellon_format_lower_in"]
@ -3575,7 +3761,7 @@ class Excellon(Geometry):
# r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
# r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
# r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
self.toolset_re = re.compile(r'^T(\d+)(?=.*C(\d*\.?\d*))?' +
self.toolset_re = re.compile(r'^T(\d+)(?=.*C,?(\d*\.?\d*))?' +
r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
@ -3590,7 +3776,8 @@ class Excellon(Geometry):
self.toolsel_re = re.compile(r'^T(\d+)')
# Headerless toolset
self.toolset_hl_re = re.compile(r'^T(\d+)(?=.*C(\d*\.?\d*))')
# self.toolset_hl_re = re.compile(r'^T(\d+)(?=.*C(\d*\.?\d*))')
self.toolset_hl_re = re.compile(r'^T(\d+)(?:.?C(\d+\.?\d*))?')
# Comment
self.comm_re = re.compile(r'^;(.*)$')
@ -3739,10 +3926,9 @@ class Excellon(Geometry):
continue
else:
log.warning("Line ignored, it's a comment: %s" % eline)
else:
if self.hend_re.search(eline):
if in_header is False:
if in_header is False or bool(self.tools) is False:
log.warning("Found end of the header but there is no header: %s" % eline)
log.warning("The only useful data in header are tools, units and format.")
log.warning("Therefore we will create units and format based on defaults.")
@ -3787,12 +3973,27 @@ class Excellon(Geometry):
if match:
current_tool = str(int(match.group(1)))
log.debug("Tool change: %s" % current_tool)
if headerless is True:
if bool(headerless):
match = self.toolset_hl_re.search(eline)
if match:
name = str(int(match.group(1)))
try:
diam = float(match.group(2))
except:
# it's possible that tool definition has only tool number and no diameter info
# (those could be in another file like PCB Wizard do)
# then match.group(2) = None and float(None) will create the exception
# the bellow construction is so each tool will have a slightly different diameter
# starting with a default value, to allow Excellon editing after that
self.diameterless = True
if self.excellon_units == 'MM':
diam = self.toolless_diam + (int(current_tool) - 1) / 100
else:
diam = (self.toolless_diam + (int(current_tool) - 1) / 100) / 25.4
spec = {
"C": float(match.group(2)),
"C": diam,
}
spec['solid_geometry'] = []
self.tools[name] = spec
@ -4719,6 +4920,8 @@ class CNCjob(Geometry):
Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
self.kind = kind
self.origin_kind = None
self.units = units
self.z_cut = z_cut
@ -4783,6 +4986,10 @@ class CNCjob(Geometry):
self.tool = 0.0
# used for creating drill CCode geometry; will be updated in the generate_from_excellon_by_tool()
self.exc_drills = None
self.exc_tools = None
# search for toolchange parameters in the Toolchange Custom Code
self.re_toolchange_custom = re.compile(r'(%[a-zA-Z0-9\-_]+%)')
@ -4904,6 +5111,11 @@ class CNCjob(Geometry):
:return: None
:rtype: None
"""
# create a local copy of the exobj.drills so it can be used for creating drill CCode geometry
self.exc_drills = deepcopy(exobj.drills)
self.exc_tools = deepcopy(exobj.tools)
if drillz > 0:
self.app.inform.emit(_("[WARNING] The Cut Z parameter has positive value. "
"It is the depth value to drill into material.\n"
@ -5109,7 +5321,13 @@ class CNCjob(Geometry):
current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
else:
current_tooldia = float('%.3f' % float(exobj.tools[tool]["C"]))
z_offset = float(self.tool_offset[current_tooldia]) * (-1)
# TODO apply offset only when using the GUI, for TclCommand this will create an error
# because the values for Z offset are created in build_ui()
try:
z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError:
z_offset = 0
self.z_cut += z_offset
# Drillling!
@ -5128,7 +5346,7 @@ class CNCjob(Geometry):
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
self.app.inform.emit(_('[ERROR_NOTCL] The loaded Excellon file has no drills ...'))
return 'fail'
log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
@ -5223,7 +5441,7 @@ class CNCjob(Geometry):
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
self.app.inform.emit(_('[ERROR_NOTCL] The loaded Excellon file has no drills ...'))
return 'fail'
log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
@ -5255,8 +5473,15 @@ class CNCjob(Geometry):
current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
else:
current_tooldia = float('%.3f' % float(exobj.tools[tool]["C"]))
z_offset = float(self.tool_offset[current_tooldia]) * (-1)
# TODO apply offset only when using the GUI, for TclCommand this will create an error
# because the values for Z offset are created in build_ui()
try:
z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError:
z_offset = 0
self.z_cut += z_offset
# Drillling!
altPoints = []
for point in points[tool]:
@ -5274,7 +5499,7 @@ class CNCjob(Geometry):
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
self.app.inform.emit(_('[ERROR_NOTCL] The loaded Excellon file has no drills ...'))
return 'fail'
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
@ -5537,7 +5762,7 @@ class CNCjob(Geometry):
# if solid_geometry is empty raise an exception
if not geometry.solid_geometry:
self.app.inform.emit(_("[ERROR_NOTCL]Trying to generate a CNC Job "
self.app.inform.emit(_("[ERROR_NOTCL] Trying to generate a CNC Job "
"from a Geometry object without solid_geometry."))
temp_solid_geometry = []
@ -5576,7 +5801,7 @@ class CNCjob(Geometry):
# if the offset is less than half of the total length or less than half of the total width of the
# solid geometry it's obvious we can't do the offset
if -offset > ((c - a) / 2) or -offset > ((d - b) / 2):
self.app.inform.emit(_("[ERROR_NOTCL]The Tool Offset value is too negative to use "
self.app.inform.emit(_("[ERROR_NOTCL] The Tool Offset value is too negative to use "
"for the current_geometry.\n"
"Raise the value (in module) and try again."))
return 'fail'
@ -6098,7 +6323,7 @@ class CNCjob(Geometry):
# Process every instruction
for line in StringIO(self.gcode):
if '%MO' in line or '%' in line:
if '%MO' in line or '%' in line or 'MOIN' in line or 'MOMM' in line:
return "fail"
gobj = self.codes_split(line)
@ -6130,6 +6355,23 @@ class CNCjob(Geometry):
"kind": kind})
path = [path[-1]] # Start with the last point of last path.
# create the geometry for the holes created when drilling Excellon drills
if self.origin_kind == 'excellon':
if current['Z'] < 0:
current_drill_point_coords = (float('%.4f' % current['X']), float('%.4f' % current['Y']))
# find the drill diameter knowing the drill coordinates
for pt_dict in self.exc_drills:
point_in_dict_coords = (float('%.4f' % pt_dict['point'].x),
float('%.4f' % pt_dict['point'].y))
if point_in_dict_coords == current_drill_point_coords:
tool = pt_dict['tool']
dia = self.exc_tools[tool]['C']
kind = ['C', 'F']
geometry.append({"geom": Point(current_drill_point_coords).
buffer(dia/2).exterior,
"kind": kind})
break
if 'G' in gobj:
current['G'] = int(gobj['G'])
@ -6256,7 +6498,17 @@ class CNCjob(Geometry):
text.append(str(path_num))
pos.append(geo['geom'].coords[0])
poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance)
# plot the geometry of Excellon objects
if self.origin_kind == 'excellon':
try:
poly = Polygon(geo['geom'])
except ValueError:
# if the geos are travel lines it will enter into Exception
poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance)
else:
# plot the geometry of any objects other than Excellon
poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance)
if kind == 'all':
obj.add_shape(shape=poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0],
visible=visible, layer=1 if geo['kind'][0] == 'C' else 2)
@ -7075,17 +7327,22 @@ def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
:param frac_digits: Number of digits used for the fractional
part of the number
:type frac_digits: int
:param zeros: If 'L', leading zeros are removed and trailing zeros are kept. If 'T', is in reverse.
:param zeros: If 'L', leading zeros are removed and trailing zeros are kept. Same situation for 'D' when
no zero suppression is done. If 'T', is in reverse.
:type zeros: str
:return: The number in floating point.
:rtype: float
"""
if zeros == 'L':
ret_val = None
if zeros == 'L' or zeros == 'D':
ret_val = int(strnumber) * (10 ** (-frac_digits))
if zeros == 'T':
int_val = int(strnumber)
ret_val = (int_val * (10 ** ((int_digits + frac_digits) - len(strnumber)))) * (10 ** (-frac_digits))
return ret_val

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

View File

@ -16,7 +16,7 @@ from flatcamGUI.GUIElements import *
import platform
import webbrowser
from FlatCAMEditor import FCShapeTool
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool
import gettext
import FlatCAMTranslation as fcTranslate
@ -66,6 +66,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.menufilenewgeo.setToolTip(
_("Will create a new, empty Geometry Object.")
)
self.menufilenewgrb = self.menufilenew.addAction(QtGui.QIcon('share/flatcam_icon32.png'), _('Gerber\tB'))
self.menufilenewgrb.setToolTip(
_("Will create a new, empty Gerber Object.")
)
self.menufilenewexc = self.menufilenew.addAction(QtGui.QIcon('share/drill16.png'), _('Excellon\tL'))
self.menufilenewexc.setToolTip(
_("Will create a new, empty Excellon Object.")
@ -107,7 +111,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Separator
self.menufile.addSeparator()
# Run Scripts
# Scripting
self.menufile_scripting = self.menufile.addMenu(QtGui.QIcon('share/script16.png'), _('Scripting'))
self.menufile_scripting.setToolTipsVisible(True)
self.menufilenewscript = QtWidgets.QAction(QtGui.QIcon('share/script_new16.png'), _('New Script ...'),
self)
self.menufileopenscript = QtWidgets.QAction(QtGui.QIcon('share/script_open16.png'), _('Open Script ...'),
self)
self.menufilerunscript = QtWidgets.QAction(QtGui.QIcon('share/script16.png'), _('Run Script ...\tSHIFT+S'),
self)
self.menufilerunscript.setToolTip(
@ -115,7 +126,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
"enabling the automation of certain\n"
"functions of FlatCAM.")
)
self.menufile.addAction(self.menufilerunscript)
self.menufile_scripting.addAction(self.menufilenewscript)
self.menufile_scripting.addAction(self.menufileopenscript)
self.menufile_scripting.addSeparator()
self.menufile_scripting.addAction(self.menufilerunscript)
# Separator
self.menufile.addSeparator()
@ -209,7 +223,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Separator
self.menuedit.addSeparator()
self.menueditedit = self.menuedit.addAction(QtGui.QIcon('share/edit16.png'), _('Edit Object\tE'))
self.menueditok = self.menuedit.addAction(QtGui.QIcon('share/edit_ok16.png'), _('Save && Close Editor\tCTRL+S'))
self.menueditok = self.menuedit.addAction(QtGui.QIcon('share/edit_ok16.png'), _('Close Editor\tCTRL+S'))
# adjust the initial state of the menu entries related to the editor
self.menueditedit.setDisabled(False)
@ -441,13 +455,48 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.exc_move_drill_menuitem = self.exc_editor_menu.addAction(
QtGui.QIcon('share/move32.png'),_( 'Move Drill(s)\tM'))
### APPLICATION GERBER EDITOR MENU ###
self.grb_editor_menu = QtWidgets.QMenu(_(">Gerber Editor<"))
self.menu.addMenu(self.grb_editor_menu)
self.grb_add_pad_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/aperture16.png'), _('Add Pad\tP'))
self.grb_add_pad_array_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/padarray32.png'), _('Add Pad Array\tA'))
self.grb_add_track_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/track32.png'), _('Add Track\tT'))
self.grb_add_region_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/rectangle32.png'),
_('Add Region\tN'))
self.grb_editor_menu.addSeparator()
self.grb_add_buffer_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/buffer16-2.png'),
_('Buffer\tB'))
self.grb_add_scale_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/scale32.png'),
_('Scale\tS'))
self.grb_transform_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/transform.png'),_( "Transform\tALT+R")
)
self.grb_editor_menu.addSeparator()
self.grb_copy_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/copy32.png'), _('Copy\tC'))
self.grb_delete_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/deleteshape32.png'), _('Delete\tDEL')
)
self.grb_editor_menu.addSeparator()
self.grb_move_menuitem = self.grb_editor_menu.addAction(
QtGui.QIcon('share/move32.png'),_( 'Move\tM'))
self.grb_editor_menu.menuAction().setVisible(False)
self.grb_editor_menu.setDisabled(True)
self.geo_editor_menu.menuAction().setVisible(False)
self.geo_editor_menu.setDisabled(True)
self.exc_editor_menu.menuAction().setVisible(False)
self.exc_editor_menu.setDisabled(True)
################################
### Project Tab Context menu ###
################################
@ -522,6 +571,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.geo_edit_toolbar.setObjectName('GeoEditor_TB')
self.addToolBar(self.geo_edit_toolbar)
self.grb_edit_toolbar = QtWidgets.QToolBar(_('Gerber Editor Toolbar'))
self.grb_edit_toolbar.setObjectName('GrbEditor_TB')
self.addToolBar(self.grb_edit_toolbar)
self.snap_toolbar = QtWidgets.QToolBar(_('Grid Toolbar'))
self.snap_toolbar.setObjectName('Snap_TB')
self.addToolBar(self.snap_toolbar)
@ -529,9 +582,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == 'Standard':
if layout == 'standard':
pass
elif layout == 'Compact':
elif layout == 'compact':
self.removeToolBar(self.snap_toolbar)
self.snap_toolbar.setMaximumHeight(30)
self.splitter_left.addWidget(self.snap_toolbar)
@ -546,6 +599,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
### Edit Toolbar ###
self.newgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_geo32_bis.png'), _("New Blank Geometry"))
self.newgrb_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_geo32.png'), _("New Blank Gerber"))
self.newexc_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_exc32.png'), _("New Blank Excellon"))
self.toolbargeo.addSeparator()
self.editgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/edit32.png'), _("Editor"))
@ -587,12 +641,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), _("Select"))
self.add_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/plus16.png'), _('Add Drill Hole'))
self.add_drill_array_btn = self.exc_edit_toolbar.addAction(
QtGui.QIcon('share/addarray16.png'), 'Add Drill Hole Array')
QtGui.QIcon('share/addarray16.png'), _('Add Drill Hole Array'))
self.resize_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/resize16.png'), _('Resize Drill'))
self.exc_edit_toolbar.addSeparator()
self.copy_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _('Copy Drill'))
self.delete_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/deleteshape32.png'), _("Delete Drill"))
self.delete_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'), _("Delete Drill"))
self.exc_edit_toolbar.addSeparator()
self.move_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move Drill"))
@ -623,13 +677,32 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.geo_cutpath_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/cutpath32.png'), _('Cut Path'))
self.geo_copy_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy Shape(s)"))
self.geo_delete_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/deleteshape32.png'),
self.geo_delete_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete Shape '-'"))
self.geo_transform_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
_("Transformations"))
self.geo_edit_toolbar.addSeparator()
self.geo_move_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move Objects "))
### Gerber Editor Toolbar ###
self.grb_select_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), _("Select"))
self.grb_add_pad_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/aperture32.png'), _("Add Pad"))
self.add_pad_ar_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/padarray32.png'), _('Add Pad Array'))
self.grb_add_track_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/track32.png'), _("Add Track"))
self.grb_add_region_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Add Region"))
self.grb_edit_toolbar.addSeparator()
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
self.grb_edit_toolbar.addSeparator()
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete"))
self.grb_transform_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
_("Transformations"))
self.grb_edit_toolbar.addSeparator()
self.aperture_move_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
### Snap Toolbar ###
# Snap GRID toolbar is always active to facilitate usage of measurements done on GRID
# self.addToolBar(self.snap_toolbar)
@ -1340,6 +1413,81 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td>&nbsp;Save Object and Exit Editor</td>
</tr>
</tbody>
</table>
<br>
<br>
<strong><span style="color:#00ff00">GERBER EDITOR</span></strong><br>
<table border="0" cellpadding="0" cellspacing="0" style="width:283px">
<tbody>
<tr height="20">
<td height="20" width="89"><strong>A</strong></td>
<td width="194">&nbsp;Add Pad Array</td>
</tr>
<tr height="20">
<td height="20"><strong>B</strong></td>
<td>&nbsp;Buffer</td>
</tr>
<tr height="20">
<td height="20"><strong>C</strong></td>
<td>&nbsp;Copy</td>
</tr>
<tr height="20">
<td height="20"><strong>J</strong></td>
<td>&nbsp;Jump to Location (x, y)</td>
</tr>
<tr height="20">
<td height="20"><strong>M</strong></td>
<td>&nbsp;Move</td>
</tr>
<tr height="20">
<td height="20"><strong>N</strong></td>
<td>&nbsp;Add Region</td>
</tr>
<tr height="20">
<td height="20"><strong>P</strong></td>
<td>&nbsp;Add Pad</td>
</tr>
<tr height="20">
<td height="20"><strong>S</strong></td>
<td>&nbsp;Scale</td>
</tr>
<tr height="20">
<td height="20"><strong>T</strong></td>
<td>&nbsp;Add Track</td>
</tr>
<tr height="20">
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr height="20">
<td height="20"><strong>Del</strong></td>
<td>&nbsp;Delete</td>
</tr>
<tr height="20">
<td height="20"><strong>Del</strong></td>
<td>&nbsp;Alternate: Delete Apertures</td>
</tr>
<tr height="20">
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr height="20">
<td height="20"><strong>ESC</strong></td>
<td>&nbsp;Abort and return to Select</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+S</strong></td>
<td>&nbsp;Save Object and Exit Editor</td>
</tr>
<tr height="20">
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+R</strong></td>
<td>&nbsp;Transformation Tool</td>
</tr>
</tbody>
</table>
'''
)
@ -1379,6 +1527,16 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.g_editor_cmenu.addSeparator()
self.draw_move = self.g_editor_cmenu.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
self.grb_editor_cmenu = self.popMenu.addMenu(QtGui.QIcon('share/draw32.png'), _("Gerber Editor"))
self.grb_draw_pad = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/aperture32.png'), _("Pad"))
self.grb_draw_pad_array = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/padarray32.png'), _("Pad Array"))
self.grb_draw_track = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/track32.png'), _("Track"))
self.grb_draw_region = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/polygon32.png'), _("Region"))
self.grb_editor_cmenu.addSeparator()
self.grb_copy = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/copy.png'), _("Copy"))
self.grb_delete = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/trash32.png'), _("Delete"))
self.grb_move = self.grb_editor_cmenu.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
self.e_editor_cmenu = self.popMenu.addMenu(QtGui.QIcon('share/drill32.png'), _("Exc Editor"))
self.drill = self.e_editor_cmenu.addAction(QtGui.QIcon('share/drill32.png'), _("Add Drill"))
self.drill_array = self.e_editor_cmenu.addAction(QtGui.QIcon('share/addarray32.png'), _("Add Drill Array"))
@ -1388,7 +1546,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.popmenu_copy = self.popMenu.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
self.popmenu_delete = self.popMenu.addAction(QtGui.QIcon('share/delete32.png'), _("Delete"))
self.popmenu_edit = self.popMenu.addAction(QtGui.QIcon('share/edit32.png'), _("Edit"))
self.popmenu_save = self.popMenu.addAction(QtGui.QIcon('share/floppy32.png'), _("Save && Close Edit"))
self.popmenu_save = self.popMenu.addAction(QtGui.QIcon('share/floppy32.png'), _("Close Editor"))
self.popmenu_save.setVisible(False)
self.popMenu.addSeparator()
@ -1404,7 +1562,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.cncjob_tab_layout.setContentsMargins(2, 2, 2, 2)
self.cncjob_tab.setLayout(self.cncjob_tab_layout)
self.code_editor = QtWidgets.QTextEdit()
self.code_editor = FCTextAreaExtended()
stylesheet = """
QTextEdit { selection-background-color:yellow;
selection-color:black;
@ -1523,6 +1681,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.grid_snap_btn.trigger()
self.g_editor_cmenu.setEnabled(False)
self.grb_editor_cmenu.setEnabled(False)
self.e_editor_cmenu.setEnabled(False)
self.general_defaults_form = GeneralPreferencesUI()
@ -1548,6 +1707,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.restoreState(saved_gui_state)
log.debug("FlatCAMGUI.__init__() --> UI state restored.")
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == 'standard':
@ -1555,24 +1715,36 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.exc_edit_toolbar.setDisabled(True)
self.geo_edit_toolbar.setVisible(False)
self.geo_edit_toolbar.setDisabled(True)
self.grb_edit_toolbar.setVisible(False)
self.grb_edit_toolbar.setDisabled(True)
self.corner_snap_btn.setVisible(False)
self.snap_magnet.setVisible(False)
elif layout == 'Compact':
elif layout == 'compact':
self.exc_edit_toolbar.setDisabled(True)
self.geo_edit_toolbar.setDisabled(True)
self.grb_edit_toolbar.setDisabled(True)
self.snap_magnet.setVisible(True)
self.corner_snap_btn.setVisible(True)
self.snap_magnet.setDisabled(True)
self.corner_snap_btn.setDisabled(True)
log.debug("FlatCAMGUI.__init__() --> UI layout restored from QSettings.")
else:
self.exc_edit_toolbar.setVisible(False)
self.exc_edit_toolbar.setDisabled(True)
self.geo_edit_toolbar.setVisible(False)
self.geo_edit_toolbar.setDisabled(True)
self.grb_edit_toolbar.setVisible(False)
self.grb_edit_toolbar.setDisabled(True)
self.corner_snap_btn.setVisible(False)
self.snap_magnet.setVisible(False)
settings.setValue('layout', "standard")
# This will write the setting to the platform specific storage.
del settings
log.debug("FlatCAMGUI.__init__() --> UI layout restored from defaults. QSettings set to 'standard'")
def eventFilter(self, obj, event):
if self.general_defaults_form.general_app_group.toggle_tooltips_cb.get_value() is False:
@ -1643,7 +1815,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.exc_edit_toolbar.addSeparator()
self.copy_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _('Copy Drill'))
self.delete_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/deleteshape32.png'),
self.delete_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete Drill"))
self.exc_edit_toolbar.addSeparator()
@ -1676,7 +1848,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.geo_edit_toolbar.addSeparator()
self.geo_cutpath_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/cutpath32.png'), _('Cut Path'))
self.geo_copy_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy Objects"))
self.geo_delete_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/deleteshape32.png'),
self.geo_delete_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete Shape"))
self.geo_transform_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
_("Transformations"))
@ -1684,6 +1856,25 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.geo_edit_toolbar.addSeparator()
self.geo_move_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move Objects"))
### Gerber Editor Toolbar ###
self.grb_select_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), _("Select"))
self.grb_add_pad_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/aperture32.png'), _("Add Pad"))
self.add_pad_ar_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/padarray32.png'), _('Add Pad Array'))
self.grb_add_track_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/track32.png'), _("Add Track"))
self.grb_add_region_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Add Region"))
self.grb_edit_toolbar.addSeparator()
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
self.grb_edit_toolbar.addSeparator()
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete"))
self.grb_transform_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
_("Transformations"))
self.grb_edit_toolbar.addSeparator()
self.aperture_move_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
### Snap Toolbar ###
# Snap GRID toolbar is always active to facilitate usage of measurements done on GRID
# self.addToolBar(self.snap_toolbar)
@ -1729,14 +1920,18 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.exc_edit_toolbar.setDisabled(True)
self.geo_edit_toolbar.setVisible(False)
self.geo_edit_toolbar.setDisabled(True)
self.grb_edit_toolbar.setVisible(False)
self.grb_edit_toolbar.setDisabled(True)
self.corner_snap_btn.setVisible(False)
self.snap_magnet.setVisible(False)
elif layout == 'Compact':
elif layout == 'compact':
self.exc_edit_toolbar.setVisible(True)
self.exc_edit_toolbar.setDisabled(True)
self.geo_edit_toolbar.setVisible(True)
self.geo_edit_toolbar.setDisabled(True)
self.grb_edit_toolbar.setVisible(False)
self.grb_edit_toolbar.setDisabled(True)
self.corner_snap_btn.setVisible(True)
self.snap_magnet.setVisible(True)
@ -1948,6 +2143,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Escape = Deselect All
if key == QtCore.Qt.Key_Escape or key == 'Escape':
self.app.on_deselect_all()
# if in full screen, exit to normal view
self.showNormal()
self.app.restore_toolbar_view()
self.splitter_left.setVisible(True)
self.app.toggle_fscreen = False
# try to disconnect the slot from Set Origin
try:
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_set_zero_click)
@ -1961,6 +2163,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
select.ui.plot_cb.toggle()
self.app.delete_selection_shape()
# New Geometry
if key == QtCore.Qt.Key_B:
self.app.new_gerber_object()
# Copy Object Name
if key == QtCore.Qt.Key_E:
self.app.object2editor()
@ -2111,7 +2317,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if self.app.geo_editor.active_tool.complete:
self.app.geo_editor.on_shape_complete()
self.app.inform.emit(_("[success]Done."))
self.app.inform.emit(_("[success] Done."))
# automatically make the selection tool active after completing current action
self.app.geo_editor.select_tool('select')
return
@ -2123,7 +2329,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if self.app.geo_editor.active_tool.complete:
self.app.geo_editor.on_shape_complete()
self.app.inform.emit(_("[success]Done."))
self.app.inform.emit(_("[success] Done."))
# automatically make the selection tool active after completing current action
self.app.geo_editor.select_tool('select')
@ -2131,7 +2337,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_Escape or key == 'Escape':
# TODO: ...?
# self.on_tool_select("select")
self.app.inform.emit(_("[WARNING_NOTCL]Cancelled."))
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled."))
self.app.geo_editor.delete_utility_geometry()
@ -2150,7 +2356,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.geo_editor.delete_selected()
self.app.geo_editor.replot()
# Move
# Rotate
if key == QtCore.Qt.Key_Space or key == 'Space':
self.app.geo_editor.transform_tool.on_rotate_key()
@ -2304,6 +2510,208 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Show Shortcut list
if key == 'F3':
self.app.on_shortcut_list()
elif self.app.call_source == 'grb_editor':
if modifiers == QtCore.Qt.ControlModifier:
# save (update) the current geometry and return to the App
if key == QtCore.Qt.Key_S or key == 'S':
self.app.editor2object()
return
# toggle the measurement tool
if key == QtCore.Qt.Key_M or key == 'M':
self.app.measurement_tool.run()
return
elif modifiers == QtCore.Qt.ShiftModifier:
pass
elif modifiers == QtCore.Qt.AltModifier:
# Transformation Tool
if key == QtCore.Qt.Key_R or key == 'R':
self.app.grb_editor.on_transform()
return
elif modifiers == QtCore.Qt.NoModifier:
# Abort the current action
if key == QtCore.Qt.Key_Escape or key == 'Escape':
# self.on_tool_select("select")
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled."))
self.app.grb_editor.delete_utility_geometry()
# self.app.grb_editor.plot_all()
self.app.grb_editor.active_tool.clean_up()
self.app.grb_editor.select_tool('select')
return
# Delete selected object if delete key event comes out of canvas
if key == 'Delete':
self.app.grb_editor.launched_from_shortcuts = True
if self.app.grb_editor.selected:
self.app.grb_editor.delete_selected()
self.app.grb_editor.plot_all()
else:
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to delete."))
return
# Delete aperture in apertures table if delete key event comes from the Selected Tab
if key == QtCore.Qt.Key_Delete:
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.on_aperture_delete()
return
if key == QtCore.Qt.Key_Minus or key == '-':
self.app.grb_editor.launched_from_shortcuts = True
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'],
[self.app.grb_editor.snap_x, self.app.grb_editor.snap_y])
return
if key == QtCore.Qt.Key_Equal or key == '=':
self.app.grb_editor.launched_from_shortcuts = True
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'],
[self.app.grb_editor.snap_x, self.app.grb_editor.snap_y])
return
# toggle display of Notebook area
if key == QtCore.Qt.Key_QuoteLeft or key == '`':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_toggle_notebook()
return
# Rotate
if key == QtCore.Qt.Key_Space or key == 'Space':
self.app.grb_editor.transform_tool.on_rotate_key()
# Switch to Project Tab
if key == QtCore.Qt.Key_1 or key == '1':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_select_tab('project')
return
# Switch to Selected Tab
if key == QtCore.Qt.Key_2 or key == '2':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_select_tab('selected')
return
# Switch to Tool Tab
if key == QtCore.Qt.Key_3 or key == '3':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_select_tab('tool')
return
# Add Array of pads
if key == QtCore.Qt.Key_A or key == 'A':
self.app.grb_editor.launched_from_shortcuts = True
self.app.inform.emit("Click on target point.")
self.app.ui.add_pad_ar_btn.setChecked(True)
self.app.grb_editor.x = self.app.mouse[0]
self.app.grb_editor.y = self.app.mouse[1]
self.app.grb_editor.select_tool('array')
return
# Scale Tool
if key == QtCore.Qt.Key_B or key == 'B':
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.select_tool('buffer')
return
# Copy
if key == QtCore.Qt.Key_C or key == 'C':
self.app.grb_editor.launched_from_shortcuts = True
if self.app.grb_editor.selected:
self.app.inform.emit(_("Click on target point."))
self.app.ui.aperture_copy_btn.setChecked(True)
self.app.grb_editor.on_tool_select('copy')
self.app.grb_editor.active_tool.set_origin(
(self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
else:
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy."))
return
# Grid Snap
if key == QtCore.Qt.Key_G or key == 'G':
self.app.grb_editor.launched_from_shortcuts = True
# make sure that the cursor shape is enabled/disabled, too
if self.app.grb_editor.options['grid_snap'] is True:
self.app.app_cursor.enabled = False
else:
self.app.app_cursor.enabled = True
self.app.ui.grid_snap_btn.trigger()
return
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.app.on_jump_to()
# Corner Snap
if key == QtCore.Qt.Key_K or key == 'K':
self.app.grb_editor.launched_from_shortcuts = True
self.app.ui.corner_snap_btn.trigger()
return
# Move
if key == QtCore.Qt.Key_M or key == 'M':
self.app.grb_editor.launched_from_shortcuts = True
if self.app.grb_editor.selected:
self.app.inform.emit(_("Click on target point."))
self.app.ui.aperture_move_btn.setChecked(True)
self.app.grb_editor.on_tool_select('move')
self.app.grb_editor.active_tool.set_origin(
(self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
else:
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move."))
return
# Add Region Tool
if key == QtCore.Qt.Key_N or key == 'N':
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.select_tool('region')
return
# Add Pad Tool
if key == QtCore.Qt.Key_P or key == 'P':
self.app.grb_editor.launched_from_shortcuts = True
self.app.inform.emit(_("Click on target point."))
self.app.ui.add_pad_ar_btn.setChecked(True)
self.app.grb_editor.x = self.app.mouse[0]
self.app.grb_editor.y = self.app.mouse[1]
self.app.grb_editor.select_tool('pad')
return
# Scale Tool
if key == QtCore.Qt.Key_S or key == 'S':
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.select_tool('scale')
return
# Add Track
if key == QtCore.Qt.Key_T or key == 'T':
self.app.grb_editor.launched_from_shortcuts = True
## Current application units in Upper Case
self.app.grb_editor.select_tool('track')
return
# Zoom Fit
if key == QtCore.Qt.Key_V or key == 'V':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_zoom_fit(None)
return
# Propagate to tool
response = None
if self.app.grb_editor.active_tool is not None:
response = self.app.grb_editor.active_tool.on_key(key=key)
if response is not None:
self.app.inform.emit(response)
# Show Shortcut list
if key == QtCore.Qt.Key_F3 or key == 'F3':
self.app.on_shortcut_list()
return
elif self.app.call_source == 'exc_editor':
if modifiers == QtCore.Qt.ControlModifier:
# save (update) the current geometry and return to the App
@ -2325,7 +2733,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_Escape or key == 'Escape':
# TODO: ...?
# self.on_tool_select("select")
self.app.inform.emit(_("[WARNING_NOTCL]Cancelled."))
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled."))
self.app.exc_editor.delete_utility_geometry()
@ -2342,7 +2750,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.exc_editor.delete_selected()
self.app.exc_editor.replot()
else:
self.app.inform.emit(_("[WARNING_NOTCL]Cancelled. Nothing selected to delete."))
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to delete."))
return
# Delete tools in tools table if delete key event comes from the Selected Tab
@ -2409,7 +2817,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.exc_editor.active_tool.set_origin(
(self.app.exc_editor.snap_x, self.app.exc_editor.snap_y))
else:
self.app.inform.emit(_("[WARNING_NOTCL]Cancelled. Nothing selected to copy."))
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy."))
return
# Add Drill Hole Tool
@ -2455,7 +2863,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.exc_editor.active_tool.set_origin(
(self.app.exc_editor.snap_x, self.app.exc_editor.snap_y))
else:
self.app.inform.emit(_("[WARNING_NOTCL]Cancelled. Nothing selected to move."))
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move."))
return
# Resize Tool
@ -2478,7 +2886,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if ok:
self.app.exc_editor.on_tool_add(tooldia=val)
self.app.inform.emit(
_("[success]Added new tool with dia: {dia} {units}").format(dia='%.4f' % float(val), units=str(self.units)))
_("[success] Added new tool with dia: {dia} {units}").format(dia='%.4f' % float(val), units=str(self.units)))
else:
self.app.inform.emit(
_("[WARNING_NOTCL] Adding Tool cancelled ..."))
@ -2524,35 +2932,37 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if self.filename == "":
self.app.inform.emit("Open cancelled.")
else:
if self.filename.lower().rpartition('.')[-1] in self.app.grb_list:
extension = self.filename.lower().rpartition('.')[-1]
if extension in self.app.grb_list:
self.app.worker_task.emit({'fcn': self.app.open_gerber,
'params': [self.filename]})
else:
event.ignore()
if self.filename.lower().rpartition('.')[-1] in self.app.exc_list:
if extension in self.app.exc_list:
self.app.worker_task.emit({'fcn': self.app.open_excellon,
'params': [self.filename]})
else:
event.ignore()
if self.filename.lower().rpartition('.')[-1] in self.app.gcode_list:
if extension in self.app.gcode_list:
self.app.worker_task.emit({'fcn': self.app.open_gcode,
'params': [self.filename]})
else:
event.ignore()
if self.filename.lower().rpartition('.')[-1] in self.app.svg_list:
if extension in self.app.svg_list:
object_type = 'geometry'
self.app.worker_task.emit({'fcn': self.app.import_svg,
'params': [self.filename, object_type, None]})
if self.filename.lower().rpartition('.')[-1] in self.app.dxf_list:
if extension in self.app.dxf_list:
object_type = 'geometry'
self.app.worker_task.emit({'fcn': self.app.import_dxf,
'params': [self.filename, object_type, None]})
if self.filename.lower().rpartition('.')[-1] in self.app.prj_list:
if extension in self.app.prj_list:
# self.app.open_project() is not Thread Safe
self.app.open_project(self.filename)
else:
@ -2561,16 +2971,17 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
event.ignore()
def closeEvent(self, event):
grect = self.geometry()
if self.app.save_in_progress:
self.app.inform.emit(_("[WARNING_NOTCL] Application is saving the project. Please wait ..."))
else:
grect = self.geometry()
# self.splitter.sizes()[0] is actually the size of the "notebook"
if not self.isMaximized():
self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height(), self.splitter.sizes()[0])
# self.splitter.sizes()[0] is actually the size of the "notebook"
if not self.isMaximized():
self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height(), self.splitter.sizes()[0])
self.final_save.emit()
if self.app.should_we_quit is False:
event.ignore()
self.final_save.emit()
event.ignore()
class GeneralPreferencesUI(QtWidgets.QWidget):
@ -3034,8 +3445,8 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
)
self.layout_combo = FCComboBox()
# don't translate the QCombo items as they are used in QSettings and identified by name
self.layout_combo.addItem("Standard")
self.layout_combo.addItem("Compact")
self.layout_combo.addItem("standard")
self.layout_combo.addItem("compact")
# Set the current index for layout_combo
settings = QSettings("Open Source", "FlatCAM")
@ -3124,24 +3535,24 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
def handle_clear(self):
msgbox = QtWidgets.QMessageBox()
# msgbox.setText("<B>Save changes ...</B>")
msgbox.setText(_("Are you sure you want to delete the GUI Settings? "
"\n")
)
msgbox.setWindowTitle(_("Clear GUI Settings"))
msgbox.setWindowIcon(QtGui.QIcon('share/trash32.png'))
msgbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.No)
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
response = msgbox.exec_()
msgbox.setDefaultButton(bt_no)
msgbox.exec_()
response = msgbox.clickedButton()
if response == QtWidgets.QMessageBox.Yes:
if response == bt_yes:
settings = QSettings("Open Source", "FlatCAM")
for key in settings.allKeys():
settings.remove(key)
# This will write the setting to the platform specific storage.
del settings
self.app.inform.emit(_("[success] GUI settings deleted ..."))
class GeneralAppPrefGroupUI(OptionsGroupUI):
@ -3267,6 +3678,25 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
_( "Check this box if you want to have toolTips displayed\n"
"when hovering with mouse over items throughout the App.")
)
self.worker_number_label = QtWidgets.QLabel(_('Workers number:'))
self.worker_number_label.setToolTip(
_("The number of Qthreads made available to the App.\n"
"A bigger number may finish the jobs more quickly but\n"
"depending on your computer speed, may make the App\n"
"unresponsive. Can have a value between 2 and 16.\n"
"Default value is 2.\n"
"After change, it will be applied at next App start.")
)
self.worker_number_sb = FCSpinner()
self.worker_number_sb.setToolTip(
_("The number of Qthreads made available to the App.\n"
"A bigger number may finish the jobs more quickly but\n"
"depending on your computer speed, may make the App\n"
"unresponsive. Can have a value between 2 and 16.\n"
"Default value is 2.\n"
"After change, it will be applied at next App start.")
)
self.worker_number_sb.set_range(2, 16)
# Just to add empty rows
self.spacelabel = QtWidgets.QLabel('')
@ -3287,6 +3717,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
self.form_box.addRow(self.project_startup_label, self.project_startup_cb)
self.form_box.addRow(self.project_autohide_label, self.project_autohide_cb)
self.form_box.addRow(self.toggle_tooltips_label, self.toggle_tooltips_cb)
self.form_box.addRow(self.worker_number_label, self.worker_number_sb)
self.form_box.addRow(self.spacelabel, self.spacelabel)
# Add the QFormLayout that holds the Application general defaults
@ -4787,7 +5219,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
self.ncc_tool_dia_entry = FCEntry()
grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
nccoverlabel = QtWidgets.QLabel(_('Overlap:'))
nccoverlabel = QtWidgets.QLabel(_('Overlap Rate:'))
nccoverlabel.setToolTip(
_( "How much (fraction) of the tool width to overlap each tool pass.\n"
"Example:\n"
@ -4929,6 +5361,15 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
self.gaps_combo.addItem(it)
self.gaps_combo.setStyleSheet('background-color: rgb(255,255,255)')
# Surrounding convex box shape
self.convex_box = FCCheckBox()
self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
self.convex_box_label.setToolTip(
_("Create a convex shape surrounding the entire PCB.")
)
grid0.addWidget(self.convex_box_label, 4, 0)
grid0.addWidget(self.convex_box, 4, 1)
self.layout.addStretch()
@ -5023,7 +5464,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.painttooldia_entry, 0, 1)
# Overlap
ovlabel = QtWidgets.QLabel(_('Overlap:'))
ovlabel = QtWidgets.QLabel(_('Overlap Rate:'))
ovlabel.setToolTip(
_("How much (fraction) of the tool\n"
"width to overlap each tool pass.")

View File

@ -490,6 +490,106 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
class FCTextAreaExtended(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super(FCTextAreaExtended, self).__init__(parent)
self.completer = MyCompleter()
self.model = QtCore.QStringListModel()
self.completer.setModel(self.model)
self.set_model_data(keyword_list=[])
self.completer.insertText.connect(self.insertCompletion)
self.completer_enable = False
def set_model_data(self, keyword_list):
self.model.setStringList(keyword_list)
def insertCompletion(self, completion):
tc = self.textCursor()
extra = (len(completion) - len(self.completer.completionPrefix()))
# don't insert if the word is finished but add a space instead
if extra == 0:
tc.insertText(' ')
self.completer.popup().hide()
return
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
# add a space after inserting the word
tc.insertText(' ')
self.setTextCursor(tc)
self.completer.popup().hide()
def focusInEvent(self, event):
if self.completer:
self.completer.setWidget(self)
QTextEdit.focusInEvent(self, event)
def set_value(self, val):
self.setText(val)
def get_value(self):
self.toPlainText()
def insertFromMimeData(self, data):
"""
Reimplemented such that when SHIFT is pressed and doing click Paste in the contextual menu, the '\' symbol
is replaced with the '/' symbol. That's because of the difference in path separators in Windows and TCL
:param data:
:return:
"""
modifier = QtWidgets.QApplication.keyboardModifiers()
if modifier == Qt.ShiftModifier:
text = data.text()
text = text.replace('\\', '/')
self.insertPlainText(text)
else:
self.insertPlainText(data.text())
def keyPressEvent(self, event):
"""
Reimplemented so the CTRL + SHIFT + V shortcut key combo will paste the text but replacing '\' with '/'
:param event:
:return:
"""
key = event.key()
modifier = QtWidgets.QApplication.keyboardModifiers()
if modifier & Qt.ControlModifier and modifier & Qt.ShiftModifier:
if key == QtCore.Qt.Key_V:
clipboard = QtWidgets.QApplication.clipboard()
clip_text = clipboard.text()
clip_text = clip_text.replace('\\', '/')
self.insertPlainText(clip_text)
tc = self.textCursor()
if (key == Qt.Key_Tab or key == Qt.Key_Enter or key == Qt.Key_Return) and self.completer.popup().isVisible():
self.completer.insertText.emit(self.completer.getSelected())
self.completer.setCompletionMode(QCompleter.PopupCompletion)
return
else:
super(FCTextAreaExtended, self).keyPressEvent(event)
if self.completer_enable:
tc.select(QTextCursor.WordUnderCursor)
cr = self.cursorRect()
if len(tc.selectedText()) > 0:
self.completer.setCompletionPrefix(tc.selectedText())
popup = self.completer.popup()
popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
cr.setWidth(self.completer.popup().sizeHintForColumn(0)
+ self.completer.popup().verticalScrollBar().sizeHint().width())
self.completer.complete(cr)
else:
self.completer.popup().hide()
class FCComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None, callback=None):
@ -595,9 +695,12 @@ class FCTab(QtWidgets.QTabWidget):
class FCDetachableTab(QtWidgets.QTabWidget):
# From here: https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
def __init__(self, protect=None, protect_by_name=None, parent=None):
"""
From here:
https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
"""
def __init__(self, protect=None, protect_by_name=None, parent=None):
super().__init__()
self.tabBar = self.FCTabBar(self)
@ -639,25 +742,38 @@ class FCDetachableTab(QtWidgets.QTabWidget):
self.removeTab(currentIndex)
def closeTab(self, currentIndex):
"""
Slot connected to the tabCloseRequested signal
:param currentIndex:
:return:
"""
self.removeTab(currentIndex)
def protectTab(self, currentIndex):
# self.FCTabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
self.tabBar.setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
##
# The default movable functionality of QTabWidget must remain disabled
# so as not to conflict with the added features
def setMovable(self, movable):
"""
The default movable functionality of QTabWidget must remain disabled
so as not to conflict with the added features
:param movable:
:return:
"""
pass
##
# Move a tab from one position (index) to another
#
# @param fromIndex the original index location of the tab
# @param toIndex the new index location of the tab
@pyqtSlot(int, int)
def moveTab(self, fromIndex, toIndex):
"""
Move a tab from one position (index) to another
:param fromIndex: the original index location of the tab
:param toIndex: the new index location of the tab
:return:
"""
widget = self.widget(fromIndex)
icon = self.tabIcon(fromIndex)
text = self.tabText(fromIndex)
@ -666,15 +782,16 @@ class FCDetachableTab(QtWidgets.QTabWidget):
self.insertTab(toIndex, widget, icon, text)
self.setCurrentIndex(toIndex)
##
# Detach the tab by removing it's contents and placing them in
# a DetachedTab window
#
# @param index the index location of the tab to be detached
# @param point the screen position for creating the new DetachedTab window
@pyqtSlot(int, QtCore.QPoint)
def detachTab(self, index, point):
"""
Detach the tab by removing it's contents and placing them in
a DetachedTab window
:param index: the index location of the tab to be detached
:param point: the screen position for creating the new DetachedTab window
:return:
"""
self.old_index = index
# Get the tab content and add name FlatCAM to the tab so we know on which app is this tab linked
@ -699,20 +816,20 @@ class FCDetachableTab(QtWidgets.QTabWidget):
detachedTab.move(point)
detachedTab.show()
# Create a reference to maintain access to the detached tab
self.detachedTabs[name] = detachedTab
##
# Re-attach the tab by removing the content from the DetachedTab window,
# closing it, and placing the content back into the DetachableTabWidget
#
# @param contentWidget the content widget from the DetachedTab window
# @param name the name of the detached tab
# @param icon the window icon for the detached tab
# @param insertAt insert the re-attached tab at the given index
def attachTab(self, contentWidget, name, icon, insertAt=None):
"""
Re-attach the tab by removing the content from the DetachedTab window,
closing it, and placing the content back into the DetachableTabWidget
:param contentWidget: the content widget from the DetachedTab window
:param name: the name of the detached tab
:param icon: the window icon for the detached tab
:param insertAt: insert the re-attached tab at the given index
:return:
"""
# Make the content widget a child of this widget
contentWidget.setParent(self)
@ -773,11 +890,13 @@ class FCDetachableTab(QtWidgets.QTabWidget):
if index > -1:
self.setCurrentIndex(insert_index) if self.use_old_index else self.setCurrentIndex(index)
##
# Remove the tab with the given name, even if it is detached
#
# @param name the name of the tab to be removed
def removeTabByName(self, name):
"""
Remove the tab with the given name, even if it is detached
:param name: the name of the tab to be removed
:return:
"""
# Remove the tab if it is attached
attached = False
@ -798,17 +917,18 @@ class FCDetachableTab(QtWidgets.QTabWidget):
del self.detachedTabs[key]
break
##
# Handle dropping of a detached tab inside the DetachableTabWidget
#
# @param name the name of the detached tab
# @param index the index of an existing tab (if the tab bar
# determined that the drop occurred on an
# existing tab)
# @param dropPos the mouse cursor position when the drop occurred
@QtCore.pyqtSlot(str, int, QtCore.QPoint)
def detachedTabDrop(self, name, index, dropPos):
"""
Handle dropping of a detached tab inside the DetachableTabWidget
:param name: the name of the detached tab
:param index: the index of an existing tab (if the tab bar
# determined that the drop occurred on an
# existing tab)
:param dropPos: the mouse cursor position when the drop occurred
:return:
"""
# If the drop occurred on an existing tab, insert the detached
# tab at the existing tab's location
@ -848,10 +968,12 @@ class FCDetachableTab(QtWidgets.QTabWidget):
# automatically
self.detachedTabs[name].close()
##
# Close all tabs that are currently detached.
def closeDetachedTabs(self):
"""
Close all tabs that are currently detached.
:return:
"""
listOfDetachedTabs = []
for key in self.detachedTabs:
@ -860,11 +982,12 @@ class FCDetachableTab(QtWidgets.QTabWidget):
for detachedTab in listOfDetachedTabs:
detachedTab.close()
##
# When a tab is detached, the contents are placed into this QMainWindow. The tab
# can be re-attached by closing the dialog or by dragging the window into the tab bar
class FCDetachedTab(QtWidgets.QMainWindow):
"""
When a tab is detached, the contents are placed into this QMainWindow. The tab
can be re-attached by closing the dialog or by dragging the window into the tab bar
"""
onCloseSignal = pyqtSignal(QtWidgets.QWidget, str, QtGui.QIcon)
onDropSignal = pyqtSignal(str, QtCore.QPoint)
@ -882,42 +1005,46 @@ class FCDetachableTab(QtWidgets.QTabWidget):
self.installEventFilter(self.windowDropFilter)
self.windowDropFilter.onDropSignal.connect(self.windowDropSlot)
##
# Handle a window drop event
#
# @param dropPos the mouse cursor position of the drop
@QtCore.pyqtSlot(QtCore.QPoint)
def windowDropSlot(self, dropPos):
"""
Handle a window drop event
:param dropPos: the mouse cursor position of the drop
:return:
"""
self.onDropSignal.emit(self.objectName(), dropPos)
##
# If the window is closed, emit the onCloseSignal and give the
# content widget back to the DetachableTabWidget
#
# @param event a close event
def closeEvent(self, event):
"""
If the window is closed, emit the onCloseSignal and give the
content widget back to the DetachableTabWidget
:param event: a close event
:return:
"""
self.onCloseSignal.emit(self.contentWidget, self.objectName(), self.windowIcon())
##
# An event filter class to detect a QMainWindow drop event
class WindowDropFilter(QtCore.QObject):
"""
An event filter class to detect a QMainWindow drop event
"""
onDropSignal = pyqtSignal(QtCore.QPoint)
def __init__(self):
QtCore.QObject.__init__(self)
self.lastEvent = None
##
# Detect a QMainWindow drop event by looking for a NonClientAreaMouseMove (173)
# event that immediately follows a Move event
#
# @param obj the object that generated the event
# @param event the current event
def eventFilter(self, obj, event):
"""
Detect a QMainWindow drop event by looking for a NonClientAreaMouseMove (173)
event that immediately follows a Move event
:param obj: the object that generated the event
:param event: the current event
:return:
"""
# If a NonClientAreaMouseMove (173) event immediately follows a Move event...
if self.lastEvent == QtCore.QEvent.Move and event.type() == 173:
@ -951,19 +1078,24 @@ class FCDetachableTab(QtWidgets.QTabWidget):
self.mouseCursor = QtGui.QCursor()
self.dragInitiated = False
# Send the onDetachTabSignal when a tab is double clicked
#
# @param event a mouse double click event
def mouseDoubleClickEvent(self, event):
"""
Send the onDetachTabSignal when a tab is double clicked
:param event: a mouse double click event
:return:
"""
event.accept()
self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
# Set the starting position for a drag event when the mouse button is pressed
#
# @param event a mouse press event
def mousePressEvent(self, event):
"""
Set the starting position for a drag event when the mouse button is pressed
:param event: a mouse press event
:return:
"""
if event.button() == QtCore.Qt.LeftButton:
self.dragStartPos = event.pos()
@ -974,14 +1106,15 @@ class FCDetachableTab(QtWidgets.QTabWidget):
QtWidgets.QTabBar.mousePressEvent(self, event)
# Determine if the current movement is a drag. If it is, convert it into a QDrag. If the
# drag ends inside the tab bar, emit an onMoveTabSignal. If the drag ends outside the tab
# bar, emit an onDetachTabSignal.
#
# @param event a mouse move event
def mouseMoveEvent(self, event):
"""
Determine if the current movement is a drag. If it is, convert it into a QDrag. If the
drag ends inside the tab bar, emit an onMoveTabSignal. If the drag ends outside the tab
bar, emit an onDetachTabSignal.
:param event: a mouse move event
:return:
"""
# Determine if the current movement is detected as a drag
if not self.dragStartPos.isNull() and ((event.pos() - self.dragStartPos).manhattanLength() < QtWidgets.QApplication.startDragDistance()):
self.dragInitiated = True
@ -1038,29 +1171,40 @@ class FCDetachableTab(QtWidgets.QTabWidget):
else:
QtWidgets.QTabBar.mouseMoveEvent(self, event)
# Determine if the drag has entered a tab position from another tab position
#
# @param event a drag enter event
def dragEnterEvent(self, event):
"""
Determine if the drag has entered a tab position from another tab position
:param event: a drag enter event
:return:
"""
mimeData = event.mimeData()
# formats = mcd imeData.formats()
# if formats.contains('action') and mimeData.data('action') == 'application/tab-detach':
# event.acceptProposedAction()
# if formats.contains('action') and mimeData.data('action') == 'application/tab-detach':
# event.acceptProposedAction()
QtWidgets.QTabBar.dragMoveEvent(self, event)
# Get the position of the end of the drag
#
# @param event a drop event
def dropEvent(self, event):
"""
Get the position of the end of the drag
:param event: a drop event
:return:
"""
self.dragDropedPos = event.pos()
QtWidgets.QTabBar.dropEvent(self, event)
# Determine if the detached tab drop event occurred on an existing tab,
# then send the event to the DetachableTabWidget
def detachedTabDrop(self, name, dropPos):
"""
Determine if the detached tab drop event occurred on an existing tab,
then send the event to the DetachableTabWidget
:param name:
:param dropPos:
:return:
"""
tabDropPos = self.mapFromGlobal(dropPos)
index = self.tabAt(tabDropPos)
@ -1192,9 +1336,64 @@ class FCTable(QtWidgets.QTableWidget):
self.addAction(action)
action.triggered.connect(call_function)
class SpinBoxDelegate(QtWidgets.QItemDelegate):
def __init__(self, units):
super(SpinBoxDelegate, self).__init__()
self.units = units
self.current_value = None
def createEditor(self, parent, option, index):
editor = QtWidgets.QDoubleSpinBox(parent)
editor.setMinimum(-999.9999)
editor.setMaximum(999.9999)
if self.units == 'MM':
editor.setDecimals(2)
else:
editor.setDecimals(3)
return editor
def setEditorData(self, spinBox, index):
try:
value = float(index.model().data(index, Qt.EditRole))
except ValueError:
value = self.current_value
# return
spinBox.setValue(value)
def setModelData(self, spinBox, model, index):
spinBox.interpretText()
value = spinBox.value()
self.current_value = value
model.setData(index, value, Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def setDecimals(self, spinbox, digits):
spinbox.setDecimals(digits)
class FCSpinner(QtWidgets.QSpinBox):
def __init__(self, parent=None):
super(FCSpinner, self).__init__(parent)
self.readyToEdit = True
def mousePressEvent(self, e, parent=None):
super(FCSpinner, self).mousePressEvent(e) # required to deselect on 2e click
if self.readyToEdit:
self.lineEdit().selectAll()
self.readyToEdit = False
def focusOutEvent(self, e):
super(FCSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
self.lineEdit().deselect()
self.readyToEdit = True
def get_value(self):
return str(self.value())
@ -1207,6 +1406,9 @@ class FCSpinner(QtWidgets.QSpinBox):
return
self.setValue(k)
def set_range(self, min_val, max_val):
self.setRange(min_val, max_val)
# def sizeHint(self):
# default_hint_size = super(FCSpinner, self).sizeHint()
# return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
@ -1243,7 +1445,7 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
self.setDecimals(val)
def set_range(self, min_val, max_val):
self.setRange(self, min_val, max_val)
self.setRange(min_val, max_val)
class Dialog_box(QtWidgets.QWidget):
@ -1261,7 +1463,20 @@ class Dialog_box(QtWidgets.QWidget):
dialog_box.setFixedWidth(290)
self.setWindowIcon(icon)
self.location, self.ok = dialog_box.getText(self, title, label)
self.location, self.ok = dialog_box.getText(self, title, label, text="0, 0")
self.readyToEdit = True
def mousePressEvent(self, e, parent=None):
super(Dialog_box, self).mousePressEvent(e) # required to deselect on 2e click
if self.readyToEdit:
self.lineEdit().selectAll()
self.readyToEdit = False
def focusOutEvent(self, e):
super(Dialog_box, self).focusOutEvent(e) # required to remove cursor on focusOut
self.lineEdit().deselect()
self.readyToEdit = True
class _BrowserTextEdit(QTextEdit):
@ -1318,9 +1533,18 @@ class _ExpandableTextEdit(QTextEdit):
def insertCompletion(self, completion):
tc = self.textCursor()
extra = (len(completion) - len(self.completer.completionPrefix()))
# don't insert if the word is finished but add a space instead
if extra == 0:
tc.insertText(' ')
self.completer.popup().hide()
return
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
# add a space after inserting the word
tc.insertText(' ')
self.setTextCursor(tc)
self.completer.popup().hide()
@ -1333,6 +1557,13 @@ class _ExpandableTextEdit(QTextEdit):
"""
Catch keyboard events. Process Enter, Up, Down
"""
key = event.key()
if (key == Qt.Key_Tab or key == Qt.Key_Return or key == Qt.Key_Enter) and self.completer.popup().isVisible():
self.completer.insertText.emit(self.completer.getSelected())
self.completer.setCompletionMode(QCompleter.PopupCompletion)
return
if event.matches(QKeySequence.InsertParagraphSeparator):
text = self.toPlainText()
if self._termWidget.is_command_complete(text):
@ -1363,10 +1594,6 @@ class _ExpandableTextEdit(QTextEdit):
return self._termWidget.browser().keyPressEvent(event)
tc = self.textCursor()
if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
self.completer.insertText.emit(self.completer.getSelected())
self.completer.setCompletionMode(QCompleter.PopupCompletion)
return
QTextEdit.keyPressEvent(self, event)
tc.select(QTextCursor.WordUnderCursor)

View File

@ -101,7 +101,7 @@ class ObjectUI(QtWidgets.QWidget):
self.scale_button.setToolTip(
_("Perform scaling operation.")
)
self.scale_button.setFixedWidth(50)
self.scale_button.setFixedWidth(70)
self.scale_grid.addWidget(self.scale_button, 0, 2)
#### Offset ####
@ -128,7 +128,7 @@ class ObjectUI(QtWidgets.QWidget):
self.offset_button.setToolTip(
_("Perform the offset operation.")
)
self.offset_button.setFixedWidth(50)
self.offset_button.setFixedWidth(70)
self.offset_grid.addWidget(self.offset_button, 0, 2)
layout.addStretch()
@ -244,79 +244,8 @@ class GerberObjectUI(ObjectUI):
_("Mark the aperture instances on canvas."))
# self.apertures_table.setColumnHidden(5, True)
#### Aperture Scale ####
self.transform_aperture_grid = QtWidgets.QGridLayout()
self.custom_box.addLayout(self.transform_aperture_grid)
# Scale Aperture Factor
self.scale_aperture_label = QtWidgets.QLabel(_('Scale Factor:'))
self.scale_aperture_label.setToolTip(
_("Change the size of the selected apertures.\n"
"Factor by which to multiply\n"
"geometric features of this object.")
)
self.scale_aperture_label.setFixedWidth(90)
self.transform_aperture_grid.addWidget(self.scale_aperture_label, 0, 0)
self.scale_aperture_entry = FloatEntry2()
self.transform_aperture_grid.addWidget(self.scale_aperture_entry, 0, 1)
# Scale Button
self.scale_aperture_button = QtWidgets.QPushButton(_('Scale'))
self.scale_aperture_button.setToolTip(
_("Perform scaling operation on the selected apertures.")
)
self.scale_aperture_button.setFixedWidth(50)
self.transform_aperture_grid.addWidget(self.scale_aperture_button, 0, 2)
# Buffer Aperture Factor
self.buffer_aperture_label = QtWidgets.QLabel(_('Buffer Factor:'))
self.buffer_aperture_label.setToolTip(
_("Change the size of the selected apertures.\n"
"Factor by which to expand/shrink\n"
"geometric features of this object.")
)
self.buffer_aperture_label.setFixedWidth(90)
self.transform_aperture_grid.addWidget(self.buffer_aperture_label, 1, 0)
self.buffer_aperture_entry = FloatEntry2()
self.transform_aperture_grid.addWidget(self.buffer_aperture_entry, 1, 1)
# Buffer Button
self.buffer_aperture_button = QtWidgets.QPushButton(_('Buffer'))
self.buffer_aperture_button.setToolTip(
_("Perform buffer operation on the selected apertures.")
)
self.buffer_aperture_button.setFixedWidth(50)
self.transform_aperture_grid.addWidget(self.buffer_aperture_button, 1, 2)
new_hlay = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(new_hlay)
self.new_grb_label = QtWidgets.QLabel(_("<b>Generate new Gerber Object:</b>"))
self.new_grb_label.setToolTip(
_("Will generate a new Gerber object from the changed apertures.")
)
new_hlay.addWidget(self.new_grb_label)
new_hlay.addStretch()
self.new_grb_button = FCButton(_('Go'))
self.new_grb_button.setToolTip(
_("Will generate a new Gerber object from the changed apertures.\n"
"This new object can then be isolated etc."))
self.new_grb_button.setFixedWidth(50)
new_hlay.addWidget(self.new_grb_button)
# start with apertures table hidden
self.apertures_table.setVisible(False)
self.scale_aperture_label.setVisible(False)
self.scale_aperture_entry.setVisible(False)
self.scale_aperture_button.setVisible(False)
self.buffer_aperture_label.setVisible(False)
self.buffer_aperture_entry.setVisible(False)
self.buffer_aperture_button.setVisible(False)
# Isolation Routing
self.isolation_routing_label = QtWidgets.QLabel(_("<b>Isolation Routing:</b>"))
@ -1601,13 +1530,6 @@ class CNCObjectUI(ObjectUI):
"a Custom Toolchange GCode (macro)."
)
)
cnclay.addWidget(self.toolchange_cb)
self.toolch_ois = OptionalInputSection(self.toolchange_cb, [self.toolchangelabel, self.toolchange_text])
cnclay.addStretch()
cnclay1 = QtWidgets.QHBoxLayout()
self.cnc_box.addLayout(cnclay1)
# Variable list
self.tc_variable_combo = FCComboBox()
@ -1618,7 +1540,6 @@ class CNCObjectUI(ObjectUI):
"They have to be surrounded by the '%' symbol"
)
)
cnclay1.addWidget(self.tc_variable_combo)
# Populate the Combo Box
variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
@ -1638,15 +1559,12 @@ class CNCObjectUI(ObjectUI):
self.tc_variable_combo.setItemData(11, _("dwelltime = time to dwell to allow the spindle to reach it's set RPM"),
Qt.ToolTipRole)
cnclay1.addStretch()
cnclay.addWidget(self.toolchange_cb)
cnclay.addStretch()
cnclay.addWidget(self.tc_variable_combo)
# Insert Variable into the Toolchange G-Code Text Box
# self.tc_insert_buton = FCButton("Insert")
# self.tc_insert_buton.setToolTip(
# "Insert the variable in the GCode Box\n"
# "surrounded by the '%' symbol."
# )
# cnclay1.addWidget(self.tc_insert_buton)
self.toolch_ois = OptionalInputSection(self.toolchange_cb,
[self.toolchangelabel, self.toolchange_text, self.tc_variable_combo])
h_lay = QtWidgets.QHBoxLayout()
h_lay.setAlignment(QtCore.Qt.AlignVCenter)

View File

@ -148,8 +148,11 @@ class PlotCanvas(QtCore.QObject):
def vis_connect(self, event_name, callback):
return getattr(self.vispy_canvas.events, event_name).connect(callback)
def vis_disconnect(self, event_name, callback):
getattr(self.vispy_canvas.events, event_name).disconnect(callback)
def vis_disconnect(self, event_name, callback=None):
if callback is None:
getattr(self.vispy_canvas.events, event_name).disconnect()
else:
getattr(self.vispy_canvas.events, event_name).disconnect(callback)
def zoom(self, factor, center=None):
"""

View File

@ -437,8 +437,11 @@ class TextGroup(object):
:param value: bool
"""
self._visible = value
self._collection.data[self._index]['visible'] = value
try:
self._collection.data[self._index]['visible'] = value
except KeyError:
print("VisPyVisuals.TextGroup.visible --> KeyError")
pass
self._collection.redraw()

View File

@ -56,9 +56,9 @@ def svgparselength(lengthstr):
def path2shapely(path, object_type, res=1.0):
"""
Converts an svg.path.Path into a Shapely
LinearRing or LinearString.
Polygon or LinearString.
:rtype : LinearRing
:rtype : Polygon
:rtype : LineString
:param path: svg.path.Path instance
:param res: Resolution (minimum step along path)
@ -68,6 +68,7 @@ def path2shapely(path, object_type, res=1.0):
points = []
geometry = []
geo_element = None
rings = []
for component in path:
@ -109,35 +110,30 @@ def path2shapely(path, object_type, res=1.0):
# Move
if isinstance(component, Move):
if object_type == 'geometry':
geo_element = LineString(points)
elif object_type == 'gerber':
# Didn't worked out using Polygon because if there is a large outline it will envelope everything
# and create issues with intersections. I will let the parameter obj_type present though
# geo_element = Polygon(points)
geo_element = LineString(points)
else:
log.error("[ERROR]: Not a valid target object.")
if not points:
continue
else:
geometry.append(geo_element)
rings.append(points)
points = []
continue
log.warning("I don't know what this is: %s" % str(component))
continue
# if there are still points in points then add them as a LineString
if points:
geo_element = LineString(points)
geometry.append(geo_element)
points = []
# if there are still points in points then add them to the last ring
if points:
rings.append(points)
if len(rings) > 0:
if len(rings) == 1:
# Polygons are closed and require more than 2 points
if Point(rings[0][0]).almost_equals(Point(rings[0][-1])) and len(rings[0]) > 2:
geo_element = Polygon(rings[0])
else:
geo_element = LineString(rings[0])
else:
geo_element = Polygon(rings[0], rings[1:])
geometry.append(geo_element)
# if path.closed:
# return Polygon(points).buffer(0)
# # return LinearRing(points)
# else:
# return LineString(points)
return geometry
def svgrect2shapely(rect, n_points=32):
@ -362,7 +358,7 @@ def getsvggeo(node, object_type):
if tr[0] == 'translate':
geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
elif tr[0] == 'scale':
geo = [scale(geoi, tr[0], tr[1], origin=(0, 0))
geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
for geoi in geo]
elif tr[0] == 'rotate':
geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
@ -459,7 +455,7 @@ def getsvgtext(node, object_type, units='MM'):
if tr[0] == 'translate':
geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
elif tr[0] == 'scale':
geo = [scale(geoi, tr[0], tr[1], origin=(0, 0))
geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
for geoi in geo]
elif tr[0] == 'rotate':
geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
@ -592,7 +588,7 @@ def parse_svg_transform(trstr):
trlist.append([
'translate',
float(match.group(1)),
float(match.group(2)) if match.group else 0.0
float(match.group(2)) if (match.group(2) is not None) else 0.0
])
trstr = trstr[len(match.group(0)):].strip(' ')
continue
@ -600,9 +596,9 @@ def parse_svg_transform(trstr):
match = re.search(r'^' + scale_re_str, trstr)
if match:
trlist.append([
'translate',
'scale',
float(match.group(1)),
float(match.group(2)) if not None else float(match.group(1))
float(match.group(2)) if (match.group(2) is not None) else float(match.group(1))
])
trstr = trstr[len(match.group(0)):].strip(' ')
continue

View File

@ -304,7 +304,7 @@ class ToolCalculator(FlatCAMTool):
try:
tip_diameter = float(self.tipDia_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -315,7 +315,7 @@ class ToolCalculator(FlatCAMTool):
try:
half_tip_angle = float(self.tipAngle_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
half_tip_angle /= 2
@ -327,7 +327,7 @@ class ToolCalculator(FlatCAMTool):
try:
cut_depth = float(self.cutDepth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -342,7 +342,7 @@ class ToolCalculator(FlatCAMTool):
try:
mm_val = float(self.mm_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
self.inch_entry.set_value('%.6f' % (mm_val / 25.4))
@ -355,7 +355,7 @@ class ToolCalculator(FlatCAMTool):
try:
inch_val = float(self.inch_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
self.mm_entry.set_value('%.6f' % (inch_val * 25.4))
@ -369,7 +369,7 @@ class ToolCalculator(FlatCAMTool):
try:
length = float(self.pcblength_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -380,7 +380,7 @@ class ToolCalculator(FlatCAMTool):
try:
width = float(self.pcbwidth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -391,7 +391,7 @@ class ToolCalculator(FlatCAMTool):
try:
density = float(self.cdensity_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -402,7 +402,7 @@ class ToolCalculator(FlatCAMTool):
try:
copper = float(self.growth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return

View File

@ -110,6 +110,14 @@ class CutOut(FlatCAMTool):
# 2tb - 2*top + 2*bottom
# 8 - 2*left + 2*right +2*top + 2*bottom
# Surrounding convex box shape
self.convex_box = FCCheckBox()
self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
self.convex_box_label.setToolTip(
_("Create a convex shape surrounding the entire PCB.")
)
form_layout.addRow(self.convex_box_label, self.convex_box)
## Title2
title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
title_param_label.setToolTip(
@ -310,7 +318,8 @@ class CutOut(FlatCAMTool):
self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"]))
self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
self.gaps.set_value(4)
self.gaps.set_value(self.app.defaults["tools_gaps_ff"])
self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
self.gapFinished.connect(self.on_gap_finished)
@ -326,11 +335,11 @@ class CutOut(FlatCAMTool):
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]There is no object selected for Cutout.\nSelect one and try again."))
self.app.inform.emit(_("[ERROR_NOTCL] There is no object selected for Cutout.\nSelect one and try again."))
return
try:
@ -346,8 +355,8 @@ class CutOut(FlatCAMTool):
if 0 in {dia}:
self.app.inform.emit(_("[WARNING_NOTCL]Tool Diameter is zero value. Change it to a positive integer."))
return "Tool Diameter is zero value. Change it to a positive integer."
self.app.inform.emit(_("[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
try:
margin = float(self.margin.get_value())
@ -388,6 +397,8 @@ class CutOut(FlatCAMTool):
"and after that perform Cutout."))
return
convex_box = self.convex_box.get_value()
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) + margin
@ -402,8 +413,12 @@ class CutOut(FlatCAMTool):
cutout_obj.options["name"] += "_cutout"
else:
def geo_init(geo_obj, app_obj):
geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
if convex_box:
geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
else:
geo = cutout_obj.solid_geometry
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
@ -465,11 +480,11 @@ class CutOut(FlatCAMTool):
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]Object not found: %s") % cutout_obj)
self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % cutout_obj)
try:
dia = float(self.dia.get_value())
@ -483,8 +498,8 @@ class CutOut(FlatCAMTool):
return
if 0 in {dia}:
self.app.inform.emit(_("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer."))
return "Tool Diameter is zero value. Change it to a positive integer."
self.app.inform.emit(_("[ERROR_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
try:
margin = float(self.margin.get_value())
@ -603,8 +618,8 @@ class CutOut(FlatCAMTool):
return
if 0 in {self.cutting_dia}:
self.app.inform.emit(_("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer."))
return "Tool Diameter is zero value. Change it to a positive integer."
self.app.inform.emit(_("[ERROR_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
try:
self.cutting_gapsize = float(self.gapsize.get_value())
@ -652,11 +667,11 @@ class CutOut(FlatCAMTool):
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve Geoemtry object: %s") % name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]Geometry object for manual cutout not found: %s") % cutout_obj)
self.app.inform.emit(_("[ERROR_NOTCL] Geometry object for manual cutout not found: %s") % cutout_obj)
return
# use the snapped position as reference
@ -683,16 +698,16 @@ class CutOut(FlatCAMTool):
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve Gerber object: %s") % name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Gerber object: %s") % name)
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]There is no Gerber object selected for Cutout.\n"
self.app.inform.emit(_("[ERROR_NOTCL] There is no Gerber object selected for Cutout.\n"
"Select one and try again."))
return
if not isinstance(cutout_obj, FlatCAMGerber):
self.app.inform.emit(_("[ERROR_NOTCL]The selected object has to be of Gerber type.\n"
self.app.inform.emit(_("[ERROR_NOTCL] The selected object has to be of Gerber type.\n"
"Select a Gerber file and try again."))
return
@ -708,8 +723,8 @@ class CutOut(FlatCAMTool):
return
if 0 in {dia}:
self.app.inform.emit(_("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer."))
return "Tool Diameter is zero value. Change it to a positive integer."
self.app.inform.emit(_("[ERROR_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
try:
margin = float(self.margin.get_value())
@ -722,16 +737,21 @@ class CutOut(FlatCAMTool):
"Add it and retry."))
return
convex_box = self.convex_box.get_value()
def geo_init(geo_obj, app_obj):
geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
if convex_box:
geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
else:
geo = cutout_obj.solid_geometry
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
def cutting_geo(self, pos):
self.cutting_gapsize = self.cutting_gapsize / 2 + (self.cutting_dia / 2)
offset = self.cutting_gapsize / 2
offset = self.cutting_dia / 2 + self.cutting_gapsize / 2
# cutting area definition
orig_x = pos[0]

View File

@ -190,7 +190,7 @@ class DblSidedTool(FlatCAMTool):
## Alignment holes
self.ah_label = QtWidgets.QLabel("<b%s</b>" % _('Alignment Drill Coordinates:'))
self.ah_label = QtWidgets.QLabel("<b>%s</b>" % _('Alignment Drill Coordinates:'))
self.ah_label.setToolTip(
_( "Alignment holes (x1, y1), (x2, y2), ... "
"on one side of the mirror axis. For each set of (x, y) coordinates\n"
@ -365,7 +365,7 @@ class DblSidedTool(FlatCAMTool):
return
if dia is '':
self.app.inform.emit(_("[WARNING_NOTCL]No value or wrong format in Drill Dia entry. Add it and retry."))
self.app.inform.emit(_("[WARNING_NOTCL] No value or wrong format in Drill Dia entry. Add it and retry."))
return
tools = {"1": {"C": dia}}

View File

@ -238,14 +238,14 @@ class Film(FlatCAMTool):
try:
border = float(self.boundary_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
try:
scale_stroke_width = int(self.film_scale_entry.get_value())
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -266,7 +266,7 @@ class Film(FlatCAMTool):
filename = str(filename)
if str(filename) == "":
self.app.inform.emit(_("[WARNING_NOTCL]Export SVG positive cancelled."))
self.app.inform.emit(_("[WARNING_NOTCL] Export SVG positive cancelled."))
return
else:
self.app.export_svg_black(name, boxname, filename, scale_factor=scale_stroke_width)
@ -282,7 +282,7 @@ class Film(FlatCAMTool):
filename = str(filename)
if str(filename) == "":
self.app.inform.emit(_("[WARNING_NOTCL]Export SVG negative cancelled."))
self.app.inform.emit(_("[WARNING_NOTCL] Export SVG negative cancelled."))
return
else:
self.app.export_svg_negative(name, boxname, filename, border, scale_factor=scale_stroke_width)

View File

@ -28,6 +28,8 @@ class Measurement(FlatCAMTool):
def __init__(self, app):
FlatCAMTool.__init__(self, app)
self.app = app
self.canvas = self.app.plotcanvas
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
## Title
@ -38,11 +40,11 @@ class Measurement(FlatCAMTool):
form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(form_layout)
form_layout_child_1 = QtWidgets.QFormLayout()
form_layout_child_1_1 = QtWidgets.QFormLayout()
form_layout_child_1_2 = QtWidgets.QFormLayout()
form_layout_child_2 = QtWidgets.QFormLayout()
form_layout_child_3 = QtWidgets.QFormLayout()
self.units_label = QtWidgets.QLabel(_("Units:"))
self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
self.units_value = QtWidgets.QLabel("%s" % str({'mm': "METRIC (mm)", 'in': "INCH (in)"}[self.units]))
self.units_value.setDisabled(True)
self.start_label = QtWidgets.QLabel("<b>%s</b> %s:" % (_('Start'), _('Coords')))
self.start_label.setToolTip(_("This is measuring Start point coordinates."))
@ -59,84 +61,38 @@ class Measurement(FlatCAMTool):
self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
self.units_entry_1 = FCEntry()
self.units_entry_1.setToolTip(_("Those are the units in which the distance is measured."))
self.units_entry_1.setDisabled(True)
self.units_entry_1.setFocusPolicy(QtCore.Qt.NoFocus)
self.units_entry_1.setFrame(False)
self.units_entry_1.setFixedWidth(30)
self.units_entry_2 = FCEntry()
self.units_entry_2.setToolTip(_("Those are the units in which the distance is measured."))
self.units_entry_2.setDisabled(True)
self.units_entry_2.setFocusPolicy(QtCore.Qt.NoFocus)
self.units_entry_2.setFrame(False)
self.units_entry_2.setFixedWidth(30)
self.units_entry_3 = FCEntry()
self.units_entry_3.setToolTip(_("Those are the units in which the distance is measured."))
self.units_entry_3.setDisabled(True)
self.units_entry_3.setFocusPolicy(QtCore.Qt.NoFocus)
self.units_entry_3.setFrame(False)
self.units_entry_3.setFixedWidth(30)
self.units_entry_4 = FCEntry()
self.units_entry_4.setToolTip(_("Those are the units in which the distance is measured."))
self.units_entry_4.setDisabled(True)
self.units_entry_4.setFocusPolicy(QtCore.Qt.NoFocus)
self.units_entry_4.setFrame(False)
self.units_entry_4.setFixedWidth(30)
self.units_entry_5 = FCEntry()
self.units_entry_5.setToolTip(_("Those are the units in which the distance is measured."))
self.units_entry_5.setDisabled(True)
self.units_entry_5.setFocusPolicy(QtCore.Qt.NoFocus)
self.units_entry_5.setFrame(False)
self.units_entry_5.setFixedWidth(30)
self.start_entry = FCEntry()
self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.start_entry.setToolTip(_("This is measuring Start point coordinates."))
self.start_entry.setFixedWidth(100)
self.stop_entry = FCEntry()
self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates."))
self.stop_entry.setFixedWidth(100)
self.distance_x_entry = FCEntry()
self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
self.distance_x_entry.setFixedWidth(100)
self.distance_y_entry = FCEntry()
self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
self.distance_y_entry.setFixedWidth(100)
self.total_distance_entry = FCEntry()
self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance."))
self.total_distance_entry.setFixedWidth(100)
self.measure_btn = QtWidgets.QPushButton(_("Measure"))
self.measure_btn.setFixedWidth(70)
# self.measure_btn.setFixedWidth(70)
self.layout.addWidget(self.measure_btn)
form_layout_child_1.addRow(self.start_entry, self.units_entry_1)
form_layout_child_1_1.addRow(self.stop_entry, self.units_entry_2)
form_layout_child_1_2.addRow(self.distance_x_entry, self.units_entry_3)
form_layout_child_2.addRow(self.distance_y_entry, self.units_entry_4)
form_layout_child_3.addRow(self.total_distance_entry, self.units_entry_5)
form_layout.addRow(self.start_label, form_layout_child_1)
form_layout.addRow(self.stop_label, form_layout_child_1_1)
form_layout.addRow(self.distance_x_label, form_layout_child_1_2)
form_layout.addRow(self.distance_y_label, form_layout_child_2)
form_layout.addRow(self.total_distance_label, form_layout_child_3)
form_layout.addRow(self.units_label, self.units_value)
form_layout.addRow(self.start_label, self.start_entry)
form_layout.addRow(self.stop_label, self.stop_entry)
form_layout.addRow(self.distance_x_label, self.distance_x_entry)
form_layout.addRow(self.distance_y_label, self.distance_y_entry)
form_layout.addRow(self.total_distance_label, self.total_distance_entry)
# initial view of the layout
self.start_entry.set_value('(0, 0)')
@ -144,12 +100,6 @@ class Measurement(FlatCAMTool):
self.distance_x_entry.set_value('0')
self.distance_y_entry.set_value('0')
self.total_distance_entry.set_value('0')
self.units_entry_1.set_value(str(self.units))
self.units_entry_2.set_value(str(self.units))
self.units_entry_3.set_value(str(self.units))
self.units_entry_4.set_value(str(self.units))
self.units_entry_5.set_value(str(self.units))
self.layout.addStretch()
@ -160,12 +110,12 @@ class Measurement(FlatCAMTool):
# the default state is disabled for the Move command
# self.setVisible(False)
self.active = False
self.active = 0
# VisPy visuals
self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
self.measure_btn.clicked.connect(self.toggle_f)
self.measure_btn.clicked.connect(lambda: self.on_measure(activate=True))
def run(self, toggle=False):
self.app.report_usage("ToolMeasurement()")
@ -173,14 +123,13 @@ class Measurement(FlatCAMTool):
if self.app.tool_tab_locked is True:
return
self.app.ui.notebook.setTabText(2, _("Meas. Tool"))
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
self.toggle_f()
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Meas. Tool"))
self.on_measure(activate=True)
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
@ -195,90 +144,91 @@ class Measurement(FlatCAMTool):
# Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
self.show()
def toggle_f(self):
# the self.active var is doing the 'toggle'
if self.active is True:
self.app.command_active = "Measurement"
# initial view of the layout
self.start_entry.set_value('(0, 0)')
self.stop_entry.set_value('(0, 0)')
self.distance_x_entry.set_value('0')
self.distance_y_entry.set_value('0')
self.total_distance_entry.set_value('0')
def activate(self):
# we disconnect the mouse/key handlers from wherever the measurement tool was called
self.canvas.vis_disconnect('key_press')
self.canvas.vis_disconnect('mouse_move')
self.canvas.vis_disconnect('mouse_press')
self.canvas.vis_disconnect('mouse_release')
self.canvas.vis_disconnect('key_release')
# we can safely connect the app mouse events to the measurement tool
self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas)
self.canvas.vis_connect('mouse_release', self.on_mouse_click)
self.canvas.vis_connect('key_release', self.on_key_release_meas)
self.set_tool_ui()
def deactivate(self):
# disconnect the mouse/key events from functions of measurement tool
self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
self.canvas.vis_disconnect('mouse_release', self.on_mouse_click)
self.canvas.vis_disconnect('key_release', self.on_key_release_meas)
# reconnect the mouse/key events to the functions from where the tool was called
self.canvas.vis_connect('key_press', self.app.ui.keyPressEvent)
if self.app.call_source == 'app':
self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor':
self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
# self.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
# self.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
elif self.app.call_source == 'grb_editor':
self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click)
# self.canvas.vis_connect('key_press', self.app.grb_editor.on_canvas_key)
self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_canvas_click_release)
self.app.ui.notebook.setTabText(2, _("Tools"))
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
def on_measure(self, signal=None, activate=None):
log.debug("Measurement.on_measure()")
if activate is False or activate is None:
# DISABLE the Measuring TOOL
self.active = False
self.deactivate()
# disconnect the mouse/key events from functions of measurement tool
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_meas)
# reconnect the mouse/key events to the functions from where the tool was called
if self.app.call_source == 'app':
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor':
self.app.geo_editor.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
self.app.geo_editor.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
self.app.geo_editor.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.app.exc_editor.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
self.app.exc_editor.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
self.app.exc_editor.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
self.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
self.app.call_source = 'measurement'
self.clicked_meas = 0
self.app.command_active = None
# delete the measuring line
self.delete_shape()
return
else:
log.debug("Measurement Tool --> exit tool")
elif activate is True:
# ENABLE the Measuring TOOL
self.active = True
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
# we disconnect the mouse/key handlers from wherever the measurement tool was called
if self.app.call_source == 'app':
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor':
self.app.geo_editor.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
self.app.geo_editor.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
self.app.geo_editor.canvas.vis_disconnect('key_press', self.app.geo_editor.on_canvas_key)
self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.app.exc_editor.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
self.app.exc_editor.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
self.app.exc_editor.canvas.vis_disconnect('key_press', self.app.exc_editor.on_canvas_key)
self.app.exc_editor.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_canvas_click_release)
# we can safely connect the app mouse events to the measurement tool
self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_meas)
self.app.plotcanvas.vis_connect('mouse_press', self.on_click_meas)
self.app.plotcanvas.vis_connect('key_release', self.on_key_release_meas)
self.app.command_active = "Measurement"
# initial view of the layout
self.start_entry.set_value('(0, 0)')
self.stop_entry.set_value('(0, 0)')
self.distance_x_entry.set_value('0')
self.distance_y_entry.set_value('0')
self.total_distance_entry.set_value('0')
self.units_entry_1.set_value(str(self.units))
self.units_entry_2.set_value(str(self.units))
self.units_entry_3.set_value(str(self.units))
self.units_entry_4.set_value(str(self.units))
self.units_entry_5.set_value(str(self.units))
self.clicked_meas = 0
self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
self.activate()
log.debug("Measurement Tool --> tool initialized")
def on_key_release_meas(self, event):
if event.key == 'escape':
# abort the measurement action
self.toggle()
self.on_measure(activate=False)
self.app.inform.emit(_("Measurement Tool exit..."))
return
if event.key == 'G':
@ -286,63 +236,57 @@ class Measurement(FlatCAMTool):
self.app.ui.grid_snap_btn.trigger()
return
def on_click_meas(self, event):
# mouse click will be accepted only if the left button is clicked
# this is necessary because right mouse click and middle mouse click
def on_mouse_click(self, event):
# mouse click releases will be accepted only if the left button is clicked
# this is necessary because right mouse click or middle mouse click
# are used for panning on the canvas
if event.button == 1:
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.clicked_meas == 0:
pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
self.clicked_meas = 1
# if GRID is active we need to get the snapped positions
if self.app.grid_status() == True:
pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
else:
pos = pos_canvas[0], pos_canvas[1]
self.point1 = pos
self.start_entry.set_value("(%.4f, %.4f)" % pos)
self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
if self.clicked_meas == 1:
try:
pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
else:
# delete the selection bounding box
self.delete_shape()
# delete the selection bounding box
self.delete_shape()
# if GRID is active we need to get the snapped positions
if self.app.grid_status() is True:
pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
else:
pos = pos_canvas[0], pos_canvas[1]
# if GRID is active we need to get the snapped positions
if self.app.grid_status() == True:
pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
else:
pos = pos_canvas[0], pos_canvas[1]
dx = pos[0] - self.point1[0]
dy = pos[1] - self.point1[1]
d = sqrt(dx ** 2 + dy ** 2)
dx = pos[0] - self.point1[0]
dy = pos[1] - self.point1[1]
d = sqrt(dx**2 + dy**2)
self.stop_entry.set_value("(%.4f, %.4f)" % pos)
self.stop_entry.set_value("(%.4f, %.4f)" % pos)
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(
d_x='%4f' % abs(dx), d_y='%4f' % abs(dy), d_z='%4f' % abs(d)))
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(
d_x='%4f' % abs(dx), d_y='%4f' % abs(dy), d_z='%4f' % abs(d)))
self.distance_x_entry.set_value('%.4f' % abs(dx))
self.distance_y_entry.set_value('%.4f' % abs(dy))
self.total_distance_entry.set_value('%.4f' % abs(d))
self.distance_x_entry.set_value('%.4f' % abs(dx))
self.distance_y_entry.set_value('%.4f' % abs(dy))
self.total_distance_entry.set_value('%.4f' % abs(d))
self.clicked_meas = 0
self.toggle_f()
# delete the measuring line
self.delete_shape()
return
except TypeError:
pass
self.clicked_meas = 1
# TODO: I don't understand why I have to do it twice ... but without it the mouse handlers are
# TODO: are left disconnected ...
self.on_measure(activate=False)
self.deactivate()
def on_mouse_move_meas(self, event):
pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
# if GRID is active we need to get the snapped positions
if self.app.grid_status() == True:
@ -356,21 +300,18 @@ class Measurement(FlatCAMTool):
self.point2 = (pos[0], pos[1])
# update utility geometry
if self.clicked_meas == 1:
self.update_meas_shape([self.point2, self.point1])
def update_meas_shape(self, pos):
self.delete_shape()
self.draw_shape(pos)
# first delete old shape
self.delete_shape()
# second draw the new shape of the utility geometry
self.meas_line = LineString([self.point2, self.point1])
self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
def delete_shape(self):
self.sel_shapes.clear()
self.sel_shapes.redraw()
def draw_shape(self, coords):
self.meas_line = LineString(coords)
self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
def set_meas_units(self, units):
self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")

View File

@ -161,7 +161,7 @@ class ToolMove(FlatCAMTool):
proc.done()
# delete the selection bounding box
self.delete_shape()
self.app.inform.emit(_('[success]%s object was moved ...') %
self.app.inform.emit(_('[success] %s object was moved ...') %
str(sel_obj.kind).capitalize())
self.app.worker_task.emit({'fcn': job_move, 'params': [self]})
@ -199,7 +199,7 @@ class ToolMove(FlatCAMTool):
def on_key_press(self, event):
if event.key == 'escape':
# abort the move action
self.app.inform.emit(_("[WARNING_NOTCL]Move action cancelled."))
self.app.inform.emit(_("[WARNING_NOTCL] Move action cancelled."))
self.toggle()
return
@ -211,7 +211,7 @@ class ToolMove(FlatCAMTool):
obj_list = self.app.collection.get_selected()
if not obj_list:
self.app.inform.emit(_("[WARNING_NOTCL]Object(s) not selected"))
self.app.inform.emit(_("[WARNING_NOTCL] Object(s) not selected"))
self.toggle()
else:
# if we have an object selected then we can safely activate the mouse events

View File

@ -13,6 +13,7 @@ import time
import gettext
import FlatCAMTranslation as fcTranslate
from shapely.geometry import base
fcTranslate.apply_language('strings')
import builtins
@ -161,7 +162,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
e_lab_1 = QtWidgets.QLabel('')
grid3.addWidget(e_lab_1, 0, 0)
nccoverlabel = QtWidgets.QLabel(_('Overlap:'))
nccoverlabel = QtWidgets.QLabel(_('Overlap Rate:'))
nccoverlabel.setToolTip(
_("How much (fraction) of the tool width to overlap each tool pass.\n"
"Example:\n"
@ -475,7 +476,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
try:
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
if tool_dia is None:
@ -508,7 +509,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
if float('%.4f' % tool_dia) in tool_dias:
if muted is None:
self.app.inform.emit(_("[WARNING_NOTCL]Adding tool cancelled. Tool already in Tool Table."))
self.app.inform.emit(_("[WARNING_NOTCL] Adding tool cancelled. Tool already in Tool Table."))
self.tools_table.itemChanged.connect(self.on_tool_edit)
return
else:
@ -605,7 +606,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.ncc_tools.pop(t, None)
except AttributeError:
self.app.inform.emit(_("[WARNING_NOTCL]Delete failed. Select a tool to delete."))
self.app.inform.emit(_("[WARNING_NOTCL] Delete failed. Select a tool to delete."))
return
except Exception as e:
log.debug(str(e))
@ -622,11 +623,16 @@ class NonCopperClear(FlatCAMTool, Gerber):
try:
over = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
over = over if over else self.app.defaults["tools_nccoverlap"]
if over >= 1 or over < 0:
self.app.inform.emit(_("[ERROR_NOTCL] Overlap value must be between "
"0 (inclusive) and 1 (exclusive), "))
return
try:
margin = float(self.ncc_margin_entry.get_value())
except ValueError:
@ -634,7 +640,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
try:
margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
margin = margin if margin else self.app.defaults["tools_nccmargin"]
@ -656,14 +662,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
try:
self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % self.obj_name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
return "Could not retrieve object: %s" % self.obj_name
# Prepare non-copper polygons
try:
bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre)
bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
except AttributeError:
self.app.inform.emit(_("[ERROR_NOTCL]No Gerber file available."))
self.app.inform.emit(_("[ERROR_NOTCL] No Gerber file available."))
return
# calculate the empty area by subtracting the solid_geometry from the object bounding box geometry

View File

@ -157,7 +157,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.tools_box.addLayout(grid3)
# Overlap
ovlabel = QtWidgets.QLabel(_('Overlap:'))
ovlabel = QtWidgets.QLabel(_('Overlap Rate:'))
ovlabel.setToolTip(
_("How much (fraction) of the tool width to overlap each tool pass.\n"
"Example:\n"
@ -534,7 +534,7 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -564,7 +564,7 @@ class ToolPaint(FlatCAMTool, Gerber):
if float('%.4f' % tool_dia) in tool_dias:
if muted is None:
self.app.inform.emit(_("[WARNING_NOTCL]Adding tool cancelled. Tool already in Tool Table."))
self.app.inform.emit(_("[WARNING_NOTCL] Adding tool cancelled. Tool already in Tool Table."))
self.tools_table.itemChanged.connect(self.on_tool_edit)
return
else:
@ -604,7 +604,7 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
tooluid = int(self.tools_table.item(row, 3).text())
@ -656,7 +656,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# print("COPIED", self.paint_tools[td])
# self.build_ui()
# except AttributeError:
# self.app.inform.emit("[WARNING_NOTCL]Failed. Select a tool to copy.")
# self.app.inform.emit("[WARNING_NOTCL] Failed. Select a tool to copy.")
# self.build_ui()
# return
# except Exception as e:
@ -664,7 +664,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# # deselect the table
# # self.ui.geo_tools_table.clearSelection()
# else:
# self.app.inform.emit("[WARNING_NOTCL]Failed. Select a tool to copy.")
# self.app.inform.emit("[WARNING_NOTCL] Failed. Select a tool to copy.")
# self.build_ui()
# return
# else:
@ -720,7 +720,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.paint_tools.pop(t, None)
except AttributeError:
self.app.inform.emit(_("[WARNING_NOTCL]Delete failed. Select a tool to delete."))
self.app.inform.emit(_("[WARNING_NOTCL] Delete failed. Select a tool to delete."))
return
except Exception as e:
log.debug(str(e))
@ -730,9 +730,8 @@ class ToolPaint(FlatCAMTool, Gerber):
def on_paint_button_click(self):
self.app.report_usage(_("geometry_on_paint_button"))
self.app.call_source = 'paint'
# self.app.call_source = 'paint'
self.app.inform.emit(_("[WARNING_NOTCL]Click inside the desired polygon."))
try:
overlap = float(self.paintoverlap_entry.get_value())
except ValueError:
@ -740,10 +739,17 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
if overlap >= 1 or overlap < 0:
self.app.inform.emit(_("[ERROR_NOTCL] Overlap value must be between "
"0 (inclusive) and 1 (exclusive), "))
return
self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
connect = self.pathconnect_cb.get_value()
contour = self.paintcontour_cb.get_value()
select_method = self.selectmethod_combo.get_value()
@ -754,11 +760,11 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
self.paint_obj = self.app.collection.get_by_name(str(self.obj_name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % self.obj_name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
return
if self.paint_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]Object not found: %s") % self.paint_obj)
self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.paint_obj)
return
# test if the Geometry Object is multigeo and return Fail if True because
@ -777,7 +783,7 @@ class ToolPaint(FlatCAMTool, Gerber):
contour=contour)
if select_method == "single":
self.app.inform.emit(_("[WARNING_NOTCL]Click inside the desired polygon."))
self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
# use the first tool in the tool table; get the diameter
tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
@ -830,7 +836,7 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -985,7 +991,7 @@ class ToolPaint(FlatCAMTool, Gerber):
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -1068,8 +1074,9 @@ class ToolPaint(FlatCAMTool, Gerber):
for geo in recurse(obj.solid_geometry):
try:
#Polygons are the only really paintable geometries, lines in theory have no area to be painted
if not isinstance(geo, Polygon):
geo = Polygon(geo)
continue
poly_buf = geo.buffer(-paint_margin)
if paint_method == "seed":

View File

@ -290,13 +290,13 @@ class Panelize(FlatCAMTool):
try:
obj = self.app.collection.get_by_name(str(name))
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % name)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
return "Could not retrieve object: %s" % name
panel_obj = obj
if panel_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL]Object not found: %s") % panel_obj)
self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % panel_obj)
return "Object not found: %s" % panel_obj
boxname = self.box_combo.currentText()
@ -304,7 +304,7 @@ class Panelize(FlatCAMTool):
try:
box = self.app.collection.get_by_name(boxname)
except:
self.app.inform.emit(_("[ERROR_NOTCL]Could not retrieve object: %s") % boxname)
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % boxname)
return "Could not retrieve object: %s" % boxname
if box is None:
@ -320,7 +320,7 @@ class Panelize(FlatCAMTool):
try:
spacing_columns = float(self.spacing_columns.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
spacing_columns = spacing_columns if spacing_columns is not None else 0
@ -332,7 +332,7 @@ class Panelize(FlatCAMTool):
try:
spacing_rows = float(self.spacing_rows.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
spacing_rows = spacing_rows if spacing_rows is not None else 0
@ -345,7 +345,7 @@ class Panelize(FlatCAMTool):
rows = float(self.rows.get_value().replace(',', '.'))
rows = int(rows)
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
rows = rows if rows is not None else 1
@ -358,7 +358,7 @@ class Panelize(FlatCAMTool):
columns = float(self.columns.get_value().replace(',', '.'))
columns = int(columns)
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
columns = columns if columns is not None else 1
@ -370,7 +370,7 @@ class Panelize(FlatCAMTool):
try:
constrain_dx = float(self.x_width_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -381,7 +381,7 @@ class Panelize(FlatCAMTool):
try:
constrain_dy = float(self.y_height_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
@ -389,7 +389,7 @@ class Panelize(FlatCAMTool):
if 0 in {columns, rows}:
self.app.inform.emit(_("[ERROR_NOTCL]Columns or Rows are zero value. Change them to a positive integer."))
self.app.inform.emit(_("[ERROR_NOTCL] Columns or Rows are zero value. Change them to a positive integer."))
return "Columns or Rows are zero value. Change them to a positive integer."
xmin, ymin, xmax, ymax = box.bounds()
@ -517,7 +517,7 @@ class Panelize(FlatCAMTool):
plot=True, autoselected=True)
if self.constrain_flag is False:
self.app.inform.emit(_("[success]Panel done..."))
self.app.inform.emit(_("[success] Panel done..."))
else:
self.constrain_flag = False
self.app.inform.emit(_("[WARNING] Too big for the constrain area. Final panel has {col} columns and {row} rows").format(
@ -528,7 +528,7 @@ class Panelize(FlatCAMTool):
def job_thread(app_obj):
try:
panelize_2()
self.app.inform.emit(_("[success]Panel created successfully."))
self.app.inform.emit(_("[success] Panel created successfully."))
except Exception as e:
proc.done()
log.debug(str(e))

View File

@ -178,6 +178,12 @@ class Properties(FlatCAMTool):
if obj.apertures[ap]['solid_geometry']:
elems = len(obj.apertures[ap]['solid_geometry'])
temp_ap['solid_geometry'] = '%s Polygons' % str(elems)
try:
if obj.apertures[ap]['follow_geometry']:
elems = len(obj.apertures[ap]['follow_geometry'])
temp_ap['follow_geometry'] = '%s Polygons' % str(elems)
except KeyError:
pass
self.addChild(apertures, [str(ap), str(temp_ap)], True)
elif obj.kind.lower() == 'excellon':
for tool, value in obj.tools.items():

View File

@ -752,7 +752,7 @@ class SolderPaste(FlatCAMTool):
try:
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return
if tool_dia is None:
@ -823,7 +823,7 @@ class SolderPaste(FlatCAMTool):
try:
new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
"use a number."))
return

View File

@ -465,7 +465,7 @@ class ToolTransform(FlatCAMTool):
try:
value = float(self.rotate_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Rotate, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Rotate, "
"use a number."))
return
self.app.worker_task.emit({'fcn': self.on_rotate_action,
@ -499,7 +499,7 @@ class ToolTransform(FlatCAMTool):
try:
value = float(self.skewx_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Skew X, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Skew X, "
"use a number."))
return
@ -517,7 +517,7 @@ class ToolTransform(FlatCAMTool):
try:
value = float(self.skewy_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Skew Y, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Skew Y, "
"use a number."))
return
@ -535,7 +535,7 @@ class ToolTransform(FlatCAMTool):
try:
xvalue = float(self.scalex_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Scale X, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Scale X, "
"use a number."))
return
@ -569,7 +569,7 @@ class ToolTransform(FlatCAMTool):
try:
yvalue = float(self.scaley_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Scale Y, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Scale Y, "
"use a number."))
return
@ -598,7 +598,7 @@ class ToolTransform(FlatCAMTool):
try:
value = float(self.offx_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Offset X, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Offset X, "
"use a number."))
return
@ -616,7 +616,7 @@ class ToolTransform(FlatCAMTool):
try:
value = float(self.offy_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[ERROR_NOTCL]Wrong value format entered for Offset Y, "
self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Offset Y, "
"use a number."))
return
@ -671,7 +671,7 @@ class ToolTransform(FlatCAMTool):
# add information to the object that it was changed and how much
sel_obj.options['rotate'] = num
self.app.inform.emit(_('[success]Rotate done ...'))
self.app.inform.emit(_('[success] Rotate done ...'))
self.app.progress.emit(100)
except Exception as e:
@ -732,7 +732,7 @@ class ToolTransform(FlatCAMTool):
else:
obj.options['mirror_y'] = True
obj.plot()
self.app.inform.emit(_('[success]Flip on the Y axis done ...'))
self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
elif axis is 'Y':
obj.mirror('Y', (px, py))
# add information to the object that it was changed and how much
@ -742,7 +742,7 @@ class ToolTransform(FlatCAMTool):
else:
obj.options['mirror_x'] = True
obj.plot()
self.app.inform.emit(_('[success]Flip on the X axis done ...'))
self.app.inform.emit(_('[success] Flip on the X axis done ...'))
self.app.object_changed.emit(obj)
self.app.progress.emit(100)
@ -790,7 +790,7 @@ class ToolTransform(FlatCAMTool):
obj.options['skew_y'] = num
obj.plot()
self.app.object_changed.emit(obj)
self.app.inform.emit(_('[success]Skew on the %s axis done ...') % str(axis))
self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
self.app.progress.emit(100)
except Exception as e:
@ -891,7 +891,7 @@ class ToolTransform(FlatCAMTool):
obj.options['offset_y'] = num
obj.plot()
self.app.object_changed.emit(obj)
self.app.inform.emit(_('[success]Offset on the %s axis done ...') % str(axis))
self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
self.app.progress.emit(100)
except Exception as e:

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
share/aperture16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

BIN
share/aperture32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

BIN
share/padarray32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

BIN
share/scale32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

BIN
share/script_new16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

BIN
share/script_open16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

BIN
share/track32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View File

@ -314,7 +314,7 @@ class TclCommandSignaled(TclCommand):
This class is child of TclCommand and is used for commands which create new objects
it handles all neccessary stuff about blocking and passing exeptions
it handles all necessary stuff about blocking and passing exceptions
"""
@abc.abstractmethod

View File

@ -121,6 +121,6 @@ class TclCommandCutout(TclCommand):
try:
obj.app.new_object("geometry", name + "_cutout", geo_init_me)
self.app.inform.emit("[success]Rectangular-form Cutout operation finished.")
self.app.inform.emit("[success] Rectangular-form Cutout operation finished.")
except Exception as e:
return "Operation failed: %s" % str(e)

View File

@ -155,8 +155,8 @@ class TclCommandGeoCutout(TclCommandSignaled):
return "Could not retrieve object: %s" % name
if 0 in {dia}:
self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.")
return "Tool Diameter is zero value. Change it to a positive integer."
self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive real number.")
return "Tool Diameter is zero value. Change it to a positive real number."
if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
@ -220,7 +220,7 @@ class TclCommandGeoCutout(TclCommandSignaled):
ymax + gapsize)
cutout_obj.plot()
self.app.inform.emit("[success]Any-form Cutout operation finished.")
self.app.inform.emit("[success] Any-form Cutout operation finished.")
elif isinstance(cutout_obj, FlatCAMGerber):
def geo_init(geo_obj, app_obj):

View File

@ -89,4 +89,4 @@ class TclCommandOpenGerber(TclCommandSignaled):
self.app.progress.emit(100)
# GUI feedback
self.app.inform.emit("[success]Opened: " + filename)
self.app.inform.emit("[success] Opened: " + filename)

View File

@ -276,7 +276,7 @@ class TclCommandPanelize(TclCommand):
def job_thread(app_obj):
try:
panelize_2()
self.app.inform.emit("[success]Panel created successfully.")
self.app.inform.emit("[success] Panel created successfully.")
except Exception as e:
proc.done()
log.debug(str(e))
@ -287,4 +287,4 @@ class TclCommandPanelize(TclCommand):
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
else:
panelize_2()
self.app.inform.emit("[success]Panel created successfully.")
self.app.inform.emit("[success] Panel created successfully.")