Merged in test_beta8.915 (pull request #144)

Test beta8.915
This commit is contained in:
Marius Stanciu 2019-05-01 19:36:41 +00:00
commit e7a32c5c90
25 changed files with 7802 additions and 6634 deletions

View File

@ -94,8 +94,8 @@ class App(QtCore.QObject):
log.addHandler(handler)
# Version
version = 8.914
version_date = "2019/04/23"
version = 8.915
version_date = "2019/05/1"
beta = True
# current date now
@ -189,6 +189,8 @@ class App(QtCore.QObject):
App.log.info("FlatCAM Starting...")
self.main_thread = QtWidgets.QApplication.instance().thread()
###################
### OS-specific ###
###################
@ -340,10 +342,13 @@ class App(QtCore.QObject):
"global_draw_color": self.ui.general_defaults_form.general_gui_group.draw_color_entry,
"global_sel_draw_color": self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry,
"global_proj_item_color": self.ui.general_defaults_form.general_gui_group.proj_color_entry,
"global_proj_item_dis_color": self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry,
# General GUI Settings
"global_layout": self.ui.general_defaults_form.general_gui_set_group.layout_combo,
"global_hover": self.ui.general_defaults_form.general_gui_set_group.hover_cb,
"global_selection_shape": self.ui.general_defaults_form.general_gui_set_group.selection_cb,
# Gerber General
"gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb,
"gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb,
@ -612,6 +617,8 @@ class App(QtCore.QObject):
"global_alt_sel_line": '#006E20BF',
"global_draw_color": '#FF0000',
"global_sel_draw_color": '#0000FF',
"global_proj_item_color": '#000000',
"global_proj_item_dis_color": '#b7b7cb',
"global_toolbar_view": 511,
@ -647,6 +654,7 @@ class App(QtCore.QObject):
# General GUI Settings
"global_hover": True,
"global_selection_shape": True,
"global_layout": "compact",
# Gerber General
"gerber_plot": True,
@ -1162,7 +1170,8 @@ class App(QtCore.QObject):
"background-color:%s" % str(self.defaults['global_sel_line'])[:7])
# Init Right-Left Selection colors
self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value(self.defaults['global_alt_sel_fill'])
self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value(
self.defaults['global_alt_sel_fill'])
self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_alt_sel_fill'])[:7])
self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.set_value(
@ -1170,18 +1179,33 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue(
int(self.defaults['global_sel_fill'][7:9], 16))
self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value(self.defaults['global_alt_sel_line'])
self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value(
self.defaults['global_alt_sel_line'])
self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_alt_sel_line'])[:7])
# Init Draw color and Selection Draw Color
self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value(self.defaults['global_draw_color'])
self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value(
self.defaults['global_draw_color'])
self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_draw_color'])[:7])
self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(self.defaults['global_sel_draw_color'])
self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(
self.defaults['global_sel_draw_color'])
self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_sel_draw_color'])[:7])
# Init Project Items color
self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value(
self.defaults['global_proj_item_color'])
self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_proj_item_color'])[:7])
self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value(
self.defaults['global_proj_item_dis_color'])
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_proj_item_dis_color'])[:7])
#### End of Data ####
#### Plot Area ####
@ -1362,29 +1386,10 @@ class App(QtCore.QObject):
self.ui.popmenu_disable.triggered.connect(lambda: self.disable_plots(self.collection.get_selected()))
self.ui.popmenu_new_geo.triggered.connect(self.new_geometry_object)
self.ui.popmenu_new_grb.triggered.connect(self.new_gerber_object)
self.ui.popmenu_new_exc.triggered.connect(self.new_excellon_object)
self.ui.popmenu_new_prj.triggered.connect(self.on_file_new)
# Geometry Editor
self.ui.draw_line.triggered.connect(self.geo_editor.draw_tool_path)
self.ui.draw_rect.triggered.connect(self.geo_editor.draw_tool_rectangle)
self.ui.draw_cut.triggered.connect(self.geo_editor.cutpath)
self.ui.draw_move.triggered.connect(self.geo_editor.on_move)
# Gerber Editor
self.ui.grb_draw_pad.triggered.connect(self.grb_editor.on_pad_add)
self.ui.grb_draw_pad_array.triggered.connect(self.grb_editor.on_pad_add_array)
self.ui.grb_draw_track.triggered.connect(self.grb_editor.on_track_add)
self.ui.grb_draw_region.triggered.connect(self.grb_editor.on_region_add)
self.ui.grb_copy.triggered.connect(self.grb_editor.on_copy_button)
self.ui.grb_delete.triggered.connect(self.grb_editor.on_delete_btn)
self.ui.grb_move.triggered.connect(self.grb_editor.on_move_button)
# Excellon Editor
self.ui.drill.triggered.connect(self.exc_editor.exc_add_drill)
self.ui.drill_array.triggered.connect(self.exc_editor.exc_add_drill_array)
self.ui.drill_copy.triggered.connect(self.exc_editor.exc_copy_drills)
self.ui.zoomfit.triggered.connect(self.on_zoom_fit)
self.ui.clearplot.triggered.connect(self.clear_plots)
self.ui.replot.triggered.connect(self.plot_all)
@ -1418,34 +1423,64 @@ class App(QtCore.QObject):
###############################
# Setting plot colors signals
self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect(self.on_pf_color_entry)
self.ui.general_defaults_form.general_gui_group.pf_color_button.clicked.connect(self.on_pf_color_button)
self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.valueChanged.connect(self.on_pf_color_spinner)
self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.valueChanged.connect(self.on_pf_color_slider)
self.ui.general_defaults_form.general_gui_group.pl_color_entry.editingFinished.connect(self.on_pl_color_entry)
self.ui.general_defaults_form.general_gui_group.pl_color_button.clicked.connect(self.on_pl_color_button)
self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect(
self.on_pf_color_entry)
self.ui.general_defaults_form.general_gui_group.pf_color_button.clicked.connect(
self.on_pf_color_button)
self.ui.general_defaults_form.general_gui_group.pf_color_alpha_spinner.valueChanged.connect(
self.on_pf_color_spinner)
self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.valueChanged.connect(
self.on_pf_color_slider)
self.ui.general_defaults_form.general_gui_group.pl_color_entry.editingFinished.connect(
self.on_pl_color_entry)
self.ui.general_defaults_form.general_gui_group.pl_color_button.clicked.connect(
self.on_pl_color_button)
# Setting selection (left - right) colors signals
self.ui.general_defaults_form.general_gui_group.sf_color_entry.editingFinished.connect(self.on_sf_color_entry)
self.ui.general_defaults_form.general_gui_group.sf_color_button.clicked.connect(self.on_sf_color_button)
self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.valueChanged.connect(self.on_sf_color_spinner)
self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.valueChanged.connect(self.on_sf_color_slider)
self.ui.general_defaults_form.general_gui_group.sl_color_entry.editingFinished.connect(self.on_sl_color_entry)
self.ui.general_defaults_form.general_gui_group.sl_color_button.clicked.connect(self.on_sl_color_button)
self.ui.general_defaults_form.general_gui_group.sf_color_entry.editingFinished.connect(
self.on_sf_color_entry)
self.ui.general_defaults_form.general_gui_group.sf_color_button.clicked.connect(
self.on_sf_color_button)
self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.valueChanged.connect(
self.on_sf_color_spinner)
self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.valueChanged.connect(
self.on_sf_color_slider)
self.ui.general_defaults_form.general_gui_group.sl_color_entry.editingFinished.connect(
self.on_sl_color_entry)
self.ui.general_defaults_form.general_gui_group.sl_color_button.clicked.connect(
self.on_sl_color_button)
# Setting selection (right - left) colors signals
self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.editingFinished.connect(self.on_alt_sf_color_entry)
self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.clicked.connect(self.on_alt_sf_color_button)
self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.editingFinished.connect(
self.on_alt_sf_color_entry)
self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.clicked.connect(
self.on_alt_sf_color_button)
self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.valueChanged.connect(
self.on_alt_sf_color_spinner)
self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.valueChanged.connect(
self.on_alt_sf_color_slider)
self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.editingFinished.connect(self.on_alt_sl_color_entry)
self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.clicked.connect(self.on_alt_sl_color_button)
self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.editingFinished.connect(
self.on_alt_sl_color_entry)
self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.clicked.connect(
self.on_alt_sl_color_button)
# Setting Editor Draw colors signals
self.ui.general_defaults_form.general_gui_group.draw_color_entry.editingFinished.connect(self.on_draw_color_entry)
self.ui.general_defaults_form.general_gui_group.draw_color_button.clicked.connect(self.on_draw_color_button)
self.ui.general_defaults_form.general_gui_group.draw_color_entry.editingFinished.connect(
self.on_draw_color_entry)
self.ui.general_defaults_form.general_gui_group.draw_color_button.clicked.connect(
self.on_draw_color_button)
self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.editingFinished.connect(self.on_sel_draw_color_entry)
self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.clicked.connect(self.on_sel_draw_color_button)
self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.editingFinished.connect(
self.on_sel_draw_color_entry)
self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.clicked.connect(
self.on_sel_draw_color_button)
self.ui.general_defaults_form.general_gui_group.proj_color_entry.editingFinished.connect(
self.on_proj_color_entry)
self.ui.general_defaults_form.general_gui_group.proj_color_button.clicked.connect(
self.on_proj_color_button)
self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.editingFinished.connect(
self.on_proj_color_dis_entry)
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect(
self.on_proj_color_dis_button)
self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace)
@ -1772,7 +1807,7 @@ class App(QtCore.QObject):
self.thr2 = QtCore.QThread()
self.worker_task.emit({'fcn': self.version_check,
'params': []})
self.thr2.start()
self.thr2.start(QtCore.QThread.LowPriority)
####################################
@ -1790,8 +1825,6 @@ class App(QtCore.QObject):
# decide if we have a double click or single click
self.doubleclick = False
# variable to store if there was motion before right mouse button click (panning)
self.panning_action = False
# variable to store if a command is active (then the var is not None) and which one it is
self.command_active = None
# variable to store the status of moving selection action
@ -1977,7 +2010,15 @@ class App(QtCore.QObject):
self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
self.paste_tool = SolderPaste(self)
self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'), separator=True)
self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'))
self.calculator_tool = ToolCalculator(self)
self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png'))
self.sub_tool = ToolSub(self)
self.sub_tool.install(icon=QtGui.QIcon('share/sub32.png'), pos=self.ui.menuedit_convert,
before=self.ui.menuedit_convert_sg2mg)
self.move_tool = ToolMove(self)
self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
@ -1995,9 +2036,6 @@ class App(QtCore.QObject):
self.paint_tool.install(icon=QtGui.QIcon('share/paint16.png'), pos=self.ui.menutool,
before=self.measurement_tool.menuAction, separator=True)
self.calculator_tool = ToolCalculator(self)
self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png'))
self.transform_tool = ToolTransform(self)
self.transform_tool.install(icon=QtGui.QIcon('share/transform.png'), pos=self.ui.menuoptions, separator=True)
@ -2081,6 +2119,7 @@ class App(QtCore.QObject):
self.ui.panelize_btn.triggered.connect(lambda: self.panelize_tool.run(toggle=True))
self.ui.film_btn.triggered.connect(lambda: self.film_tool.run(toggle=True))
self.ui.solder_btn.triggered.connect(lambda: self.paste_tool.run(toggle=True))
self.ui.sub_btn.triggered.connect(lambda: self.sub_tool.run(toggle=True))
self.ui.calculators_btn.triggered.connect(lambda: self.calculator_tool.run(toggle=True))
self.ui.transform_btn.triggered.connect(lambda: self.transform_tool.run(toggle=True))
@ -2138,8 +2177,9 @@ class App(QtCore.QObject):
# set call source to the Editor we go into
self.call_source = 'grb_editor'
# make sure that we can't select another object while in Editor Mode:
self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
# # make sure that we can't select another object while in Editor Mode:
# self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.ui.project_frame.setDisabled(True)
# delete any selection shape that might be active as they are not relevant in Editor
self.delete_selection_shape()
@ -2178,6 +2218,11 @@ class App(QtCore.QObject):
response = msgbox.clickedButton()
if response == bt_yes:
# clean the Tools Tab
self.ui.tool_scroll_area.takeWidget()
self.ui.tool_scroll_area.setWidget(QtWidgets.QWidget())
self.ui.notebook.setTabText(2, "Tool")
if isinstance(edited_obj, FlatCAMGeometry):
obj_type = "Geometry"
if cleanup is None:
@ -2232,6 +2277,11 @@ class App(QtCore.QObject):
self.inform.emit(_("[selected] %s is updated, returning to App...") % obj_type)
elif response == bt_no:
# clean the Tools Tab
self.ui.tool_scroll_area.takeWidget()
self.ui.tool_scroll_area.setWidget(QtWidgets.QWidget())
self.ui.notebook.setTabText(2, "Tool")
if isinstance(edited_obj, FlatCAMGeometry):
self.geo_editor.deactivate()
elif isinstance(edited_obj, FlatCAMGerber):
@ -2268,7 +2318,8 @@ class App(QtCore.QObject):
self.ui.plot_tab_area.protectTab(0)
# make sure that we reenable the selection on Project Tab after returning from Editor Mode:
self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
# self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.ui.project_frame.setDisabled(False)
def get_last_folder(self):
return self.defaults["global_last_folder"]
@ -2773,7 +2824,7 @@ class App(QtCore.QObject):
def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True):
"""
Creates a new specalized FlatCAMObj and attaches it to the application,
Creates a new specialized FlatCAMObj and attaches it to the application,
this is, updates the GUI accordingly, any other records and plots it.
This method is thread-safe.
@ -2883,7 +2934,7 @@ class App(QtCore.QObject):
FlatCAMApp.App.log.debug("Moving new object back to main thread.")
# Move the object to the main thread and let the app know that it is available.
obj.moveToThread(QtWidgets.QApplication.instance().thread())
obj.moveToThread(self.main_thread)
self.object_created.emit(obj, obj_plot, obj_autoselected)
return obj
@ -2911,6 +2962,14 @@ class App(QtCore.QObject):
grb_obj.follow = False
grb_obj.apertures = {}
try:
grb_obj.options['xmin'] = 0
grb_obj.options['ymin'] = 0
grb_obj.options['xmax'] = 0
grb_obj.options['ymax'] = 0
except KeyError:
pass
self.new_object('gerber', 'new_grb', initialize, plot=False)
def on_object_created(self, obj, plot, autoselect):
@ -4024,6 +4083,50 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(new_val_sel)
self.defaults['global_sel_draw_color'] = new_val_sel
def on_proj_color_entry(self):
self.defaults['global_proj_item_color'] = self.ui.general_defaults_form.general_gui_group \
.proj_color_entry.get_value()
self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_proj_item_color']))
def on_proj_color_button(self):
current_color = QtGui.QColor(self.defaults['global_proj_item_color'])
c_dialog = QtWidgets.QColorDialog()
proj_color = c_dialog.getColor(initial=current_color)
if proj_color.isValid() is False:
return
self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet(
"background-color:%s" % str(proj_color.name()))
new_val_sel = str(proj_color.name())
self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value(new_val_sel)
self.defaults['global_proj_item_color'] = new_val_sel
def on_proj_color_dis_entry(self):
self.defaults['global_proj_item_dis_color'] = self.ui.general_defaults_form.general_gui_group \
.proj_color_dis_entry.get_value()
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
"background-color:%s" % str(self.defaults['global_proj_item_dis_color']))
def on_proj_color_dis_button(self):
current_color = QtGui.QColor(self.defaults['global_proj_item_dis_color'])
c_dialog = QtWidgets.QColorDialog()
proj_color = c_dialog.getColor(initial=current_color)
if proj_color.isValid() is False:
return
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet(
"background-color:%s" % str(proj_color.name()))
new_val_sel = str(proj_color.name())
self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value(new_val_sel)
self.defaults['global_proj_item_dis_color'] = new_val_sel
def on_deselect_all(self):
self.collection.set_all_inactive()
self.delete_selection_shape()
@ -4481,7 +4584,7 @@ class App(QtCore.QObject):
"""
self.report_usage("on_jump_to()")
if custom_location is None:
if not custom_location:
dia_box = Dialog_box(title=_("Jump to ..."),
label=_("Enter the coordinates in format X,Y:"),
icon=QtGui.QIcon('share/jump_to16.png'))
@ -4627,9 +4730,14 @@ class App(QtCore.QObject):
if obj.tools:
obj_init.tools = obj.tools
def initialize_excellon(obj, app):
objs = self.collection.get_selected()
FlatCAMGeometry.merge(objs, obj)
def initialize_excellon(obj_init, app):
# objs = self.collection.get_selected()
# FlatCAMGeometry.merge(objs, obj)
solid_geo = []
for tool in obj.tools:
for geo in obj.tools[tool]['solid_geometry']:
solid_geo.append(geo)
obj_init.solid_geometry = deepcopy(solid_geo)
for obj in self.collection.get_selected():
@ -4680,7 +4788,8 @@ class App(QtCore.QObject):
self.collection.set_active(name)
curr_sel_obj = self.collection.get_by_name(name)
# create the selection box around the selected object
self.draw_selection_shape(curr_sel_obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
def on_preferences(self):
@ -4915,6 +5024,7 @@ class App(QtCore.QObject):
# self.plotcanvas.auto_adjust_axes()
self.plotcanvas.vispy_canvas.update() # TODO: Need update canvas?
self.on_zoom_fit(None)
self.collection.update_view()
# TODO: Rework toolbar 'clear', 'replot' functions
def on_toolbar_replot(self):
@ -4957,9 +5067,9 @@ class App(QtCore.QObject):
action.triggered.connect(self.set_grid)
self.ui.cmenu_gridmenu.addSeparator()
grid_add = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/plus32.png'), "Add")
grid_add = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/plus32.png'), _("Add"))
grid_delete = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/delete32.png'), _("Delete"))
grid_add.triggered.connect(self.on_grid_add)
grid_delete = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon('share/delete32.png'), "Delete")
grid_delete.triggered.connect(self.on_grid_delete)
def set_grid(self):
@ -4970,8 +5080,8 @@ class App(QtCore.QObject):
## Current application units in lower Case
units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
grid_add_popup = FCInputDialog(title="New Grid ...",
text='Enter a Grid VAlue:',
grid_add_popup = FCInputDialog(title=_("New Grid ..."),
text=_('Enter a Grid Value:'),
min=0.0000, max=99.9999, decimals=4)
grid_add_popup.setWindowIcon(QtGui.QIcon('share/plus32.png'))
@ -5126,15 +5236,13 @@ class App(QtCore.QObject):
self.plotcanvas.vispy_canvas.native.setFocus()
self.pos_jump = event.pos
if origin_click is True:
pass
else:
self.ui.popMenu.mouse_is_panning = False
if origin_click != True:
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
if event.button == 2:
self.panning_action = True
if event.button == 2 and event.is_dragging == 1:
self.ui.popMenu.mouse_is_panning = True
return
else:
self.panning_action = False
if self.rel_point1 is not None:
try: # May fail in case mouse not within axes
@ -5222,12 +5330,12 @@ class App(QtCore.QObject):
# canvas menu
try:
if event.button == 2: # right click
if self.panning_action is True:
self.panning_action = False
else:
if self.ui.popMenu.mouse_is_panning is False:
self.cursor = QtGui.QCursor()
self.populate_cmenu_grids()
self.ui.popMenu.popup(self.cursor.pos())
except Exception as e:
log.warning("Error: %s" % str(e))
return
@ -5294,12 +5402,14 @@ class App(QtCore.QObject):
if sel_type is True:
if poly_obj.within(poly_selection):
# create the selection box around the selected object
self.draw_selection_shape(obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj)
self.collection.set_active(obj.options['name'])
else:
if poly_selection.intersects(poly_obj):
# create the selection box around the selected object
self.draw_selection_shape(obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj)
self.collection.set_active(obj.options['name'])
except:
# the Exception here will happen if we try to select on screen and we have an newly (and empty)
@ -5349,7 +5459,8 @@ class App(QtCore.QObject):
self.collection.set_active(objects_under_the_click_list[0])
# create the selection box around the selected object
curr_sel_obj = self.collection.get_active()
self.draw_selection_shape(curr_sel_obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
# self.inform.emit('[selected] %s: %s selected' %
# (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
@ -5372,7 +5483,8 @@ class App(QtCore.QObject):
self.collection.set_active(objects_under_the_click_list[0])
# create the selection box around the selected object
curr_sel_obj = self.collection.get_active()
self.draw_selection_shape(curr_sel_obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
# self.inform.emit('[selected] %s: %s selected' %
# (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
@ -5420,7 +5532,8 @@ class App(QtCore.QObject):
# delete the possible selection box around a possible selected object
self.delete_selection_shape()
# create the selection box around the selected object
self.draw_selection_shape(curr_sel_obj)
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
# self.inform.emit('[selected] %s: %s selected' %
# (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
@ -7597,6 +7710,7 @@ class App(QtCore.QObject):
icons = {
"gerber": "share/flatcam_icon16.png",
"excellon": "share/drill16.png",
'geometry': "share/geometry16.png",
"cncjob": "share/cnc16.png",
"project": "share/project16.png",
"svg": "share/geometry16.png",
@ -7868,23 +7982,23 @@ The normal flow when working in FlatCAM is the following:</span></p>
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().
'''
def enable_plots(self, objects, threaded=False):
def enable_plots(self, objects, threaded=True):
if threaded is True:
def worker_task(app_obj):
percentage = 0.1
try:
delta = 0.9 / len(objects)
except ZeroDivisionError:
self.progress.emit(0)
return
# percentage = 0.1
# try:
# delta = 0.9 / len(objects)
# except ZeroDivisionError:
# self.progress.emit(0)
# return
for obj in objects:
obj.options['plot'] = True
percentage += delta
self.progress.emit(int(percentage*100))
# percentage += delta
# self.progress.emit(int(percentage*100))
self.progress.emit(0)
# self.progress.emit(0)
self.plots_updated.emit()
self.collection.update_view()
# self.collection.update_view()
# Send to worker
# self.worker.add_task(worker_task, [self])
@ -7892,9 +8006,9 @@ The normal flow when working in FlatCAM is the following:</span></p>
else:
for obj in objects:
obj.options['plot'] = True
self.progress.emit(0)
# self.progress.emit(0)
self.plots_updated.emit()
self.collection.update_view()
# self.collection.update_view()
# TODO: FIX THIS
'''
@ -7904,7 +8018,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
QObject::connect: Cannot queue arguments of type 'QVector<int>'
(Make sure 'QVector<int>' is registered using qRegisterMetaType().
'''
def disable_plots(self, objects, threaded=False):
def disable_plots(self, objects, threaded=True):
# TODO: This method is very similar to replot_all. Try to merge.
"""
Disables plots
@ -7914,23 +8028,23 @@ The normal flow when working in FlatCAM is the following:</span></p>
"""
if threaded is True:
self.progress.emit(10)
# self.progress.emit(10)
def worker_task(app_obj):
percentage = 0.1
try:
delta = 0.9 / len(objects)
except ZeroDivisionError:
self.progress.emit(0)
return
# percentage = 0.1
# try:
# delta = 0.9 / len(objects)
# except ZeroDivisionError:
# self.progress.emit(0)
# return
for obj in objects:
obj.options['plot'] = False
percentage += delta
self.progress.emit(int(percentage*100))
# percentage += delta
# self.progress.emit(int(percentage*100))
self.progress.emit(0)
# self.progress.emit(0)
self.plots_updated.emit()
self.collection.update_view()
# self.collection.update_view()
# Send to worker
self.worker_task.emit({'fcn': worker_task, 'params': [self]})
@ -7938,7 +8052,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
for obj in objects:
obj.options['plot'] = False
self.plots_updated.emit()
self.collection.update_view()
# self.collection.update_view()
def clear_plots(self):

View File

@ -181,17 +181,18 @@ class FlatCAMObj(QtCore.QObject):
old_name = copy(self.options["name"])
new_name = self.ui.name_entry.get_value()
# update the SHELL auto-completer model data
try:
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")
if new_name != old_name:
# update the SHELL auto-completer model data
try:
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.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))
def on_offset_button_click(self):
self.app.report_usage("obj_on_offset_button")
@ -358,6 +359,12 @@ class FlatCAMObj(QtCore.QObject):
except AttributeError:
pass
# Not all object types have mark_shapes
# try:
# self.mark_shapes.clear(update)
# except AttributeError:
# pass
def delete(self):
# Free resources
del self.ui
@ -2717,7 +2724,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# those elements are the ones used for generating GCode
self.sel_tools = {}
self.offset_item_options = [_("Path"), _("In"), _("Out"), _("Custom")]
self.offset_item_options = ["Path", "In", "Out", "Custom"]
self.type_item_options = [_("Iso"), _("Rough"), _("Finish")]
self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
@ -2959,7 +2966,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.tools.update({
self.tooluid: {
'tooldia': float(self.options["cnctooldia"]),
'offset': _('Path'),
'offset': ('Path'),
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': 'C1',
@ -3041,7 +3048,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
tool_offset = self.ui.geo_tools_table.cellWidget(current_row, 2)
if tool_offset is not None:
tool_offset_txt = tool_offset.currentText()
if tool_offset_txt == _('Custom'):
if tool_offset_txt == ('Custom'):
self.ui.tool_offset_entry.show()
self.ui.tool_offset_lbl.show()
else:
@ -3246,7 +3253,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.tools.update({
self.tooluid: {
'tooldia': tooldia,
'offset': _('Path'),
'offset': ('Path'),
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': 'C1',
@ -3615,7 +3622,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
cb_txt = cw.currentText()
if cw_col == 2:
tooluid_value['offset'] = cb_txt
if cb_txt == _('Custom'):
if cb_txt == ('Custom'):
self.ui.tool_offset_entry.show()
self.ui.tool_offset_lbl.show()
else:
@ -4088,13 +4095,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
diadict_key: datadict
})
if dia_cnc_dict['offset'] == 'in':
if dia_cnc_dict['offset'] == ('in'):
tool_offset = -dia_cnc_dict['tooldia'] / 2
offset_str = 'inside'
elif dia_cnc_dict['offset'].lower() == 'out':
elif dia_cnc_dict['offset'].lower() == ('out'):
tool_offset = dia_cnc_dict['tooldia'] / 2
offset_str = 'outside'
elif dia_cnc_dict['offset'].lower() == 'path':
elif dia_cnc_dict['offset'].lower() == ('path'):
offset_str = 'onpath'
tool_offset = 0.0
else:
@ -4321,13 +4328,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
diadict_key: datadict
})
if dia_cnc_dict['offset'] == 'in':
if dia_cnc_dict['offset'] == ('in'):
tool_offset = -dia_cnc_dict['tooldia'] / 2
offset_str = 'inside'
elif dia_cnc_dict['offset'].lower() == 'out':
elif dia_cnc_dict['offset'].lower() == ('out'):
tool_offset = dia_cnc_dict['tooldia'] / 2
offset_str = 'outside'
elif dia_cnc_dict['offset'].lower() == 'path':
elif dia_cnc_dict['offset'].lower() == ('path'):
offset_str = 'onpath'
tool_offset = 0.0
else:

View File

@ -25,7 +25,7 @@ class WorkerStack(QtCore.QObject):
thread.started.connect(worker.run)
worker.task_completed.connect(self.on_task_completed)
thread.start()
thread.start(QtCore.QThread.LowPriority)
self.workers.append(worker)
self.threads.append(thread)

View File

@ -118,13 +118,13 @@ class KeySensitiveListView(QtWidgets.QTreeView):
event.ignore()
class TreeItem:
class TreeItem(KeySensitiveListView):
"""
Item of a tree model
"""
def __init__(self, data, icon=None, obj=None, parent_item=None):
super(TreeItem, self).__init__(parent_item)
self.parent_item = parent_item
self.item_data = data # Columns string data
self.icon = icon # Decoration
@ -378,9 +378,11 @@ class ObjectCollection(QtCore.QAbstractItemModel):
return index.internalPointer().data(index.column())
if role == Qt.ForegroundRole:
color = QColor(self.app.defaults['global_proj_item_color'])
color_disabled = QColor(self.app.defaults['global_proj_item_dis_color'])
obj = index.internalPointer().obj
if obj:
return QtGui.QBrush(QtCore.Qt.black) if obj.options["plot"] else QtGui.QBrush(QtCore.Qt.darkGray)
return QtGui.QBrush(color) if obj.options["plot"] else QtGui.QBrush(color_disabled)
else:
return index.internalPointer().data(index.column())
@ -397,23 +399,25 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if index.isValid():
obj = index.internalPointer().obj
if obj:
old_name = obj.options['name']
# rename the object
obj.options["name"] = str(data)
new_name = obj.options['name']
old_name = str(obj.options['name'])
new_name = str(data)
if old_name != new_name and new_name != '':
# rename the object
obj.options["name"] = str(data)
# update the SHELL auto-completer model data
try:
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")
# update the SHELL auto-completer model data
try:
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")
obj.build_ui()
self.app.inform.emit(_("Object renamed from {old} to {new}").format(old=old_name, new=new_name))
obj.build_ui()
self.app.inform.emit(_("Object renamed from <b>{old}</b> to <b>{new}</b>").format(old=old_name,
new=new_name))
return True
@ -681,6 +685,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
:param name: Name of the FlatCAM Object
:return: None
"""
log.debug("ObjectCollection.set_inactive()")
obj = self.get_by_name(name)
item = obj.item
group = self.group_items[obj.kind]
@ -721,7 +727,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
color='red', name=str(obj.options['name'])))
except IndexError:
FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
# FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
self.app.inform.emit('')
try:
self.app.ui.selected_scroll_area.takeWidget()

View File

@ -9,6 +9,67 @@ CAD program, and create G-Code for Isolation routing.
=================================================
01.05.2019
- the project items color is now controlled from Foreground Role in ObjectCollection.data()
- made again plot functions threaded but moved the dataChanged signal (update_view() ) to the main thread by using an already existing signal (plots_updated signal) to avoid the errors with register QVector
- Enable/Disable Object toggle key ("Space" key) will trigger also the datChanged signal for the Project MVC
- added a new setting for the color of the Project items, the color when they are disabled.
- fixed a crash when triggering 'Jump To' menu action (shortcut key 'J' worked ok)
- made some mods to what can be translated as some of the translations interfered with the correct functioning of FlatCAM
- updated the translations
- fixed bugs in Excellon Editor
- Excellon Editor: made Add Pad tool to work until right click
- Excellon Editor: fixed mouse right click was always doing popup context menu
- GUIElements.FCEntry2(): added a try-except clause
- made sure that the Tools Tab is cleared on Editors exit
- Geometry Editor: restored the old behavior: a tool is active until it is voluntarily exited: either by using the 'ESC' key, or selecting the Select tool or new: right click on canvas
- RELEASE 8.915
30.04.2019
- in ObjectCollection class, made sure that renaming an object in Project View does not result in an empty name. If new name is blank the rename is cancelled.
- made ObjectCollection.TreeItem() inherit KeySensitiveListVIew and implicitly QTreeView (in the hope that the theme applied on app will be applied on the tree items, too (for MacOs new DarkUI theme)
- renamed SilkScreen Tool to Substract Tool and move it's menu location in Edit -> Conversion
- started to modify the Substract Tool to work on Geometry objects too
- progress in the new Substract Tool for Geometry Objects
- finished the new Substract Tool
- added new setting for the color of the Project Tree items; it helps in providing contrast when using dark theme like the one in MacOS
29.04.2019
- solved bug in Gerber Editor: the '0' aperture (the region aperture) had no size which created errors. Made the size to be zero.
- solved bug in editors: the canvas selection shape was not deleted on mouse release if the grid snap was OFF
- solved bug in Excellon Editor: when selecting a drill hole on canvas the selected row in the Tools Table was not the correct one but the next highest row
- finished the Silkscreen Tool but there are some limitations (some wires fragments from silkscreen are lost)
- solved the issue in Silkscreen Tool with losing some fragments of wires from silkscreen
26.04.2019
- small changes in GUI; optimized contextual menu display
- made sure that the Project Tab is disabled while one of the Editors is active and it is restored after returning to app
- fixed some bugs recently introduced in Editors due of the changes done to the way mouse panning is detected
- cleaned up the context menu's when in Editors; made some structural changes
- updated the code in camlib.CNCJob.generate_from_excellon_by_tools() to work with the new API from Google OR-Tools
- all Gerber regions (G36 G37) are stored in the '0' aperture
- fixed a bug that added geometry with clear polarity in the apertures where was not supposed to be
25.04.2019
- Geometry Editor: modified the intersection (if the selected shapes don't intersects preserve them) and substract functions (delete all shapes that were used in the process)
- work in the ToolSub
- for all objects, if in Selected the object name is changed to the same name, the rename is not done (because there is nothing changed)
- fixed Edit -> Copy as Geom function handler to work for Excellon objects, too
- made sure that the mouse pointer is restored to default on Editor exit
- added a toggle button in Preferences to toggle on/off the display of the selection box on canvas when the user is clicking an object or selecting it by mouse dragging.
24.04.2019
- PDF import tool: working in making the PDF layer rendering multithreaded in itself (one layer rendered on each worker)
- PDF import tool: solved a bug in parsing the rectangle subpath (an extra point was added to the subpath creating nonexisting geometry)
- PDF import tool: finished layer rendering multithreading
- New tool: Silkscreen Tool: I am trying to remove the overlapped geo with the soldermask layer from overlay layer; layed out the class and functions - not working yet
23.04.2019
- Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)

101
camlib.py
View File

@ -1942,6 +1942,10 @@ class Gerber (Geometry):
# will store the Gerber geometry's as paths
self.follow_geometry = []
# 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
self.source_file = ''
# Attributes to be included in serialization
@ -2174,10 +2178,6 @@ 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,6 +2242,8 @@ class Gerber (Geometry):
match = self.lpol_re.search(gline)
if match:
new_polarity = match.group(1)
# log.info("Polarity CHANGE, LPC = %s, poly_buff = %s" % (self.is_lpc, poly_buffer))
self.is_lpc = True if new_polarity == 'C' else False
if len(path) > 1 and current_polarity != new_polarity:
# finish the current path and add it to the storage
@ -2258,6 +2260,7 @@ class Gerber (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)
if self.is_lpc is True:
@ -2280,12 +2283,10 @@ 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))
@ -2414,7 +2415,7 @@ class Gerber (Geometry):
### Aperture definitions %ADD...
match = self.ad_re.search(gline)
if match:
log.info("Found aperture definition. Line %d: %s" % (line_num, gline))
# log.info("Found aperture definition. Line %d: %s" % (line_num, gline))
self.aperture_parse(match.group(1), match.group(2), match.group(3))
continue
@ -2460,7 +2461,7 @@ class Gerber (Geometry):
match = self.tool_re.search(gline)
if match:
current_aperture = match.group(1)
log.debug("Line %d: Aperture change to (%s)" % (line_num, match.group(1)))
# log.debug("Line %d: Aperture change to (%s)" % (line_num, current_aperture))
# If the aperture value is zero then make it something quite small but with a non-zero value
# so it can be processed by FlatCAM.
@ -2469,7 +2470,7 @@ class Gerber (Geometry):
if self.apertures[current_aperture]["type"] is not "AM":
if self.apertures[current_aperture]["size"] == 0:
self.apertures[current_aperture]["size"] = 1e-12
log.debug(self.apertures[current_aperture])
# log.debug(self.apertures[current_aperture])
# Take care of the current path with the previous tool
if len(path) > 1:
@ -2479,7 +2480,6 @@ class Gerber (Geometry):
else:
# --- Buffered ----
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
@ -2551,6 +2551,12 @@ class Gerber (Geometry):
if self.regionoff_re.search(gline):
making_region = False
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
# if D02 happened before G37 we now have a path with 1 element only so we have to add the current
# geo to the poly_buffer otherwise we loose it
if current_operation_code == 2:
@ -2558,24 +2564,24 @@ class Gerber (Geometry):
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
self.apertures['0']['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
self.apertures['0']['follow_geometry'] = []
self.apertures['0']['follow_geometry'].append(geo)
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(geo)
self.apertures['0']['clear_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(geo)
self.apertures['0']['clear_geometry'] = []
self.apertures['0']['clear_geometry'].append(geo)
else:
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures['0']['solid_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures['0']['solid_geometry'] = []
self.apertures['0']['solid_geometry'].append(geo)
continue
# Only one path defines region?
@ -2599,10 +2605,10 @@ class Gerber (Geometry):
if not region.is_empty:
follow_buffer.append(region)
try:
self.apertures[current_aperture]['follow_geometry'].append(region)
self.apertures['0']['follow_geometry'].append(region)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(region)
self.apertures['0']['follow_geometry'] = []
self.apertures['0']['follow_geometry'].append(region)
region = Polygon(path)
if not region.is_valid:
@ -2613,17 +2619,18 @@ class Gerber (Geometry):
# we do this for the case that a region is done without having defined any aperture
# Allegro does that
if current_aperture:
used_aperture = current_aperture
elif last_path_aperture:
used_aperture = last_path_aperture
else:
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['solid_geometry'] = []
used_aperture = '0'
# if current_aperture:
# used_aperture = current_aperture
# elif last_path_aperture:
# used_aperture = last_path_aperture
# else:
# if '0' not in self.apertures:
# self.apertures['0'] = {}
# self.apertures['0']['size'] = 0.0
# self.apertures['0']['type'] = 'REG'
# self.apertures['0']['solid_geometry'] = []
# used_aperture = '0'
used_aperture = '0'
if self.is_lpc is True:
try:
self.apertures[used_aperture]['clear_geometry'].append(region)
@ -2727,6 +2734,7 @@ class Gerber (Geometry):
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
last_path_aperture = '0'
else:
@ -2746,6 +2754,7 @@ class Gerber (Geometry):
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
last_path_aperture = '0'
geo = Polygon()
@ -2779,6 +2788,7 @@ class Gerber (Geometry):
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
last_path_aperture = '0'
elem = [linear_x, linear_y]
@ -5288,8 +5298,13 @@ class CNCjob(Geometry):
y2 = locations[to_node][1]
self.matrix[from_node][to_node] = distance_euclidian(x1, y1, x2, y2)
def Distance(self, from_node, to_node):
return int(self.matrix[from_node][to_node])
# def Distance(self, from_node, to_node):
# return int(self.matrix[from_node][to_node])
def Distance(self, from_index, to_index):
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return self.matrix[from_node][to_node]
# Create the data.
def create_data_array():
@ -5327,8 +5342,9 @@ class CNCjob(Geometry):
depot = 0
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
routing = pywrapcp.RoutingModel(manager)
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
@ -5343,7 +5359,8 @@ class CNCjob(Geometry):
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
transit_callback_index = routing.RegisterTransitCallback(dist_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
@ -5432,14 +5449,16 @@ class CNCjob(Geometry):
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
manager = pywrapcp.RoutingIndexManager(tsp_size, num_routes, depot)
routing = pywrapcp.RoutingModel(manager)
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
# Callback to the distance function. The callback takes two
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
transit_callback_index = routing.RegisterTransitCallback(dist_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)

View File

@ -44,7 +44,7 @@ class FCDrillAdd(FCShapeTool):
except KeyError:
self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add a drill first select a tool"))
self.draw_app.select_tool("select")
self.draw_app.select_tool("drill_select")
return
try:
@ -103,6 +103,7 @@ class FCDrillAdd(FCShapeTool):
self.draw_app.current_storage = self.draw_app.storage_dict[self.selected_dia]
self.geometry = DrawToolShape(self.util_shape(self.points))
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit(_("[success] Done. Drill added."))
@ -319,7 +320,7 @@ class FCDrillArray(FCShapeTool):
self.geometry.append(DrawToolShape(geo))
self.complete = True
self.draw_app.app.inform.emit(_("[success] Done. Drill Array added."))
self.draw_app.in_action = True
self.draw_app.in_action = False
self.draw_app.array_frame.hide()
return
@ -428,7 +429,7 @@ class FCDrillResize(FCShapeTool):
self.complete = True
# MS: always return to the Select Tool
self.draw_app.select_tool("select")
self.draw_app.select_tool("drill_select")
class FCDrillMove(FCShapeTool):
@ -475,7 +476,7 @@ class FCDrillMove(FCShapeTool):
self.make()
# MS: always return to the Select Tool
self.draw_app.select_tool("select")
self.draw_app.select_tool("drill_select")
return
def make(self):
@ -643,8 +644,11 @@ class FCDrillSelect(DrawTool):
sel_tools.add(storage)
for storage in sel_tools:
self.exc_editor_app.tools_table_exc.selectRow(int(storage))
self.draw_app.last_tool_selected = int(storage)
for k, v in self.draw_app.tool2tooldia.items():
if v == storage:
self.exc_editor_app.tools_table_exc.selectRow(int(k) - 1)
self.draw_app.last_tool_selected = int(k)
break
self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@ -931,7 +935,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.drill_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
{'label': 'Y', 'value': 'Y'},
{'label': _('Angle'), 'value': 'A'}])
{'label': 'Angle', 'value': 'A'}])
self.drill_axis_radio.set_value('X')
self.linear_form.addRow(self.drill_axis_label, self.drill_axis_radio)
@ -999,7 +1003,7 @@ class FlatCAMExcEditor(QtCore.QObject):
## Toolbar events and properties
self.tools_exc = {
"select": {"button": self.app.ui.select_drill_btn,
"drill_select": {"button": self.app.ui.select_drill_btn,
"constructor": FCDrillSelect},
"drill_add": {"button": self.app.ui.add_drill_btn,
"constructor": FCDrillAdd},
@ -1016,6 +1020,8 @@ class FlatCAMExcEditor(QtCore.QObject):
### Data
self.active_tool = None
self.in_action = False
self.storage_dict = {}
self.current_storage = []
@ -1067,7 +1073,6 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.exc_move_drill_menuitem.triggered.connect(self.exc_move_drills)
# Init GUI
self.drill_array_size_entry.set_value(5)
self.drill_pitch_entry.set_value(2.54)
@ -1646,6 +1651,13 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
self.app.ui.popmenu_properties.setVisible(False)
self.app.ui.e_editor_cmenu.menuAction().setVisible(True)
self.app.ui.g_editor_cmenu.menuAction().setVisible(False)
self.app.ui.grb_editor_cmenu.menuAction().setVisible(False)
# Tell the App that the editor is active
self.editor_active = True
@ -1653,6 +1665,11 @@ class FlatCAMExcEditor(QtCore.QObject):
self.drills_frame.show()
def deactivate(self):
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
# adjust the status of the menu entries related to the editor
self.app.ui.menueditedit.setDisabled(False)
self.app.ui.menueditok.setDisabled(True)
@ -1706,8 +1723,12 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.update_obj_btn.setEnabled(False)
self.app.ui.g_editor_cmenu.setEnabled(False)
self.app.ui.e_editor_cmenu.setEnabled(False)
self.app.ui.popmenu_disable.setVisible(True)
self.app.ui.cmenu_newmenu.menuAction().setVisible(True)
self.app.ui.popmenu_properties.setVisible(True)
self.app.ui.g_editor_cmenu.menuAction().setVisible(False)
self.app.ui.e_editor_cmenu.menuAction().setVisible(False)
self.app.ui.grb_editor_cmenu.menuAction().setVisible(False)
# Show original geometry
if self.exc_obj:
@ -1733,6 +1754,18 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.disconnect()
self.app.ui.popmenu_copy.triggered.disconnect()
self.app.ui.popmenu_delete.triggered.disconnect()
self.app.ui.popmenu_move.triggered.disconnect()
self.app.ui.popmenu_copy.triggered.connect(self.exc_copy_drills)
self.app.ui.popmenu_delete.triggered.connect(self.on_delete_btn)
self.app.ui.popmenu_move.triggered.connect(self.exc_move_drills)
# Excellon Editor
self.app.ui.drill.triggered.connect(self.exc_add_drill)
self.app.ui.drill_array.triggered.connect(self.exc_add_drill_array)
def disconnect_canvas_event_handlers(self):
# we restore the key and mouse control to FlatCAMApp method
# first connect to new, then disconnect the old handlers
@ -1747,6 +1780,36 @@ class FlatCAMExcEditor(QtCore.QObject):
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
try:
self.app.ui.popmenu_copy.triggered.disconnect(self.exc_copy_drills)
except TypeError:
pass
try:
self.app.ui.popmenu_delete.triggered.disconnect(self.on_delete_btn)
except TypeError:
pass
try:
self.app.ui.popmenu_move.triggered.disconnect(self.exc_move_drills)
except TypeError:
pass
self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_object)
self.app.ui.popmenu_delete.triggered.connect(self.app.on_delete)
self.app.ui.popmenu_move.triggered.connect(self.app.obj_move)
# Excellon Editor
try:
self.app.ui.drill.triggered.disconnect(self.exc_add_drill)
except TypeError:
pass
try:
self.app.ui.drill_array.triggered.disconnect(self.exc_add_drill_array)
except TypeError:
pass
def clear(self):
self.active_tool = None
# self.shape_buffer = []
@ -1786,7 +1849,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# Set selection tolerance
# DrawToolShape.tolerance = fc_excellon.drawing_tolerance * 10
self.select_tool("select")
self.select_tool("drill_select")
self.set_ui()
@ -2002,10 +2065,10 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.log.debug("on_tool_select('%s')" % tool)
if self.last_tool_selected is None and current_tool is not 'select':
# self.draw_app.select_tool('select')
if self.last_tool_selected is None and current_tool is not 'drill_select':
# self.draw_app.select_tool('drill_select')
self.complete = True
current_tool = 'select'
current_tool = 'drill_select'
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. There is no Tool/Drill selected"))
# This is to make the group behave as radio group
@ -2024,7 +2087,7 @@ class FlatCAMExcEditor(QtCore.QObject):
for t in self.tools_exc:
self.tools_exc[t]["button"].setChecked(False)
self.select_tool('select')
self.select_tool('drill_select')
self.active_tool = FCDrillSelect(self)
def on_row_selected(self, row, col):
@ -2042,7 +2105,7 @@ class FlatCAMExcEditor(QtCore.QObject):
try:
selected_dia = self.tool2tooldia[self.tools_table_exc.currentRow() + 1]
self.last_tool_selected = copy(self.tools_table_exc.currentRow()) + 1
self.last_tool_selected = int(self.tools_table_exc.currentRow()) + 1
for obj in self.storage_dict[selected_dia].get_objects():
self.selected.append(obj)
except Exception as e:
@ -2060,23 +2123,29 @@ class FlatCAMExcEditor(QtCore.QObject):
def on_canvas_click(self, event):
"""
event.x and .y have canvas coordinates
event.xdaya and .ydata have plot coordinates
event.xdata and .ydata have plot coordinates
:param event: Event object dispatched by Matplotlib
:param event: Event object dispatched by VisPy
:return: None
"""
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
size=20)
else:
self.pos = (self.pos[0], self.pos[1])
self.app.app_cursor.enabled = False
if event.button is 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
### Snap coordinates
x, y = self.app.geo_editor.snap(self.pos[0], self.pos[1])
self.pos = (x, y)
# print(self.active_tool)
# Selection with left mouse button
if self.active_tool is not None and event.button is 1:
# Dispatch event to active_tool
@ -2100,7 +2169,11 @@ class FlatCAMExcEditor(QtCore.QObject):
if key_modifier == modifier_to_use:
self.select_tool(self.active_tool.name)
else:
self.select_tool("select")
# return to Select tool but not for FCPad
if isinstance(self.active_tool, FCDrillAdd):
self.select_tool(self.active_tool.name)
else:
self.select_tool("drill_select")
return
if isinstance(self.active_tool, FCDrillSelect):
@ -2187,10 +2260,9 @@ class FlatCAMExcEditor(QtCore.QObject):
self.storage.insert(shape) # TODO: Check performance
def on_exc_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
else:
@ -2200,11 +2272,29 @@ class FlatCAMExcEditor(QtCore.QObject):
# canvas menu
try:
if event.button == 2: # right click
if self.app.panning_action is True:
self.app.panning_action = False
else:
self.app.cursor = QtGui.QCursor()
self.app.ui.popMenu.popup(self.app.cursor.pos())
if self.app.ui.popMenu.mouse_is_panning is False:
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
if self.active_tool.complete is False and not isinstance(self.active_tool, FCDrillSelect):
self.active_tool.complete = True
self.in_action = False
self.delete_utility_geometry()
self.app.inform.emit(_("[success] Done."))
self.select_tool('drill_select')
else:
if isinstance(self.active_tool, FCDrillAdd):
self.active_tool.complete = True
self.in_action = False
self.delete_utility_geometry()
self.app.inform.emit(_("[success] Done."))
self.select_tool('drill_select')
self.app.cursor = QtGui.QCursor()
self.app.populate_cmenu_grids()
self.app.ui.popMenu.popup(self.app.cursor.pos())
except Exception as e:
log.warning("Error: %s" % str(e))
raise
@ -2216,13 +2306,13 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.app.selection_type is not None:
self.draw_selection_area_handler(self.pos, pos, self.app.selection_type)
self.app.selection_type = None
elif isinstance(self.active_tool, FCDrillSelect):
# Dispatch event to active_tool
# msg = self.active_tool.click(self.app.geo_editor.snap(event.xdata, event.ydata))
# msg = self.active_tool.click_release((self.pos[0], self.pos[1]))
# self.app.inform.emit(msg)
self.active_tool.click_release((self.pos[0], self.pos[1]))
self.replot()
# if there are selected objects then plot them
if self.selected:
self.replot()
except Exception as e:
log.warning("Error: %s" % str(e))
raise
@ -2264,7 +2354,7 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.tool2tooldia[key] == storage:
item = self.tools_table_exc.item((key - 1), 1)
self.tools_table_exc.setCurrentItem(item)
self.last_tool_selected = key
self.last_tool_selected = int(key)
self.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@ -2287,16 +2377,12 @@ class FlatCAMExcEditor(QtCore.QObject):
self.x = event.xdata
self.y = event.ydata
# Prevent updates on pan
# if len(event.buttons) > 0:
# return
self.app.ui.popMenu.mouse_is_panning = False
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
if event.button == 2:
self.app.panning_action = True
if event.button == 2 and event.is_dragging == 1:
self.app.ui.popMenu.mouse_is_panning = True
return
else:
self.app.panning_action = False
try:
x = float(event.xdata)
@ -2308,7 +2394,13 @@ class FlatCAMExcEditor(QtCore.QObject):
return
### Snap coordinates
x, y = self.app.geo_editor.app.geo_editor.snap(x, y)
if self.app.grid_status():
x, y = self.app.geo_editor.snap(x, y)
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
else:
self.app.app_cursor.enabled = False
self.snap_x = x
self.snap_y = y
@ -2330,23 +2422,27 @@ class FlatCAMExcEditor(QtCore.QObject):
geo = self.active_tool.utility_geometry(data=(x, y))
if isinstance(geo, DrawToolShape) and geo.geo is not None:
# Remove any previous utility shape
self.tool_shape.clear(update=True)
self.draw_utility_geometry(geo=geo)
### Selection area on canvas section ###
dx = pos[0] - self.pos[0]
if event.is_dragging == 1 and event.button == 1:
self.app.delete_selection_shape()
if dx < 0:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y),
color=self.app.defaults["global_alt_sel_line"],
face_color=self.app.defaults['global_alt_sel_fill'])
self.app.selection_type = False
# I make an exception for FCDrillAdd and FCDrillArray because clicking and dragging while making regions
# can create strange issues
if isinstance(self.active_tool, FCDrillAdd) or isinstance(self.active_tool, FCDrillArray):
pass
else:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y))
self.app.selection_type = True
dx = pos[0] - self.pos[0]
self.app.delete_selection_shape()
if dx < 0:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y),
color=self.app.defaults["global_alt_sel_line"],
face_color=self.app.defaults['global_alt_sel_fill'])
self.app.selection_type = False
else:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y))
self.app.selection_type = True
else:
self.app.selection_type = None
@ -2587,21 +2683,21 @@ class FlatCAMExcEditor(QtCore.QObject):
self.linear_angle_label.hide()
def exc_add_drill(self):
self.select_tool('add')
self.select_tool('drill_add')
return
def exc_add_drill_array(self):
self.select_tool('add_array')
self.select_tool('drill_array')
return
def exc_resize_drills(self):
self.select_tool('resize')
self.select_tool('drill_resize')
return
def exc_copy_drills(self):
self.select_tool('copy')
self.select_tool('drill_copy')
return
def exc_move_drills(self):
self.select_tool('move')
self.select_tool('drill_move')
return

View File

@ -18,7 +18,7 @@ from FlatCAMTool import FlatCAMTool
from flatcamGUI.ObjectUI import LengthEntry, RadioSet
from shapely.geometry import LineString, LinearRing, MultiLineString
from shapely.ops import cascaded_union
from shapely.ops import cascaded_union, unary_union
import shapely.affinity as affinity
from numpy import arctan2, Inf, array, sqrt, sign, dot
@ -475,9 +475,9 @@ class PaintOptionsTool(FlatCAMTool):
)
grid.addWidget(methodlabel, 3, 0)
self.paintmethod_combo = RadioSet([
{"label": _("Standard"), "value": "standard"},
{"label": _("Seed-based"), "value": "seed"},
{"label": _("Straight lines"), "value": "lines"}
{"label": "Standard", "value": "standard"},
{"label": "Seed-based", "value": "seed"},
{"label": "Straight lines", "value": "lines"}
], orientation='vertical', stretch=False)
grid.addWidget(self.paintmethod_combo, 3, 1)
@ -2875,8 +2875,12 @@ class FlatCAMGeoEditor(QtCore.QObject):
def gridx_changed(goption, gentry):
entry2option(option=goption, entry=gentry)
# if the grid link is checked copy the value in the GridX field to GridY
try:
val = float(self.app.ui.grid_gap_x_entry.get_value())
except ValueError:
return
if self.app.ui.grid_gap_link_cb.isChecked():
self.app.ui.grid_gap_y_entry.set_value(self.app.ui.grid_gap_x_entry.get_value())
self.app.ui.grid_gap_y_entry.set_value(val)
self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_x_entry.textChanged.connect(
@ -2970,6 +2974,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.snap_toolbar.setDisabled(False)
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
self.app.ui.popmenu_properties.setVisible(False)
self.app.ui.g_editor_cmenu.menuAction().setVisible(True)
# prevent the user to change anything in the Selected Tab while the Geo Editor is active
sel_tab_widget_list = self.app.ui.selected_tab.findChildren(QtWidgets.QWidget)
for w in sel_tab_widget_list:
@ -2979,6 +2988,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.editor_active = True
def deactivate(self):
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
# adjust the status of the menu entries related to the editor
self.app.ui.menueditedit.setDisabled(False)
self.app.ui.menueditok.setDisabled(True)
@ -3033,6 +3047,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Tell the app that the editor is no longer active
self.editor_active = False
self.app.ui.popmenu_disable.setVisible(True)
self.app.ui.cmenu_newmenu.menuAction().setVisible(True)
self.app.ui.popmenu_properties.setVisible(True)
self.app.ui.grb_editor_cmenu.menuAction().setVisible(False)
self.app.ui.e_editor_cmenu.menuAction().setVisible(False)
self.app.ui.g_editor_cmenu.menuAction().setVisible(False)
try:
# re-enable all the widgets in the Selected Tab that were disabled after entering in Edit Geometry Mode
sel_tab_widget_list = self.app.ui.selected_tab.findChildren(QtWidgets.QWidget)
@ -3061,7 +3082,21 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.disconnect()
# self.app.collection.view.clicked.disconnect()
self.app.ui.popmenu_copy.triggered.disconnect()
self.app.ui.popmenu_delete.triggered.disconnect()
self.app.ui.popmenu_move.triggered.disconnect()
self.app.ui.popmenu_copy.triggered.connect(lambda: self.select_tool('copy'))
self.app.ui.popmenu_delete.triggered.connect(self.on_delete_btn)
self.app.ui.popmenu_move.triggered.connect(lambda: self.select_tool('move'))
# Geometry Editor
self.app.ui.draw_line.triggered.connect(self.draw_tool_path)
self.app.ui.draw_rect.triggered.connect(self.draw_tool_rectangle)
self.app.ui.draw_cut.triggered.connect(self.cutpath)
self.app.ui.draw_move.triggered.connect(self.on_move)
def disconnect_canvas_event_handlers(self):
# we restore the key and mouse control to FlatCAMApp method
@ -3071,12 +3106,50 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
# self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
try:
self.app.ui.popmenu_copy.triggered.disconnect(lambda: self.select_tool('copy'))
except TypeError:
pass
try:
self.app.ui.popmenu_delete.triggered.disconnect(self.on_delete_btn)
except TypeError:
pass
try:
self.app.ui.popmenu_move.triggered.disconnect(lambda: self.select_tool('move'))
except TypeError:
pass
self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_object)
self.app.ui.popmenu_delete.triggered.connect(self.app.on_delete)
self.app.ui.popmenu_move.triggered.connect(self.app.obj_move)
# Geometry Editor
try:
self.app.ui.draw_line.triggered.disconnect(self.draw_tool_path)
except TypeError:
pass
try:
self.app.ui.draw_rect.triggered.disconnect(self.draw_tool_rectangle)
except TypeError:
pass
try:
self.app.ui.draw_cut.triggered.disconnect(self.cutpath)
except TypeError:
pass
try:
self.app.ui.draw_move.triggered.disconnect(self.on_move)
except TypeError:
pass
def add_shape(self, shape):
"""
Adds a shape to the shape storage.
@ -3117,31 +3190,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.tool_shape.clear(update=True)
self.tool_shape.redraw()
def cutpath(self):
selected = self.get_selected()
tools = selected[1:]
toolgeo = cascaded_union([shp.geo for shp in tools])
target = selected[0]
if type(target.geo) == Polygon:
for ring in poly2rings(target.geo):
self.add_shape(DrawToolShape(ring.difference(toolgeo)))
self.delete_shape(target)
elif type(target.geo) == LineString or type(target.geo) == LinearRing:
self.add_shape(DrawToolShape(target.geo.difference(toolgeo)))
self.delete_shape(target)
elif type(target.geo) == MultiLineString:
try:
for linestring in target.geo:
self.add_shape(DrawToolShape(linestring.difference(toolgeo)))
except:
self.app.log.warning("Current LinearString does not intersect the target")
self.delete_shape(target)
else:
self.app.log.warning("Not implemented. Object type: %s" % str(type(target.geo)))
self.replot()
def toolbar_tool_toggle(self, key):
self.options[key] = self.sender().isChecked()
if self.options[key] == True:
@ -3268,15 +3316,21 @@ class FlatCAMGeoEditor(QtCore.QObject):
:return: None
"""
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
size=20)
else:
self.pos = (self.pos[0], self.pos[1])
self.app.app_cursor.enabled = False
if event.button is 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
### Snap coordinates
x, y = self.snap(self.pos[0], self.pos[1])
self.pos = (x, y)
modifiers = QtWidgets.QApplication.keyboardModifiers()
# If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
@ -3288,7 +3342,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Selection with left mouse button
if self.active_tool is not None and event.button is 1:
# Dispatch event to active_tool
# msg = self.active_tool.click(self.snap(event.xdata, event.ydata))
msg = self.active_tool.click(self.snap(self.pos[0], self.pos[1]))
# If it is a shape generating tool
@ -3303,13 +3356,19 @@ class FlatCAMGeoEditor(QtCore.QObject):
else:
modifier_to_use = Qt.ShiftModifier
if isinstance(self.active_tool, FCText):
self.select_tool("select")
else:
self.select_tool(self.active_tool.name)
# if modifier key is pressed then we add to the selected list the current shape but if
# it's already in the selected list, we removed it. Therefore first click selects, second deselects.
if key_modifier == modifier_to_use:
self.select_tool(self.active_tool.name)
else:
self.select_tool("select")
return
# if key_modifier == modifier_to_use:
# self.select_tool(self.active_tool.name)
# else:
# self.select_tool("select")
# return
if isinstance(self.active_tool, FCSelect):
# self.app.log.debug("Replotting after click.")
@ -3334,16 +3393,12 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.x = event.xdata
self.y = event.ydata
# Prevent updates on pan
# if len(event.buttons) > 0:
# return
self.app.ui.popMenu.mouse_is_panning = False
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
if event.button == 2:
self.app.panning_action = True
if event.button == 2 and event.is_dragging == 1:
self.app.ui.popMenu.mouse_is_panning = True
return
else:
self.app.panning_action = False
try:
x = float(event.xdata)
@ -3355,7 +3410,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
return
### Snap coordinates
x, y = self.snap(x, y)
if self.app.grid_status():
x, y = self.snap(x, y)
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
else:
self.app.app_cursor.enabled = False
self.snap_x = x
self.snap_y = y
@ -3396,9 +3457,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
else:
self.app.selection_type = None
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
def on_geo_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
@ -3411,12 +3469,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
# canvas menu
try:
if event.button == 2: # right click
if self.app.panning_action is True:
self.app.panning_action = False
else:
if self.app.ui.popMenu.mouse_is_panning is False:
if self.in_action is False:
self.app.cursor = QtGui.QCursor()
self.app.ui.popMenu.popup(self.app.cursor.pos())
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
if self.active_tool.complete is False and not isinstance(self.active_tool, FCSelect):
self.active_tool.complete = True
self.in_action = False
self.delete_utility_geometry()
self.app.inform.emit(_("[success] Done."))
self.select_tool('select')
else:
self.app.cursor = QtGui.QCursor()
self.app.populate_cmenu_grids()
self.app.ui.popMenu.popup(self.app.cursor.pos())
else:
# if right click on canvas and the active tool need to be finished (like Path or Polygon)
# right mouse click will finish the action
@ -3426,19 +3495,20 @@ class FlatCAMGeoEditor(QtCore.QObject):
if self.active_tool.complete:
self.on_shape_complete()
self.app.inform.emit(_("[success] Done."))
self.select_tool(self.active_tool.name)
# MS: always return to the Select Tool if modifier key is not pressed
# else return to the current tool
key_modifier = QtWidgets.QApplication.keyboardModifiers()
if self.app.defaults["global_mselect_key"] == 'Control':
modifier_to_use = Qt.ControlModifier
else:
modifier_to_use = Qt.ShiftModifier
if key_modifier == modifier_to_use:
self.select_tool(self.active_tool.name)
else:
self.select_tool("select")
# key_modifier = QtWidgets.QApplication.keyboardModifiers()
# if self.app.defaults["global_mselect_key"] == 'Control':
# modifier_to_use = Qt.ControlModifier
# else:
# modifier_to_use = Qt.ShiftModifier
#
# if key_modifier == modifier_to_use:
# self.select_tool(self.active_tool.name)
# else:
# self.select_tool("select")
except Exception as e:
log.warning("Error: %s" % str(e))
@ -3781,7 +3851,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
:return: None.
"""
results = cascaded_union([t.geo for t in self.get_selected()])
results = unary_union([t.geo for t in self.get_selected()])
# Delete originals.
for_deletion = [s for s in self.get_selected()]
@ -3795,9 +3865,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.replot()
def intersection(self):
def intersection_2(self):
"""
Makes intersectino of selected polygons. Original polygons are deleted.
Makes intersection of selected polygons. Original polygons are deleted.
:return: None
"""
@ -3827,11 +3897,67 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.replot()
def intersection(self):
"""
Makes intersection of selected polygons. Original polygons are deleted.
:return: None
"""
shapes = self.get_selected()
results = []
intact = []
try:
intersector = shapes[0].geo
except Exception as e:
log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e))
self.app.inform.emit(_("[WARNING_NOTCL] A selection of at least 2 geo items is required to do Intersection."))
self.select_tool('select')
return
for shape in shapes[1:]:
if intersector.intersects(shape.geo):
results.append(intersector.intersection(shape.geo))
else:
intact.append(shape)
if len(results) != 0:
# Delete originals.
for_deletion = [s for s in self.get_selected()]
for shape in for_deletion:
if shape not in intact:
self.delete_shape(shape)
for geo in results:
self.add_shape(DrawToolShape(geo))
# Selected geometry is now gone!
self.selected = []
self.replot()
def subtract(self):
selected = self.get_selected()
try:
tools = selected[1:]
toolgeo = cascaded_union([shp.geo for shp in tools])
toolgeo = unary_union([shp.geo for shp in tools])
result = selected[0].geo.difference(toolgeo)
for_deletion = [s for s in self.get_selected()]
for shape in for_deletion:
self.delete_shape(shape)
self.add_shape(DrawToolShape(result))
self.replot()
except Exception as e:
log.debug(str(e))
def subtract_2(self):
selected = self.get_selected()
try:
tools = selected[1:]
toolgeo = unary_union([shp.geo for shp in tools])
result = selected[0].geo.difference(toolgeo)
self.delete_shape(selected[0])
@ -3841,6 +3967,30 @@ class FlatCAMGeoEditor(QtCore.QObject):
except Exception as e:
log.debug(str(e))
def cutpath(self):
selected = self.get_selected()
tools = selected[1:]
toolgeo = unary_union([shp.geo for shp in tools])
target = selected[0]
if type(target.geo) == Polygon:
for ring in poly2rings(target.geo):
self.add_shape(DrawToolShape(ring.difference(toolgeo)))
elif type(target.geo) == LineString or type(target.geo) == LinearRing:
self.add_shape(DrawToolShape(target.geo.difference(toolgeo)))
elif type(target.geo) == MultiLineString:
try:
for linestring in target.geo:
self.add_shape(DrawToolShape(linestring.difference(toolgeo)))
except:
self.app.log.warning("Current LinearString does not intersect the target")
else:
self.app.log.warning("Not implemented. Object type: %s" % str(type(target.geo)))
return
self.delete_shape(target)
self.replot()
def buffer(self, buf_distance, join_style):
selected = self.get_selected()

View File

@ -1613,22 +1613,25 @@ class FCApertureSelect(DrawTool):
key_modifier = QtWidgets.QApplication.keyboardModifiers()
for storage in self.grb_editor_app.storage_dict:
for shape in self.grb_editor_app.storage_dict[storage]['solid_geometry']:
if Point(point).within(shape.geo):
if (self.grb_editor_app.app.defaults["global_mselect_key"] == 'Control' and
key_modifier == Qt.ControlModifier) or \
(self.grb_editor_app.app.defaults["global_mselect_key"] == 'Shift' and
key_modifier == Qt.ShiftModifier):
try:
for shape in self.grb_editor_app.storage_dict[storage]['solid_geometry']:
if Point(point).within(shape.geo):
if (self.grb_editor_app.app.defaults["global_mselect_key"] == 'Control' and
key_modifier == Qt.ControlModifier) or \
(self.grb_editor_app.app.defaults["global_mselect_key"] == 'Shift' and
key_modifier == Qt.ShiftModifier):
if shape in self.draw_app.selected:
self.draw_app.selected.remove(shape)
if shape in self.draw_app.selected:
self.draw_app.selected.remove(shape)
else:
# add the object to the selected shapes
self.draw_app.selected.append(shape)
sel_aperture.add(storage)
else:
# add the object to the selected shapes
self.draw_app.selected.append(shape)
sel_aperture.add(storage)
else:
self.draw_app.selected.append(shape)
sel_aperture.add(storage)
except KeyError:
pass
# select the aperture in the Apertures Table that is associated with the selected shape
try:
@ -1980,7 +1983,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.pad_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
{'label': 'Y', 'value': 'Y'},
{'label': _('Angle'), 'value': 'A'}])
{'label': 'Angle', 'value': 'A'}])
self.pad_axis_radio.set_value('X')
self.linear_form.addRow(self.pad_axis_label, self.pad_axis_radio)
@ -2174,11 +2177,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
# flag to show if the object was modified
self.is_modified = False
self.edited_obj_name = ""
self.tool_row = 0
# A QTimer
self.plot_thread = None
# store the status of the editor so the Delete at object level will not work until the edit is finished
self.editor_active = False
@ -2629,14 +2633,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.edited_obj_name = self.name_entry.get_value()
def on_aptype_changed(self, current_text):
# 'O' is letter O not zero.
if current_text == 'R' or current_text == 'O':
self.apdim_lbl.show()
self.apdim_entry.show()
self.apsize_entry.setReadOnly(True)
self.apsize_entry.setDisabled(True)
else:
self.apdim_lbl.hide()
self.apdim_entry.hide()
self.apsize_entry.setReadOnly(False)
self.apsize_entry.setDisabled(False)
def activate_grb_editor(self):
# adjust the status of the menu entries related to the editor
@ -2684,10 +2689,20 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.popmenu_edit.setVisible(False)
self.app.ui.popmenu_save.setVisible(True)
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
self.app.ui.popmenu_properties.setVisible(False)
self.app.ui.grb_editor_cmenu.menuAction().setVisible(True)
# Tell the App that the editor is active
self.editor_active = True
def deactivate_grb_editor(self):
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
# adjust the status of the menu entries related to the editor
self.app.ui.menueditedit.setDisabled(False)
self.app.ui.menueditok.setDisabled(True)
@ -2741,14 +2756,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.update_obj_btn.setEnabled(False)
self.app.ui.g_editor_cmenu.setEnabled(False)
self.app.ui.grb_editor_cmenu.setEnabled(False)
self.app.ui.e_editor_cmenu.setEnabled(False)
# adjust the visibility of some of the canvas context menu
self.app.ui.popmenu_edit.setVisible(True)
self.app.ui.popmenu_save.setVisible(False)
self.app.ui.popmenu_disable.setVisible(True)
self.app.ui.cmenu_newmenu.menuAction().setVisible(True)
self.app.ui.popmenu_properties.setVisible(True)
self.app.ui.g_editor_cmenu.menuAction().setVisible(False)
self.app.ui.e_editor_cmenu.menuAction().setVisible(False)
self.app.ui.grb_editor_cmenu.menuAction().setVisible(False)
# Show original geometry
if self.gerber_obj:
self.gerber_obj.visible = True
@ -2771,6 +2789,20 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.canvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.disconnect()
self.app.ui.popmenu_copy.triggered.disconnect()
self.app.ui.popmenu_delete.triggered.disconnect()
self.app.ui.popmenu_move.triggered.disconnect()
self.app.ui.popmenu_copy.triggered.connect(self.on_copy_button)
self.app.ui.popmenu_delete.triggered.connect(self.on_delete_btn)
self.app.ui.popmenu_move.triggered.connect(self.on_move_button)
# Gerber Editor
self.app.ui.grb_draw_pad.triggered.connect(self.on_pad_add)
self.app.ui.grb_draw_pad_array.triggered.connect(self.on_pad_add_array)
self.app.ui.grb_draw_track.triggered.connect(self.on_track_add)
self.app.ui.grb_draw_region.triggered.connect(self.on_region_add)
def disconnect_canvas_event_handlers(self):
# we restore the key and mouse control to FlatCAMApp method
@ -2786,6 +2818,47 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
try:
self.app.ui.popmenu_copy.triggered.disconnect(self.on_copy_button)
except TypeError:
pass
try:
self.app.ui.popmenu_delete.triggered.disconnect(self.on_delete_btn)
except TypeError:
pass
try:
self.app.ui.popmenu_move.triggered.disconnect(self.on_move_button)
except TypeError:
pass
self.app.ui.popmenu_copy.triggered.connect(self.app.on_copy_object)
self.app.ui.popmenu_delete.triggered.connect(self.app.on_delete)
self.app.ui.popmenu_move.triggered.connect(self.app.obj_move)
# Gerber Editor
try:
self.app.ui.grb_draw_pad.triggered.disconnect(self.on_pad_add)
except TypeError:
pass
try:
self.app.ui.grb_draw_pad_array.triggered.disconnect(self.on_pad_add_array)
except TypeError:
pass
try:
self.app.ui.grb_draw_track.triggered.disconnect(self.on_track_add)
except TypeError:
pass
try:
self.app.ui.grb_draw_region.triggered.disconnect(self.on_region_add)
except TypeError:
pass
def clear(self):
self.active_tool = None
# self.shape_buffer = []
@ -3160,26 +3233,31 @@ class FlatCAMGrbEditor(QtCore.QObject):
def on_canvas_click(self, event):
"""
event.x and .y have canvas coordinates
event.xdaya and .ydata have plot coordinates
event.xdata and .ydata have plot coordinates
:param event: Event object dispatched by Matplotlib
:param event: Event object dispatched by VisPy
:return: None
"""
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
size=20)
else:
self.pos = (self.pos[0], self.pos[1])
self.app.app_cursor.enabled = False
if event.button is 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
### Snap coordinates
x, y = self.app.geo_editor.snap(self.pos[0], self.pos[1])
self.pos = (x, y)
# Selection with left mouse button
if self.active_tool is not None and event.button is 1:
# Dispatch event to active_tool
# msg = self.active_tool.click(self.app.geo_editor.snap(event.xdata, event.ydata))
msg = self.active_tool.click(self.app.geo_editor.snap(self.pos[0], self.pos[1]))
# If it is a shape generating tool
@ -3213,10 +3291,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.log.debug("No active tool to respond to click!")
def on_grb_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
else:
@ -3226,9 +3303,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# canvas menu
try:
if event.button == 2: # right click
if self.app.panning_action is True:
self.app.panning_action = False
else:
if self.app.ui.popMenu.mouse_is_panning is False:
if self.in_action is False:
try:
QtGui.QGuiApplication.restoreOverrideCursor()
@ -3243,6 +3318,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.select_tool('select')
else:
self.app.cursor = QtGui.QCursor()
self.app.populate_cmenu_grids()
self.app.ui.popMenu.popup(self.app.cursor.pos())
else:
# if right click on canvas and the active tool need to be finished (like Path or Polygon)
@ -3282,10 +3358,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.selection_type = None
elif isinstance(self.active_tool, FCApertureSelect):
# Dispatch event to active_tool
# msg = self.active_tool.click(self.app.geo_editor.snap(event.xdata, event.ydata))
# msg = self.active_tool.click_release((self.pos[0], self.pos[1]))
# self.app.inform.emit(msg)
self.active_tool.click_release((self.pos[0], self.pos[1]))
# if there are selected objects then plot them
@ -3303,26 +3375,29 @@ class FlatCAMGrbEditor(QtCore.QObject):
:type Bool
:return:
"""
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
sel_aperture = set()
self.apertures_table.clearSelection()
self.app.delete_selection_shape()
for storage in self.storage_dict:
for obj in self.storage_dict[storage]['solid_geometry']:
if (sel_type is True and poly_selection.contains(obj.geo)) or \
(sel_type is False and poly_selection.intersects(obj.geo)):
if self.key == self.app.defaults["global_mselect_key"]:
if obj in self.selected:
self.selected.remove(obj)
try:
for obj in self.storage_dict[storage]['solid_geometry']:
if (sel_type is True and poly_selection.contains(obj.geo)) or \
(sel_type is False and poly_selection.intersects(obj.geo)):
if self.key == self.app.defaults["global_mselect_key"]:
if obj in self.selected:
self.selected.remove(obj)
else:
# add the object to the selected shapes
self.selected.append(obj)
sel_aperture.add(storage)
else:
# add the object to the selected shapes
self.selected.append(obj)
sel_aperture.add(storage)
else:
self.selected.append(obj)
sel_aperture.add(storage)
except KeyError:
pass
try:
self.apertures_table.cellPressed.disconnect()
except:
@ -3349,22 +3424,18 @@ class FlatCAMGrbEditor(QtCore.QObject):
:return: None
"""
pos = self.canvas.vispy_canvas.translate_coords(event.pos)
event.xdata, event.ydata = pos[0], pos[1]
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
event.xdata, event.ydata = pos_canvas[0], pos_canvas[1]
self.x = event.xdata
self.y = event.ydata
# Prevent updates on pan
# if len(event.buttons) > 0:
# return
self.app.ui.popMenu.mouse_is_panning = False
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
if event.button == 2:
self.app.panning_action = True
if event.button == 2 and event.is_dragging == 1:
self.app.ui.popMenu.mouse_is_panning = True
return
else:
self.app.panning_action = False
try:
x = float(event.xdata)
@ -3376,7 +3447,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
return
### Snap coordinates
x, y = self.app.geo_editor.app.geo_editor.snap(x, y)
if self.app.grid_status():
x, y = self.app.geo_editor.snap(x, y)
self.app.app_cursor.enabled = True
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
else:
self.app.app_cursor.enabled = False
self.snap_x = x
self.snap_y = y
@ -3398,7 +3475,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
geo = self.active_tool.utility_geometry(data=(x, y))
if isinstance(geo, DrawToolShape) and geo.geo is not None:
# Remove any previous utility shape
self.tool_shape.clear(update=True)
self.draw_utility_geometry(geo=geo)
@ -3410,7 +3486,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
if isinstance(self.active_tool, FCRegion) or isinstance(self.active_tool, FCTrack):
pass
else:
dx = pos[0] - self.pos[0]
dx = pos_canvas[0] - self.pos[0]
self.app.delete_selection_shape()
if dx < 0:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y),
@ -3423,9 +3499,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
else:
self.app.selection_type = None
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
def on_canvas_key_release(self, event):
self.key = None
@ -3459,15 +3532,18 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.clear(update=True)
for storage in self.storage_dict:
for shape in self.storage_dict[storage]['solid_geometry']:
if shape.geo is None:
continue
try:
for shape in self.storage_dict[storage]['solid_geometry']:
if shape.geo is None:
continue
if shape in self.selected:
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'],
linewidth=2)
continue
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'])
if shape in self.selected:
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'],
linewidth=2)
continue
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'])
except KeyError:
pass
for shape in self.utility:
self.plot_shape(geometry=shape.geo, linewidth=1)
@ -3498,6 +3574,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.add(shape=geometry, color=color, face_color=color+'AF', layer=0)
def start_delayed_plot(self, check_period):
"""
This function starts an QTImer and it will periodically check if all the workers finish the plotting functions
:param check_period: time at which to check periodically if all plots finished to be plotted
:return:
"""
# self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
# self.plot_thread.start()
log.debug("FlatCAMGrbEditor --> Delayed Plot started.")
@ -3507,7 +3590,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.plot_thread.start()
def check_plot_finished(self):
# print(self.grb_plot_promises)
"""
If all the promises made are finished then all the shapes are in shapes_storage and can be plotted safely and
then the UI is rebuilt accordingly.
:return:
"""
try:
if not self.grb_plot_promises:
self.plot_thread.stop()
@ -3571,13 +3659,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
return
for storage in self.storage_dict:
# try:
# self.storage_dict[storage].remove(shape)
# except:
# pass
if shape in self.storage_dict[storage]['solid_geometry']:
self.storage_dict[storage]['solid_geometry'].remove(shape)
try:
if shape in self.storage_dict[storage]['solid_geometry']:
self.storage_dict[storage]['solid_geometry'].remove(shape)
except KeyError:
pass
if shape in self.selected:
self.selected.remove(shape) # TODO: Check performance

View File

@ -638,6 +638,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), _("Panel Tool"))
self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'),_( "Film Tool"))
self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), _("SolderPaste Tool"))
self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Substract Tool"))
self.toolbartools.addSeparator()
self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'), _("Calculators Tool"))
@ -699,7 +701,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/poligonize32.png'),
_("Poligonize"))
self.grb_add_semidisc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/semidisc32.png'), _("SemiDisc"))
self.grb_add_disc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/disc32.png'), _("Disc"))
self.grb_edit_toolbar.addSeparator()
@ -752,12 +753,27 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
################
### Project ###
# self.project_tab = QtWidgets.QWidget()
# self.project_tab.setObjectName("project_tab")
# # project_tab.setMinimumWidth(250) # Hack
# self.project_tab_layout = QtWidgets.QVBoxLayout(self.project_tab)
# self.project_tab_layout.setContentsMargins(2, 2, 2, 2)
# self.notebook.addTab(self.project_tab,_( "Project"))
self.project_tab = QtWidgets.QWidget()
self.project_tab.setObjectName("project_tab")
# project_tab.setMinimumWidth(250) # Hack
self.project_tab_layout = QtWidgets.QVBoxLayout(self.project_tab)
self.project_frame_lay = QtWidgets.QVBoxLayout(self.project_tab)
self.project_frame_lay.setContentsMargins(0, 0, 0, 0)
self.project_frame = QtWidgets.QFrame()
self.project_frame.setContentsMargins(0, 0, 0, 0)
self.project_frame_lay.addWidget(self.project_frame)
self.project_tab_layout = QtWidgets.QVBoxLayout(self.project_frame)
self.project_tab_layout.setContentsMargins(2, 2, 2, 2)
self.notebook.addTab(self.project_tab,_( "Project"))
self.notebook.addTab(self.project_tab, _("Project"))
self.project_frame.setDisabled(False)
### Selected ###
self.selected_tab = QtWidgets.QWidget()
@ -1545,12 +1561,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
##############################################################
### HERE WE BUILD THE CONTEXT MENU FOR RMB CLICK ON CANVAS ###
##############################################################
self.popMenu = QtWidgets.QMenu()
self.popMenu = FCMenu()
self.popmenu_disable = self.popMenu.addAction(QtGui.QIcon('share/clear_plot32.png'), _("Disable"))
self.popMenu.addSeparator()
self.cmenu_newmenu = self.popMenu.addMenu(QtGui.QIcon('share/file32.png'), _("New"))
self.popmenu_new_geo = self.cmenu_newmenu.addAction(QtGui.QIcon('share/new_geo32_bis.png'), _("Geometry"))
self.popmenu_new_grb = self.cmenu_newmenu.addAction(QtGui.QIcon('share/flatcam_icon32.png'), "Gerber")
self.popmenu_new_exc = self.cmenu_newmenu.addAction(QtGui.QIcon('share/new_exc32.png'), _("Excellon"))
self.cmenu_newmenu.addSeparator()
self.popmenu_new_prj = self.cmenu_newmenu.addAction(QtGui.QIcon('share/file16.png'), _("Project"))
@ -1576,15 +1593,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
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"))
self.drill_copy = self.e_editor_cmenu.addAction(QtGui.QIcon('share/copy32.png'), _("Copy Drill(s)"))
self.popMenu.addSeparator()
self.popmenu_copy = self.popMenu.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
@ -1724,9 +1736,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# start with GRID activated
self.grid_snap_btn.trigger()
self.g_editor_cmenu.setEnabled(False)
self.grb_editor_cmenu.setEnabled(False)
self.e_editor_cmenu.setEnabled(False)
self.g_editor_cmenu.menuAction().setVisible(False)
self.grb_editor_cmenu.menuAction().setVisible(False)
self.e_editor_cmenu.menuAction().setVisible(False)
self.general_defaults_form = GeneralPreferencesUI()
self.gerber_defaults_form = GerberPreferencesUI()
@ -1844,13 +1856,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), _("Film Tool"))
self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'),
_("SolderPaste Tool"))
self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Substract Tool"))
self.toolbartools.addSeparator()
self.calculators_btn = self.toolbartools.addAction(QtGui.QIcon('share/calculator24.png'),
_("Calculators Tool"))
self.transform_btn = self.toolbartools.addAction(QtGui.QIcon('share/transform.png'), _("Transform Tool"))
### Drill Editor Toolbar ###
### Excellon Editor Toolbar ###
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(
@ -1906,6 +1920,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
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_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/poligonize32.png'),
_("Poligonize"))
self.grb_add_semidisc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/semidisc32.png'), _("SemiDisc"))
self.grb_add_disc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/disc32.png'), _("Disc"))
self.grb_edit_toolbar.addSeparator()
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
@ -1974,7 +1993,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
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.setVisible(True)
self.grb_edit_toolbar.setDisabled(True)
self.corner_snap_btn.setVisible(True)
@ -2146,6 +2165,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.cutout_tool.run(toggle=True)
return
# Substract Tool
if key == QtCore.Qt.Key_W:
self.app.sub_tool.run(toggle=True)
return
# Panelize Tool
if key == QtCore.Qt.Key_Z:
self.app.panelize_tool.run(toggle=True)
@ -2214,6 +2238,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_Space:
for select in selected:
select.ui.plot_cb.toggle()
self.app.collection.update_view()
self.app.delete_selection_shape()
# New Geometry
@ -2807,7 +2832,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.exc_editor.replot()
# self.select_btn.setChecked(True)
# self.on_tool_select('select')
self.app.exc_editor.select_tool('select')
self.app.exc_editor.select_tool('drill_select')
return
# Delete selected object if delete key event comes out of canvas
@ -2943,7 +2968,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_T or key == 'T':
self.app.exc_editor.launched_from_shortcuts = True
## Current application units in Upper Case
self.units = self.general_defaults_group.general_app_group.units_radio.get_value().upper()
self.units = self.general_defaults_form.general_app_group.units_radio.get_value().upper()
tool_add_popup = FCInputDialog(title=_("New Tool ..."),
text=_('Enter a Tool Diameter:'),
min=0.0000, max=99.9999, decimals=4)
@ -3047,6 +3072,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
'params': [self.filename, object_type, None]})
if extension in self.app.pdf_list:
self.app.pdf_tool.periodic_check(1000)
self.app.worker_task.emit({'fcn': self.app.pdf_tool.open_pdf,
'params': [self.filename]})
@ -3483,6 +3509,34 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.form_box_child_11.addWidget(self.sel_draw_color_button)
self.form_box_child_11.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
# Project Tab items color
self.proj_color_label = QtWidgets.QLabel(_('Project Items:'))
self.proj_color_label.setToolTip(
_("Set the color of the items in Project Tab Tree.")
)
self.proj_color_entry = FCEntry()
self.proj_color_button = QtWidgets.QPushButton()
self.proj_color_button.setFixedSize(15, 15)
self.form_box_child_12 = QtWidgets.QHBoxLayout()
self.form_box_child_12.addWidget(self.proj_color_entry)
self.form_box_child_12.addWidget(self.proj_color_button)
self.form_box_child_12.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.proj_color_dis_label = QtWidgets.QLabel(_('Proj. Dis. Items:'))
self.proj_color_dis_label.setToolTip(
_("Set the color of the items in Project Tab Tree,\n"
"for the case when the items are disabled.")
)
self.proj_color_dis_entry = FCEntry()
self.proj_color_dis_button = QtWidgets.QPushButton()
self.proj_color_dis_button.setFixedSize(15, 15)
self.form_box_child_13 = QtWidgets.QHBoxLayout()
self.form_box_child_13.addWidget(self.proj_color_dis_entry)
self.form_box_child_13.addWidget(self.proj_color_dis_button)
self.form_box_child_13.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
# Just to add empty rows
self.spacelabel = QtWidgets.QLabel('')
@ -3507,6 +3561,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.form_box.addRow(self.alt_sl_color_label, self.form_box_child_9)
self.form_box.addRow(self.draw_color_label, self.form_box_child_10)
self.form_box.addRow(self.sel_draw_color_label, self.form_box_child_11)
self.form_box.addRow(QtWidgets.QLabel(""))
self.form_box.addRow(self.proj_color_label, self.form_box_child_12)
self.form_box.addRow(self.proj_color_dis_label, self.form_box_child_13)
self.form_box.addRow(self.spacelabel, self.spacelabel)
@ -3589,6 +3646,16 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
)
self.hover_cb = FCCheckBox()
# Enable Selection box
self.selection_label = QtWidgets.QLabel(_('Sel. Shape:'))
self.selection_label.setToolTip(
_("Enable the display of a selection shape for FlatCAM objects.\n"
"It is displayed whenever the mouse selects an object\n"
"either by clicking or dragging mouse from left to right or\n"
"right to left.")
)
self.selection_cb = FCCheckBox()
# Just to add empty rows
self.spacelabel = QtWidgets.QLabel('')
@ -3600,6 +3667,7 @@ class GeneralGUISetGroupUI(OptionsGroupUI):
self.form_box.addRow(self.hdpi_label, self.hdpi_cb)
self.form_box.addRow(self.clear_label, self.clear_btn)
self.form_box.addRow(self.hover_label, self.hover_cb)
self.form_box.addRow(self.selection_label, self.selection_cb)
# Add the QFormLayout that holds the Application general defaults
# to the main layout of this TAB
@ -3667,8 +3735,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
"ADVANCED level -> full functionality.\n\n"
"The choice here will influence the parameters in\n"
"the Selected Tab for all kinds of FlatCAM objects."))
self.app_level_radio = RadioSet([{'label': _('Basic'), 'value': 'b'},
{'label': _('Advanced'), 'value': 'a'}])
self.app_level_radio = RadioSet([{'label': 'Basic', 'value': 'b'},
{'label': 'Advanced', 'value': 'a'}])
# Languages for FlatCAM
self.languagelabel = QtWidgets.QLabel(_('<b>Languages:</b>'))
@ -3676,6 +3744,13 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
self.language_cb = FCComboBox()
self.languagespace = QtWidgets.QLabel('')
self.language_apply_btn = FCButton(_("Apply Language"))
self.language_apply_btn.setToolTip(_("Set the language used throughout FlatCAM.\n"
"The app will restart after click."
"Windows: When FlatCAM is installed in Program Files\n"
"directory, it is possible that the app will not\n"
"restart after the button is clicked due of Windows\n"
"security features. In this case the language will be\n"
"applied at the next app start."))
# Shell StartUp CB
self.shell_startup_label = QtWidgets.QLabel(_('Shell at StartUp:'))
@ -3720,14 +3795,14 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
"- MMB --> Middle Mouse Button\n"
"- RMB --> Right Mouse Button"))
self.pan_button_radio = RadioSet([{'label': _('MMB'), 'value': '3'},
{'label': _('RMB'), 'value': '2'}])
self.pan_button_radio = RadioSet([{'label': 'MMB', 'value': '3'},
{'label': 'RMB', 'value': '2'}])
# Multiple Selection Modifier Key
self.mselectlabel = QtWidgets.QLabel(_('<b>Multiple Sel:</b>'))
self.mselectlabel.setToolTip(_("Select the key used for multiple selection."))
self.mselect_radio = RadioSet([{'label': _('CTRL'), 'value': 'Control'},
{'label': _('SHIFT'), 'value': 'Shift'}])
self.mselect_radio = RadioSet([{'label': 'CTRL', 'value': 'Control'},
{'label': 'SHIFT', 'value': 'Shift'}])
# Project at StartUp CB
self.project_startup_label = QtWidgets.QLabel(_('Project at StartUp:'))
@ -3955,8 +4030,8 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
"- conventional / useful when there is no backlash compensation")
)
grid0.addWidget(milling_type_label, 3, 0)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conv.'), 'value': 'cv'}])
self.milling_type_radio = RadioSet([{'label': 'Climb', 'value': 'cl'},
{'label': 'Conv.', 'value': 'cv'}])
grid0.addWidget(self.milling_type_radio, 3, 1)
# Combine passes
@ -4224,8 +4299,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
)
hlay3.addWidget(self.excellon_zeros_label)
self.excellon_zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
{'label': _('TZ'), 'value': 'T'}])
self.excellon_zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'},
{'label': 'TZ', 'value': 'T'}])
self.excellon_zeros_radio.setToolTip(
_("This sets the default type of Excellon zeros.\n"
"If it is not detected in the parsed file the value here\n"
@ -4252,8 +4327,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
)
hlay4.addWidget(self.excellon_units_label)
self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
{'label': _('MM'), 'value': 'METRIC'}])
self.excellon_units_radio = RadioSet([{'label': 'INCH', 'value': 'INCH'},
{'label': 'MM', 'value': 'METRIC'}])
self.excellon_units_radio.setToolTip(
_("This sets the units of Excellon files.\n"
"Some Excellon files don't have an header\n"
@ -4291,8 +4366,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
"Travelling Salesman algorithm for path optimization.")
)
self.excellon_optimization_radio = RadioSet([{'label': _('MH'), 'value': 'M'},
{'label': _('Basic'), 'value': 'B'}])
self.excellon_optimization_radio = RadioSet([{'label': 'MH', 'value': 'M'},
{'label': 'Basic', 'value': 'B'}])
self.excellon_optimization_radio.setToolTip(
_("This sets the optimization type for the Excellon drill path.\n"
"If MH is checked then Google OR-Tools algorithm with MetaHeuristic\n"
@ -4457,9 +4532,9 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
"When choosing 'Slots' or 'Both', slots will be\n"
"converted to drills.")
)
self.excellon_gcode_type_radio = RadioSet([{'label': _('Drills'), 'value': 'drills'},
{'label': _('Slots'), 'value': 'slots'},
{'label': _('Both'), 'value': 'both'}])
self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
{'label': 'Slots', 'value': 'slots'},
{'label': 'Both', 'value': 'both'}])
grid2.addWidget(excellon_gcode_type_label, 9, 0)
grid2.addWidget(self.excellon_gcode_type_radio, 9, 1)
@ -4643,8 +4718,8 @@ class ExcellonExpPrefGroupUI(OptionsGroupUI):
_("The units used in the Excellon file.")
)
self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
{'label': _('MM'), 'value': 'METRIC'}])
self.excellon_units_radio = RadioSet([{'label': 'INCH', 'value': 'INCH'},
{'label': 'MM', 'value': 'METRIC'}])
self.excellon_units_radio.setToolTip(
_("The units used in the Excellon file.")
)
@ -4699,8 +4774,8 @@ class ExcellonExpPrefGroupUI(OptionsGroupUI):
"Also it will have to be specified if LZ = leading zeros are kept\n"
"or TZ = trailing zeros are kept.")
)
self.format_radio = RadioSet([{'label': _('Decimal'), 'value': 'dec'},
{'label': _('No-Decimal'), 'value': 'ndec'}])
self.format_radio = RadioSet([{'label': 'Decimal', 'value': 'dec'},
{'label': 'No-Decimal', 'value': 'ndec'}])
self.format_radio.setToolTip(
_("Select the kind of coordinates format used.\n"
"Coordinates can be saved with decimal point or without.\n"
@ -4723,8 +4798,8 @@ class ExcellonExpPrefGroupUI(OptionsGroupUI):
"and Leading Zeros are removed.")
)
self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
{'label': _('TZ'), 'value': 'TZ'}])
self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'LZ'},
{'label': 'TZ', 'value': 'TZ'}])
self.zeros_radio.setToolTip(
_("This sets the default type of Excellon zeros.\n"
"If LZ then Leading Zeros are kept and\n"
@ -5105,9 +5180,9 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
)
self.cncplot_method_radio = RadioSet([
{"label": _("All"), "value": "all"},
{"label": _("Travel"), "value": "travel"},
{"label": _("Cut"), "value": "cut"}
{"label": "All", "value": "all"},
{"label": "Travel", "value": "travel"},
{"label": "Cut", "value": "cut"}
], stretch=False)
grid0.addWidget(self.cncplot_method_label, 1, 0)
@ -5341,9 +5416,9 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(methodlabel, 3, 0)
self.ncc_method_radio = RadioSet([
{"label": _("Standard"), "value": "standard"},
{"label": _("Seed-based"), "value": "seed"},
{"label": _("Straight lines"), "value": "lines"}
{"label": "Standard", "value": "standard"},
{"label": "Seed-based", "value": "seed"},
{"label": "Straight lines", "value": "lines"}
], orientation='vertical', stretch=False)
grid0.addWidget(self.ncc_method_radio, 3, 1)
@ -5490,8 +5565,8 @@ class Tools2sidedPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.drill_dia_entry, 0, 1)
## Axis
self.mirror_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
{'label': _('Y'), 'value': 'Y'}])
self.mirror_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
{'label': 'Y', 'value': 'Y'}])
self.mirax_label = QtWidgets.QLabel(_("Mirror Axis:"))
self.mirax_label.setToolTip(
_("Mirror vertically (X) or horizontally (Y).")
@ -5503,8 +5578,8 @@ class Tools2sidedPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.mirror_axis_radio, 2, 1)
## Axis Location
self.axis_location_radio = RadioSet([{'label': _('Point'), 'value': 'point'},
{'label': _('Box'), 'value': 'box'}])
self.axis_location_radio = RadioSet([{'label': 'Point', 'value': 'point'},
{'label': 'Box', 'value': 'box'}])
self.axloc_label = QtWidgets.QLabel(_("Axis Ref:"))
self.axloc_label.setToolTip(
_("The axis should pass through a <b>point</b> or cut\n "
@ -5581,9 +5656,9 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(methodlabel, 3, 0)
self.paintmethod_combo = RadioSet([
{"label": _("Standard"), "value": "standard"},
{"label": _("Seed-based"), "value": "seed"},
{"label": _("Straight lines"), "value": "lines"}
{"label": "Standard", "value": "standard"},
{"label": "Seed-based", "value": "seed"},
{"label": "Straight lines", "value": "lines"}
], orientation='vertical', stretch=False)
grid0.addWidget(self.paintmethod_combo, 3, 1)
@ -5614,8 +5689,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(selectlabel, 6, 0)
self.selectmethod_combo = RadioSet([
{"label": _("Single"), "value": "single"},
{"label": _("All"), "value": "all"},
{"label": "Single", "value": "single"},
{"label": "All", "value": "all"},
# {"label": "Rectangle", "value": "rectangle"}
])
grid0.addWidget(self.selectmethod_combo, 6, 1)
@ -5642,8 +5717,8 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
self.film_type_radio = RadioSet([{'label': _('Pos'), 'value': 'pos'},
{'label': _('Neg'), 'value': 'neg'}])
self.film_type_radio = RadioSet([{'label': 'Pos', 'value': 'pos'},
{'label': 'Neg', 'value': 'neg'}])
ftypelbl = QtWidgets.QLabel(_('Film Type:'))
ftypelbl.setToolTip(
_("Generate a Positive black film or a Negative film.\n"
@ -5742,8 +5817,8 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.prows, 3, 1)
## Type of resulting Panel object
self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
{'label': _('Geo'), 'value': 'geometry'}])
self.panel_type_radio = RadioSet([{'label': 'Gerber', 'value': 'gerber'},
{'label': 'Geo', 'value': 'geometry'}])
self.panel_type_label = QtWidgets.QLabel(_("Panel Type:"))
self.panel_type_label.setToolTip(
_( "Choose the type of object for the panel object:\n"

View File

@ -367,7 +367,11 @@ class FCEntry2(FCEntry):
self.readyToEdit = True
def set_value(self, val):
self.setText('%.4f' % float(val))
try:
fval = float(val)
except ValueError:
return
self.setText('%.4f' % fval)
class EvalEntry(QtWidgets.QLineEdit):
@ -676,6 +680,16 @@ class FCButton(QtWidgets.QPushButton):
self.setText(str(val))
class FCMenu(QtWidgets.QMenu):
def __init__(self):
super().__init__()
self.mouse_is_panning = False
def popup(self, pos, action=None):
self.mouse_is_panning = False
super().popup(pos)
class FCTab(QtWidgets.QTabWidget):
def __init__(self, parent=None):
super(FCTab, self).__init__(parent)

View File

@ -153,7 +153,7 @@ class GerberObjectUI(ObjectUI):
grid0.addWidget(self.plot_options_label, 0, 0)
# Solid CB
self.solid_cb = FCCheckBox(label=_('Solid '))
self.solid_cb = FCCheckBox(label=_('Solid'))
self.solid_cb.setToolTip(
_("Solid color polygons.")
)
@ -161,7 +161,7 @@ class GerberObjectUI(ObjectUI):
grid0.addWidget(self.solid_cb, 0, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label=_('M-Color '))
self.multicolored_cb = FCCheckBox(label=_('M-Color'))
self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.")
)
@ -299,8 +299,8 @@ class GerberObjectUI(ObjectUI):
"- conventional / useful when there is no backlash compensation")
)
grid1.addWidget(self.milling_type_label, 3, 0)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conv.'), 'value': 'cv'}])
self.milling_type_radio = RadioSet([{'label': 'Climb', 'value': 'cl'},
{'label': 'Conv.', 'value': 'cv'}])
grid1.addWidget(self.milling_type_radio, 3, 1)
# combine all passes CB
@ -749,9 +749,9 @@ class ExcellonObjectUI(ObjectUI):
"When choosing 'Slots' or 'Both', slots will be\n"
"converted to a series of drills.")
)
self.excellon_gcode_type_radio = RadioSet([{'label': _('Drills'), 'value': 'drills'},
{'label': _('Slots'), 'value': 'slots'},
{'label': _('Both'), 'value': 'both'}])
self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
{'label': 'Slots', 'value': 'slots'},
{'label': 'Both', 'value': 'both'}])
gcode_box.addRow(gcode_type_label, self.excellon_gcode_type_radio)
self.tools_box.addLayout(gcode_box)
@ -1355,9 +1355,9 @@ class CNCObjectUI(ObjectUI):
)
self.cncplot_method_combo = RadioSet([
{"label": _("All"), "value": "all"},
{"label": _("Travel"), "value": "travel"},
{"label": _("Cut"), "value": "cut"}
{"label": "All", "value": "all"},
{"label": "Travel", "value": "travel"},
{"label": "Cut", "value": "cut"}
], stretch=False)
## Object name

View File

@ -12,9 +12,9 @@ import math
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
import builtins
if '_' not in builtins.__dict__:
_ = gettext.gettext

View File

@ -2,7 +2,7 @@
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# File Author: Marius Adrian Stanciu (c) #
# Date: 3/10/2019 #
# Date: 4/23/2019 #
# MIT Licence #
############################################################
@ -18,6 +18,7 @@ import numpy as np
import zlib
import re
import time
import gettext
import FlatCAMTranslation as fcTranslate
@ -106,9 +107,18 @@ class ToolPDF(FlatCAMTool):
self.gs['transform'] = []
self.gs['line_width'] = [] # each element is a float
self.obj_dict = dict()
self.pdf_parsed = ''
self.parsed_obj_dict = dict()
self.pdf_decompressed = {}
# key = file name and extension
# value is a dict to store the parsed content of the PDF
self.pdf_parsed = {}
# QTimer for periodic check
self.check_thread = QtCore.QTimer()
# Every time a parser is started we add a promise; every time a parser finished we remove a promise
# when empty we start the layer rendering
self.parsing_promises = []
# conversion factor to INCH
self.point_to_unit_factor = 0.01388888888
@ -148,16 +158,22 @@ class ToolPDF(FlatCAMTool):
if len(filenames) == 0:
self.app.inform.emit(_("[WARNING_NOTCL] Open PDF cancelled."))
else:
# start the parsing timer with a period of 1 second
self.periodic_check(1000)
for filename in filenames:
if filename != '':
self.app.worker_task.emit({'fcn': self.open_pdf, 'params': [filename]})
self.app.worker_task.emit({'fcn': self.open_pdf,
'params': [filename]})
def open_pdf(self, filename):
new_name = filename.split('/')[-1].split('\\')[-1]
self.obj_dict.clear()
self.pdf_parsed = ''
self.parsed_obj_dict = {}
obj_type = 'gerber'
short_name = filename.split('/')[-1].split('\\')[-1]
self.parsing_promises.append(short_name)
self.pdf_parsed[short_name] = {}
self.pdf_parsed[short_name]['pdf'] = {}
self.pdf_parsed[short_name]['filename'] = filename
self.pdf_decompressed[short_name] = ''
# the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
@ -177,102 +193,183 @@ class ToolPDF(FlatCAMTool):
log.debug(" PDF STREAM: %d\n" % stream_nr)
s = s.strip(b'\r\n')
try:
self.pdf_parsed += (zlib.decompress(s).decode('UTF-8') + '\r\n')
self.pdf_decompressed[short_name] += (zlib.decompress(s).decode('UTF-8') + '\r\n')
except Exception as e:
log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
self.parsed_obj_dict = self.parse_pdf(pdf_content=self.pdf_parsed)
self.pdf_parsed[short_name]['pdf'] = self.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
# we used it, now we delete it
self.pdf_decompressed[short_name] = ''
for k in self.parsed_obj_dict:
ap_dict = deepcopy(self.parsed_obj_dict[k])
if ap_dict:
if k == 0:
# Excellon
obj_type = 'excellon'
# removal from list is done in a multithreaded way therefore not always the removal can be done
# try to remove until it's done
try:
while True:
self.parsing_promises.remove(short_name)
time.sleep(0.1)
except:
pass
self.app.inform.emit(_("[success] Opened: %s") % filename)
new_name = new_name + "_exc"
# store the points here until reconstitution: keys are diameters and values are list of (x,y) coords
points = {}
def layer_rendering_as_excellon(self, filename, ap_dict, layer_nr):
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
def obj_init(exc_obj, app_obj):
# print(self.parsed_obj_dict[0])
# store the points here until reconstitution:
# keys are diameters and values are list of (x,y) coords
points = {}
for geo in self.parsed_obj_dict[0]['0']['solid_geometry']:
xmin, ymin, xmax, ymax = geo.bounds
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
def obj_init(exc_obj, app_obj):
# for drill bits, even in INCH, it's enough 3 decimals
correction_factor = 0.974
dia = (xmax - xmin) * correction_factor
dia = round(dia, 3)
if dia in points:
points[dia].append(center)
else:
points[dia] = [center]
for geo in ap_dict['0']['solid_geometry']:
xmin, ymin, xmax, ymax = geo.bounds
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
sorted_dia = sorted(points.keys())
name_tool = 0
for dia in sorted_dia:
name_tool += 1
# create tools dictionary
spec = {"C": dia}
spec['solid_geometry'] = []
exc_obj.tools[str(name_tool)] = spec
# create drill list of dictionaries
for dia_points in points:
if dia == dia_points:
for pt in points[dia_points]:
exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
break
ret = exc_obj.create_geometry()
if ret == 'fail':
log.debug("Could not create geometry for Excellon object.")
return "fail"
for tool in exc_obj.tools:
if exc_obj.tools[tool]['solid_geometry']:
return
app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % new_name)
return "fail"
# for drill bits, even in INCH, it's enough 3 decimals
correction_factor = 0.974
dia = (xmax - xmin) * correction_factor
dia = round(dia, 3)
if dia in points:
points[dia].append(center)
else:
# Gerber
obj_type = 'gerber'
points[dia] = [center]
def obj_init(grb_obj, app_obj):
sorted_dia = sorted(points.keys())
grb_obj.apertures = ap_dict
name_tool = 0
for dia in sorted_dia:
name_tool += 1
poly_buff = []
for ap in grb_obj.apertures:
for k in grb_obj.apertures[ap]:
if k == 'solid_geometry':
poly_buff += ap_dict[ap][k]
# create tools dictionary
spec = {"C": dia}
spec['solid_geometry'] = []
exc_obj.tools[str(name_tool)] = spec
poly_buff = unary_union(poly_buff)
try:
poly_buff = poly_buff.buffer(0.0000001)
except ValueError:
pass
try:
poly_buff = poly_buff.buffer(-0.0000001)
except ValueError:
pass
# create drill list of dictionaries
for dia_points in points:
if dia == dia_points:
for pt in points[dia_points]:
exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
break
grb_obj.solid_geometry = deepcopy(poly_buff)
ret = exc_obj.create_geometry()
if ret == 'fail':
log.debug("Could not create geometry for Excellon object.")
return "fail"
for tool in exc_obj.tools:
if exc_obj.tools[tool]['solid_geometry']:
return
app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % outname)
return "fail"
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % (int(k) - 2)):
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
ret = self.app.new_object(obj_type, new_name, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
return
# Register recent file
self.app.file_opened.emit(obj_type, filename)
# GUI feedback
self.app.inform.emit(_("[success] Opened: %s") % filename)
ret = self.app.new_object("excellon", outname, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
return
# Register recent file
self.app.file_opened.emit("excellon", filename)
# GUI feedback
self.app.inform.emit(_("[success] Rendered: %s") % outname)
def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
def obj_init(grb_obj, app_obj):
grb_obj.apertures = ap_dict
poly_buff = []
for ap in grb_obj.apertures:
for k in grb_obj.apertures[ap]:
if k == 'solid_geometry':
poly_buff += ap_dict[ap][k]
poly_buff = unary_union(poly_buff)
try:
poly_buff = poly_buff.buffer(0.0000001)
except ValueError:
pass
try:
poly_buff = poly_buff.buffer(-0.0000001)
except ValueError:
pass
grb_obj.solid_geometry = deepcopy(poly_buff)
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
return
# Register recent file
self.app.file_opened.emit('gerber', filename)
# GUI feedback
self.app.inform.emit(_("[success] Rendered: %s") % outname)
def periodic_check(self, check_period):
"""
This function starts an QTimer and it will periodically check if parsing was done
:param check_period: time at which to check periodically if all plots finished to be plotted
:return:
"""
# self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
# self.plot_thread.start()
log.debug("ToolPDF --> Periodic Check started.")
try:
self.check_thread.stop()
except:
pass
self.check_thread.setInterval(check_period)
try:
self.check_thread.timeout.disconnect(self.periodic_check_handler)
except:
pass
self.check_thread.timeout.connect(self.periodic_check_handler)
self.check_thread.start(QtCore.QThread.HighPriority)
def periodic_check_handler(self):
"""
If the parsing worker finished then start multithreaded rendering
:return:
"""
# log.debug("checking parsing --> %s" % str(self.parsing_promises))
try:
if not self.parsing_promises:
self.check_thread.stop()
# parsing finished start the layer rendering
if self.pdf_parsed:
obj_to_delete = []
for object_name in self.pdf_parsed:
filename = deepcopy(self.pdf_parsed[object_name]['filename'])
pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
obj_to_delete.append(object_name)
for k in pdf_content:
ap_dict = pdf_content[k]
if ap_dict:
layer_nr = k
if k == 0:
self.app.worker_task.emit({'fcn': self.layer_rendering_as_excellon,
'params': [filename, ap_dict, layer_nr]})
else:
self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber,
'params': [filename, ap_dict, layer_nr]})
# delete the object already processed so it will not be processed again for other objects
# that were opened at the same time; like in drag & drop on GUI
for obj_name in obj_to_delete:
if obj_name in self.pdf_parsed:
self.pdf_parsed.pop(obj_name)
log.debug("ToolPDF --> Periodic check finished.")
except Exception:
traceback.print_exc()
def parse_pdf(self, pdf_content):
path = dict()
@ -301,9 +398,10 @@ class ToolPDF(FlatCAMTool):
# store the objects to be transformed into Gerbers
object_dict = {}
# will serve as key in the object_dict
object_nr = 1
layer_nr = 1
# create first object
object_dict[layer_nr] = {}
# store the apertures here
apertures_dict = {}
@ -320,15 +418,11 @@ class ToolPDF(FlatCAMTool):
clear_apertures_dict['0']['type'] = 'C'
clear_apertures_dict['0']['solid_geometry'] = []
# create first object
object_dict[object_nr] = apertures_dict
object_nr += 1
# on stroke color change we create a new apertures dictionary and store the old one in a storage from where
# it will be transformed into Gerber object
old_color = [None, None ,None]
# signal that we have clear geometry and the geometry will be added to a special object_nr = 0
# signal that we have clear geometry and the geometry will be added to a special layer_nr = 0
flag_clear_geo = False
line_nr = 0
@ -350,11 +444,12 @@ class ToolPDF(FlatCAMTool):
# same color, do nothing
continue
else:
object_dict[object_nr] = deepcopy(apertures_dict)
object_nr += 1
if apertures_dict:
object_dict[layer_nr] = deepcopy(apertures_dict)
apertures_dict.clear()
layer_nr += 1
object_dict[object_nr] = dict()
apertures_dict = {}
object_dict[layer_nr] = dict()
old_color = copy(color)
# we make sure that the following geometry is added to the right storage
flag_clear_geo = False
@ -536,7 +631,6 @@ class ToolPDF(FlatCAMTool):
y * self.point_to_unit_factor * scale_geo[1])
subpath['bezier'].append([start, c1, stop, stop])
print(subpath['bezier'])
current_point = stop
continue
@ -747,18 +841,18 @@ class ToolPDF(FlatCAMTool):
if path['rectangle']:
for subp in path['rectangle']:
geo = copy(subp)
# close the subpath if it was not closed already
if close_subpath is False and start_point is not None:
geo.append(start_point)
# # close the subpath if it was not closed already
# if close_subpath is False and start_point is not None:
# geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el)
# the path was painted therefore initialize it
path['rectangle'] = []
else:
geo = copy(subpath['rectangle'])
# close the subpath if it was not closed already
if close_subpath is False and start_point is not None:
geo.append(start_point)
# # close the subpath if it was not closed already
# if close_subpath is False and start_point is not None:
# geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el)
subpath['rectangle'] = []
@ -869,9 +963,9 @@ class ToolPDF(FlatCAMTool):
# fill
for subp in path['rectangle']:
geo = copy(subp)
# close the subpath if it was not closed already
if close_subpath is False:
geo.append(geo[0])
# # close the subpath if it was not closed already
# if close_subpath is False:
# geo.append(geo[0])
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
fill_geo.append(geo_el)
# stroke
@ -884,9 +978,9 @@ class ToolPDF(FlatCAMTool):
else:
# fill
geo = copy(subpath['rectangle'])
# close the subpath if it was not closed already
if close_subpath is False:
geo.append(start_point)
# # close the subpath if it was not closed already
# if close_subpath is False:
# geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
fill_geo.append(geo_el)
# stroke
@ -940,11 +1034,20 @@ class ToolPDF(FlatCAMTool):
# tidy up. copy the current aperture dict to the object dict but only if it is not empty
if apertures_dict:
object_dict[object_nr] = deepcopy(apertures_dict)
object_dict[layer_nr] = deepcopy(apertures_dict)
if clear_apertures_dict['0']['solid_geometry']:
object_dict[0] = deepcopy(clear_apertures_dict)
# delete keys (layers) with empty values
empty_layers = []
for layer in object_dict:
if not object_dict[layer]:
empty_layers.append(layer)
for x in empty_layers:
if x in object_dict:
object_dict.pop(x)
return object_dict
def bezier_to_points(self, start, c1, c2, stop):
@ -958,7 +1061,7 @@ class ToolPDF(FlatCAMTool):
# with the final point P3. Intermediate values of t generate intermediate points along the curve.
# The curve does not, in general, pass through the two control points P1 and P2
:return: LineString geometry
:return: A list of point coordinates tuples (x, y)
"""
# here we store the geometric points

553
flatcamTools/ToolSub.py Normal file
View File

@ -0,0 +1,553 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# File Author: Marius Adrian Stanciu (c) #
# Date: 4/24/2019 #
# MIT Licence #
############################################################
from FlatCAMTool import FlatCAMTool
# from copy import copy, deepcopy
from ObjectCollection import *
import time
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class ToolSub(FlatCAMTool):
toolName = _("Substract Tool")
def __init__(self, app):
self.app = app
FlatCAMTool.__init__(self, app)
self.tools_frame = QtWidgets.QFrame()
self.tools_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.tools_frame)
self.tools_box = QtWidgets.QVBoxLayout()
self.tools_box.setContentsMargins(0, 0, 0, 0)
self.tools_frame.setLayout(self.tools_box)
# Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.tools_box.addWidget(title_label)
# Form Layout
form_layout = QtWidgets.QFormLayout()
self.tools_box.addLayout(form_layout)
self.gerber_title = QtWidgets.QLabel(_("<b>Gerber Objects</b>"))
form_layout.addRow(self.gerber_title)
# Target Gerber Object
self.target_gerber_combo = QtWidgets.QComboBox()
self.target_gerber_combo.setModel(self.app.collection)
self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.target_gerber_combo.setCurrentIndex(1)
self.target_gerber_label = QtWidgets.QLabel(_("Target:"))
self.target_gerber_label.setToolTip(
_("Gerber object from which to substract\n"
"the substractor Gerber object.")
)
form_layout.addRow(self.target_gerber_label, self.target_gerber_combo)
# Substractor Gerber Object
self.sub_gerber_combo = QtWidgets.QComboBox()
self.sub_gerber_combo.setModel(self.app.collection)
self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.sub_gerber_combo.setCurrentIndex(1)
self.sub_gerber_label = QtWidgets.QLabel(_("Substractor:"))
self.sub_gerber_label.setToolTip(
_("Gerber object that will be substracted\n"
"from the target Gerber object.")
)
e_lab_1 = QtWidgets.QLabel('')
form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo)
self.intersect_btn = FCButton(_('Substract Gerber'))
self.intersect_btn.setToolTip(
_("Will remove the area occupied by the substractor\n"
"Gerber from the Target Gerber.\n"
"Can be used to remove the overlapping silkscreen\n"
"over the soldermask.")
)
self.tools_box.addWidget(self.intersect_btn)
self.tools_box.addWidget(e_lab_1)
# Form Layout
form_geo_layout = QtWidgets.QFormLayout()
self.tools_box.addLayout(form_geo_layout)
self.geo_title = QtWidgets.QLabel(_("<b>Geometry Objects</b>"))
form_geo_layout.addRow(self.geo_title)
# Target Geometry Object
self.target_geo_combo = QtWidgets.QComboBox()
self.target_geo_combo.setModel(self.app.collection)
self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
self.target_geo_combo.setCurrentIndex(1)
self.target_geo_label = QtWidgets.QLabel(_("Target:"))
self.target_geo_label.setToolTip(
_("Geometry object from which to substract\n"
"the substractor Geometry object.")
)
form_geo_layout.addRow(self.target_geo_label, self.target_geo_combo)
# Substractor Geometry Object
self.sub_geo_combo = QtWidgets.QComboBox()
self.sub_geo_combo.setModel(self.app.collection)
self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
self.sub_geo_combo.setCurrentIndex(1)
self.sub_geo_label = QtWidgets.QLabel(_("Substractor:"))
self.sub_geo_label.setToolTip(
_("Geometry object that will be substracted\n"
"from the target Geometry object.")
)
e_lab_1 = QtWidgets.QLabel('')
form_geo_layout.addRow(self.sub_geo_label, self.sub_geo_combo)
self.intersect_geo_btn = FCButton(_('Substract Geometry'))
self.intersect_geo_btn.setToolTip(
_("Will remove the area occupied by the substractor\n"
"Geometry from the Target Geometry.")
)
self.tools_box.addWidget(self.intersect_geo_btn)
self.tools_box.addWidget(e_lab_1)
self.tools_box.addStretch()
# QTimer for periodic check
self.check_thread = QtCore.QTimer()
# Every time an intersection job is started we add a promise; every time an intersection job is finished
# we remove a promise.
# When empty we start the layer rendering
self.promises = []
self.new_apertures = {}
self.new_tools = {}
self.new_solid_geometry = []
self.sub_union = None
self.sub_grb_obj = None
self.sub_grb_obj_name = None
self.target_grb_obj = None
self.target_grb_obj_name = None
self.sub_geo_obj = None
self.sub_geo_obj_name = None
self.target_geo_obj = None
self.target_geo_obj_name = None
# signal which type of substraction to do: "geo" or "gerber"
self.sub_type = None
# store here the options from target_obj
self.target_options = {}
try:
self.intersect_btn.clicked.disconnect(self.on_grb_intersection_click)
except:
pass
self.intersect_btn.clicked.connect(self.on_grb_intersection_click)
try:
self.intersect_geo_btn.clicked.disconnect()
except:
pass
self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+W', **kwargs)
def run(self, toggle=True):
self.app.report_usage("ToolSub()")
if toggle:
# 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])
else:
try:
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
self.app.ui.splitter.setSizes([0, 1])
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
FlatCAMTool.run(self)
self.set_tool_ui()
self.new_apertures.clear()
self.new_tools.clear()
self.new_solid_geometry = []
self.target_options.clear()
self.app.ui.notebook.setTabText(2, _("Sub Tool"))
def set_tool_ui(self):
self.tools_frame.show()
def on_grb_intersection_click(self):
# reset previous values
self.new_apertures.clear()
self.new_solid_geometry = []
self.sub_union = []
self.sub_type = "gerber"
self.target_grb_obj_name = self.target_gerber_combo.currentText()
if self.target_grb_obj_name == '':
self.app.inform.emit(_("[ERROR_NOTCL] No Target object loaded."))
return
# Get source object.
try:
self.target_grb_obj = self.app.collection.get_by_name(self.target_grb_obj_name)
except:
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
return "Could not retrieve object: %s" % self.target_grb_obj_name
self.sub_grb_obj_name = self.sub_gerber_combo.currentText()
if self.sub_grb_obj_name == '':
self.app.inform.emit(_("[ERROR_NOTCL] No Substractor object loaded."))
return
# Get source object.
try:
self.sub_grb_obj = self.app.collection.get_by_name(self.sub_grb_obj_name)
except:
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
return "Could not retrieve object: %s" % self.sub_grb_obj_name
# crate the new_apertures dict structure
for apid in self.target_grb_obj.apertures:
self.new_apertures[apid] = {}
self.new_apertures[apid]['type'] = 'C'
self.new_apertures[apid]['size'] = self.target_grb_obj.apertures[apid]['size']
self.new_apertures[apid]['solid_geometry'] = []
geo_union_list = []
for apid1 in self.sub_grb_obj.apertures:
geo_union_list += self.sub_grb_obj.apertures[apid1]['solid_geometry']
self.sub_union = cascaded_union(geo_union_list)
# add the promises
for apid in self.target_grb_obj.apertures:
self.promises.append(apid)
# start the QTimer to check for promises with 1 second period check
self.periodic_check(500, reset=True)
for apid in self.target_grb_obj.apertures:
geo = self.target_grb_obj.apertures[apid]['solid_geometry']
self.app.worker_task.emit({'fcn': self.aperture_intersection,
'params': [apid, geo]})
def aperture_intersection(self, apid, geo):
new_solid_geometry = []
log.debug("Working on promise: %s" % str(apid))
with self.app.proc_container.new(_("Parsing aperture %s geometry ..." % str(apid))):
for geo_silk in geo:
if geo_silk.intersects(self.sub_union):
new_geo = geo_silk.difference(self.sub_union)
new_geo = new_geo.buffer(0)
if new_geo:
if not new_geo.is_empty:
new_solid_geometry.append(new_geo)
else:
new_solid_geometry.append(geo_silk)
else:
new_solid_geometry.append(geo_silk)
else:
new_solid_geometry.append(geo_silk)
if new_solid_geometry:
while not self.new_apertures[apid]['solid_geometry']:
self.new_apertures[apid]['solid_geometry'] = deepcopy(new_solid_geometry)
time.sleep(0.5)
while True:
# removal from list is done in a multithreaded way therefore not always the removal can be done
# so we keep trying until it's done
if apid not in self.promises:
break
self.promises.remove(apid)
time.sleep(0.5)
log.debug("Promise fulfilled: %s" % str(apid))
def new_gerber_object(self, outname):
def obj_init(grb_obj, app_obj):
grb_obj.apertures = deepcopy(self.new_apertures)
poly_buff = []
for ap in self.new_apertures:
for poly in self.new_apertures[ap]['solid_geometry']:
poly_buff.append(poly)
work_poly_buff = cascaded_union(poly_buff)
try:
poly_buff = work_poly_buff.buffer(0.0000001)
except ValueError:
pass
try:
poly_buff = poly_buff.buffer(-0.0000001)
except ValueError:
pass
grb_obj.solid_geometry = deepcopy(poly_buff)
with self.app.proc_container.new(_("Generating new object ...")):
ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Generating new object failed.'))
return
# Register recent file
self.app.file_opened.emit('gerber', outname)
# GUI feedback
self.app.inform.emit(_("[success] Created: %s") % outname)
# cleanup
self.new_apertures.clear()
self.new_solid_geometry[:] = []
self.sub_union[:] = []
def on_geo_intersection_click(self):
# reset previous values
self.new_tools.clear()
self.target_options.clear()
self.new_solid_geometry = []
self.sub_union = []
self.sub_type = "geo"
self.target_geo_obj_name = self.target_geo_combo.currentText()
if self.target_geo_obj_name == '':
self.app.inform.emit(_("[ERROR_NOTCL] No Target object loaded."))
return
# Get source object.
try:
self.target_geo_obj = self.app.collection.get_by_name(self.target_geo_obj_name)
except:
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.target_geo_obj_name)
return "Could not retrieve object: %s" % self.target_grb_obj_name
self.sub_geo_obj_name = self.sub_geo_combo.currentText()
if self.sub_geo_obj_name == '':
self.app.inform.emit(_("[ERROR_NOTCL] No Substractor object loaded."))
return
# Get source object.
try:
self.sub_geo_obj = self.app.collection.get_by_name(self.sub_geo_obj_name)
except:
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.sub_geo_obj_name)
return "Could not retrieve object: %s" % self.sub_geo_obj_name
if self.sub_geo_obj.multigeo:
self.app.inform.emit(_("[ERROR_NOTCL] Currently, the Substractor geometry cannot be of type Multigeo."))
return
# create the target_options obj
self.target_options = {}
for opt in self.target_geo_obj.options:
if opt != 'name':
self.target_options[opt] = deepcopy(self.target_geo_obj.options[opt])
# crate the new_tools dict structure
for tool in self.target_geo_obj.tools:
self.new_tools[tool] = {}
for key in self.target_geo_obj.tools[tool]:
if key == 'solid_geometry':
self.new_tools[tool][key] = []
else:
self.new_tools[tool][key] = deepcopy(self.target_geo_obj.tools[tool][key])
# add the promises
if self.target_geo_obj.multigeo:
for tool in self.target_geo_obj.tools:
self.promises.append(tool)
else:
self.promises.append("single")
self.sub_union = cascaded_union(self.sub_geo_obj.solid_geometry)
# start the QTimer to check for promises with 0.5 second period check
self.periodic_check(500, reset=True)
if self.target_geo_obj.multigeo:
for tool in self.target_geo_obj.tools:
geo = self.target_geo_obj.tools[tool]['solid_geometry']
self.app.worker_task.emit({'fcn': self.toolgeo_intersection,
'params': [tool, geo]})
else:
geo = self.target_geo_obj.solid_geometry
self.app.worker_task.emit({'fcn': self.toolgeo_intersection,
'params': ["single", geo]})
def toolgeo_intersection(self, tool, geo):
new_geometry = []
log.debug("Working on promise: %s" % str(tool))
if tool == "single":
text = _("Parsing solid_geometry ...")
else:
text = _("Parsing tool %s geometry ...") % str(tool)
with self.app.proc_container.new(text):
new_geo = (cascaded_union(geo)).difference(self.sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
if new_geometry:
if tool == "single":
while not self.new_solid_geometry:
self.new_solid_geometry = deepcopy(new_geometry)
time.sleep(0.5)
else:
while not self.new_tools[tool]['solid_geometry']:
self.new_tools[tool]['solid_geometry'] = deepcopy(new_geometry)
time.sleep(0.5)
while True:
# removal from list is done in a multithreaded way therefore not always the removal can be done
# so we keep trying until it's done
if tool not in self.promises:
break
self.promises.remove(tool)
time.sleep(0.5)
log.debug("Promise fulfilled: %s" % str(tool))
def new_geo_object(self, outname):
def obj_init(geo_obj, app_obj):
geo_obj.options = deepcopy(self.target_options)
geo_obj.options['name'] = outname
if self.target_geo_obj.multigeo:
geo_obj.tools = deepcopy(self.new_tools)
# this turn on the FlatCAMCNCJob plot for multiple tools
geo_obj.multigeo = True
geo_obj.multitool = True
else:
geo_obj.solid_geometry = deepcopy(self.new_solid_geometry)
try:
geo_obj.tools = deepcopy(self.new_tools)
for tool in geo_obj.tools:
geo_obj.tools[tool]['solid_geometry'] = deepcopy(self.new_solid_geometry)
except:
pass
with self.app.proc_container.new(_("Generating new object ...")):
ret = self.app.new_object('geometry', outname, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Generating new object failed.'))
return
# Register recent file
self.app.file_opened.emit('geometry', outname)
# GUI feedback
self.app.inform.emit(_("[success] Created: %s") % outname)
# cleanup
self.new_tools.clear()
self.new_solid_geometry[:] = []
self.sub_union[:] = []
def periodic_check(self, check_period, reset=False):
"""
This function starts an QTimer and it will periodically check if intersections are done
:param check_period: time at which to check periodically
:param reset: will reset the timer
:return:
"""
log.debug("ToolSub --> Periodic Check started.")
try:
self.check_thread.stop()
except Exception as e:
pass
if reset:
self.check_thread.setInterval(check_period)
try:
self.check_thread.timeout.disconnect(self.periodic_check_handler)
except Exception as e:
pass
self.check_thread.timeout.connect(self.periodic_check_handler)
self.check_thread.start(QtCore.QThread.HighPriority)
def periodic_check_handler(self):
"""
If the intersections workers finished then start creating the solid_geometry
:return:
"""
# log.debug("checking parsing --> %s" % str(self.parsing_promises))
try:
if not self.promises:
self.check_thread.stop()
if self.sub_type == "gerber":
outname = self.target_gerber_combo.currentText() + '_sub'
# intersection jobs finished, start the creation of solid_geometry
self.app.worker_task.emit({'fcn': self.new_gerber_object,
'params': [outname]})
else:
outname = self.target_geo_combo.currentText() + '_sub'
# intersection jobs finished, start the creation of solid_geometry
self.app.worker_task.emit({'fcn': self.new_geo_object,
'params': [outname]})
# reset the type of substraction for next time
self.sub_type = None
log.debug("ToolSub --> Periodic check finished.")
except Exception as e:
log.debug("ToolSub().periodic_check_handler() --> %s" % str(e))
traceback.print_exc()
def reset_fields(self):
self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))

View File

@ -16,5 +16,7 @@ from flatcamTools.ToolTransform import ToolTransform
from flatcamTools.ToolSolderPaste import SolderPaste
from flatcamTools.ToolPcbWizard import PcbWizard
from flatcamTools.ToolPDF import ToolPDF
from flatcamTools.ToolSub import ToolSub
from flatcamTools.ToolShell import FCShell

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

View File

@ -20,7 +20,6 @@ pip3 install --upgrade Shapely
pip3 install --upgrade vispy
pip3 install --upgrade rtree
pip3 install --upgrade pyopengl
pip3 install --upgrade pyopengl-accelerate
pip3 install --upgrade setuptools
pip3 install --upgrade svg.path
pip3 install --upgrade ortools

BIN
share/sub32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B