jpcgt/flatcam/Beta слито с Beta

This commit is contained in:
Camellan 2019-12-24 20:26:34 +04:00
commit 5784e4659e
28 changed files with 871 additions and 155 deletions

View File

@ -56,7 +56,7 @@ from flatcamGUI.PlotCanvas import *
from flatcamGUI.PlotCanvasLegacy import * from flatcamGUI.PlotCanvasLegacy import *
from flatcamGUI.FlatCAMGUI import * from flatcamGUI.FlatCAMGUI import *
from FlatCAMCommon import LoudDict, BookmarkManager, ToolsDB from FlatCAMCommon import LoudDict, BookmarkManager, ToolsDB, color_variant
from FlatCAMPostProc import load_preprocessors from FlatCAMPostProc import load_preprocessors
from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor
@ -830,6 +830,8 @@ class App(QtCore.QObject):
"tools_transform_offset_y": 0.0, "tools_transform_offset_y": 0.0,
"tools_transform_mirror_reference": False, "tools_transform_mirror_reference": False,
"tools_transform_mirror_point": (0, 0), "tools_transform_mirror_point": (0, 0),
"tools_transform_buffer_dis": 0.0,
"tools_transform_buffer_corner": True,
# SolderPaste Tool # SolderPaste Tool
"tools_solderpaste_tools": "1.0, 0.3", "tools_solderpaste_tools": "1.0, 0.3",
@ -1038,6 +1040,7 @@ class App(QtCore.QObject):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.ui = FlatCAMGUI(self) self.ui = FlatCAMGUI(self)
self.on_grid_snap_triggered(state=True)
theme_settings = QtCore.QSettings("Open Source", "FlatCAM") theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
if theme_settings.contains("theme"): if theme_settings.contains("theme"):
@ -1431,6 +1434,8 @@ class App(QtCore.QObject):
"tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry, "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry,
"tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb, "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb,
"tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry, "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry,
"tools_transform_buffer_dis": self.ui.tools_defaults_form.tools_transform_group.buffer_entry,
"tools_transform_buffer_corner": self.ui.tools_defaults_form.tools_transform_group.buffer_rounded_cb,
# SolderPaste Dispensing Tool # SolderPaste Dispensing Tool
"tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry, "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,
@ -1939,6 +1944,10 @@ class App(QtCore.QObject):
self.ui.popmenu_properties.triggered.connect(self.obj_properties) self.ui.popmenu_properties.triggered.connect(self.obj_properties)
# Project Context Menu -> Color Setting
for act in self.ui.menuprojectcolor.actions():
act.triggered.connect(self.on_set_color_action_triggered)
# Preferences Plot Area TAB # Preferences Plot Area TAB
self.ui.pref_save_button.clicked.connect(lambda: self.on_save_button(save_to_file=True)) self.ui.pref_save_button.clicked.connect(lambda: self.on_save_button(save_to_file=True))
self.ui.pref_apply_button.clicked.connect(lambda: self.on_save_button(save_to_file=False)) self.ui.pref_apply_button.clicked.connect(lambda: self.on_save_button(save_to_file=False))
@ -2023,7 +2032,7 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect( self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect(
self.on_proj_color_dis_button) self.on_proj_color_dis_button)
# ############################# workspace setting signals ##################### # ############################# Workspace Setting Signals #####################
self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified) self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
self.ui.general_defaults_form.general_gui_group.wk_orientation_radio.activated_custom.connect( self.ui.general_defaults_form.general_gui_group.wk_orientation_radio.activated_custom.connect(
self.on_workspace_modified self.on_workspace_modified
@ -2153,6 +2162,8 @@ class App(QtCore.QObject):
# signal emitted when a tab is closed in the Plot Area # signal emitted when a tab is closed in the Plot Area
self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed) self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed)
self.ui.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
# ##################################################################################### # #####################################################################################
# ########### FINISHED CONNECTING SIGNALS ############################################# # ########### FINISHED CONNECTING SIGNALS #############################################
# ##################################################################################### # #####################################################################################
@ -4728,7 +4739,7 @@ class App(QtCore.QObject):
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 3) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 3, 0) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 3, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 3, 1) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 3, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, @detlefeckardt"), 3, 2) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, Detlef Eckardt"), 3, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 3) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 4, 0) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 4, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 1) self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 1)
@ -6079,6 +6090,7 @@ class App(QtCore.QObject):
self.report_usage("on_toggle_grid()") self.report_usage("on_toggle_grid()")
self.ui.grid_snap_btn.trigger() self.ui.grid_snap_btn.trigger()
self.on_grid_snap_triggered(state=True)
def on_toggle_grid_lines(self): def on_toggle_grid_lines(self):
self.report_usage("on_toggle_grd_lines()") self.report_usage("on_toggle_grd_lines()")
@ -7019,6 +7031,8 @@ class App(QtCore.QObject):
self.connect_toolbar_signals() self.connect_toolbar_signals()
self.ui.grid_snap_btn.setChecked(True) self.ui.grid_snap_btn.setChecked(True)
self.on_grid_snap_triggered(state=True)
self.ui.grid_gap_x_entry.setText(str(self.defaults["global_gridx"])) self.ui.grid_gap_x_entry.setText(str(self.defaults["global_gridx"]))
self.ui.grid_gap_y_entry.setText(str(self.defaults["global_gridy"])) self.ui.grid_gap_y_entry.setText(str(self.defaults["global_gridy"]))
self.ui.snap_max_dist_entry.setText(str(self.defaults["global_snap_max"])) self.ui.snap_max_dist_entry.setText(str(self.defaults["global_snap_max"]))
@ -8437,16 +8451,16 @@ class App(QtCore.QObject):
def populate_cmenu_grids(self): def populate_cmenu_grids(self):
units = self.defaults['units'].lower() units = self.defaults['units'].lower()
for act in self.ui.cmenu_gridmenu.actions():
act.triggered.disconnect()
self.ui.cmenu_gridmenu.clear() self.ui.cmenu_gridmenu.clear()
sorted_list = sorted(self.defaults["global_grid_context_menu"][str(units)]) sorted_list = sorted(self.defaults["global_grid_context_menu"][str(units)])
grid_toggle = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon(self.resource_location + '/grid32_menu.png'), grid_toggle = self.ui.cmenu_gridmenu.addAction(QtGui.QIcon(self.resource_location + '/grid32_menu.png'),
_("Grid On/Off")) _("Grid On/Off"))
grid_toggle.setCheckable(True) grid_toggle.setCheckable(True)
if self.grid_status() == True: grid_toggle.setChecked(True) if self.grid_status() else grid_toggle.setChecked(False)
grid_toggle.setChecked(True)
else:
grid_toggle.setChecked(False)
self.ui.cmenu_gridmenu.addSeparator() self.ui.cmenu_gridmenu.addSeparator()
for grid in sorted_list: for grid in sorted_list:
@ -8842,12 +8856,14 @@ class App(QtCore.QObject):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj) self.draw_selection_shape(obj)
obj.selection_shape_drawn = True
self.collection.set_active(obj.options['name']) self.collection.set_active(obj.options['name'])
else: else:
if poly_selection.intersects(poly_obj): if poly_selection.intersects(poly_obj):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(obj) self.draw_selection_shape(obj)
obj.selection_shape_drawn = True
self.collection.set_active(obj.options['name']) self.collection.set_active(obj.options['name'])
except Exception as e: except Exception as e:
# the Exception here will happen if we try to select on screen and we have an newly (and empty) # the Exception here will happen if we try to select on screen and we have an newly (and empty)
@ -8894,20 +8910,26 @@ class App(QtCore.QObject):
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
elif self.collection.get_active().options['name'] not in objects_under_the_click_list: elif curr_sel_obj.options['name'] not in objects_under_the_click_list:
self.on_objects_selection(False) self.on_objects_selection(False)
self.delete_selection_shape() self.delete_selection_shape()
curr_sel_obj.selection_shape_drawn = False
self.collection.set_active(objects_under_the_click_list[0]) self.collection.set_active(objects_under_the_click_list[0])
curr_sel_obj = self.collection.get_active() curr_sel_obj = self.collection.get_active()
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
self.selected_message(curr_sel_obj=curr_sel_obj) self.selected_message(curr_sel_obj=curr_sel_obj)
elif curr_sel_obj.selection_shape_drawn is False:
if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
else: else:
self.on_objects_selection(False) self.on_objects_selection(False)
self.delete_selection_shape() self.delete_selection_shape()
@ -8922,6 +8944,7 @@ class App(QtCore.QObject):
# make active the first element of the overlapped objects list # make active the first element of the overlapped objects list
if self.collection.get_active() is None: if self.collection.get_active() is None:
self.collection.set_active(objects_under_the_click_list[0]) self.collection.set_active(objects_under_the_click_list[0])
objects_under_the_click_list[0].selection_shape_drawn = True
name_sel_obj = self.collection.get_active().options['name'] name_sel_obj = self.collection.get_active().options['name']
# In case that there is a selected object but it is not in the overlapped object list # In case that there is a selected object but it is not in the overlapped object list
@ -8939,9 +8962,12 @@ class App(QtCore.QObject):
curr_sel_obj = self.collection.get_active() curr_sel_obj = self.collection.get_active()
# delete the possible selection box around a possible selected object # delete the possible selection box around a possible selected object
self.delete_selection_shape() self.delete_selection_shape()
curr_sel_obj.selection_shape_drawn = False
# create the selection box around the selected object # create the selection box around the selected object
if self.defaults['global_selection_shape'] is True: if self.defaults['global_selection_shape'] is True:
self.draw_selection_shape(curr_sel_obj) self.draw_selection_shape(curr_sel_obj)
curr_sel_obj.selection_shape_drawn = True
self.selected_message(curr_sel_obj=curr_sel_obj) self.selected_message(curr_sel_obj=curr_sel_obj)
@ -8951,6 +8977,9 @@ class App(QtCore.QObject):
# delete the possible selection box around a possible selected object # delete the possible selection box around a possible selected object
self.delete_selection_shape() self.delete_selection_shape()
for o in self.collection.get_list():
o.selection_shape_drawn = False
# and as a convenience move the focus to the Project tab because Selected tab is now empty but # and as a convenience move the focus to the Project tab because Selected tab is now empty but
# only when working on App # only when working on App
if self.call_source == 'app': if self.call_source == 'app':
@ -10442,7 +10471,8 @@ class App(QtCore.QObject):
mirror=None) mirror=None)
if obj.kind.lower() == 'gerber': if obj.kind.lower() == 'gerber':
color = self.defaults["global_plot_fill"][:-2] # color = self.defaults["global_plot_fill"][:-2]
color = obj.fill_color[:-2]
elif obj.kind.lower() == 'excellon': elif obj.kind.lower() == 'excellon':
color = '#C40000' color = '#C40000'
elif obj.kind.lower() == 'geometry': elif obj.kind.lower() == 'geometry':
@ -11501,26 +11531,28 @@ class App(QtCore.QObject):
App.log.debug(" **************** Started PROEJCT loading... **************** ") App.log.debug(" **************** Started PROEJCT loading... **************** ")
for obj in d['objs']: for obj in d['objs']:
def obj_init(obj_inst, app_inst): try:
def obj_init(obj_inst, app_inst):
obj_inst.from_dict(obj) obj_inst.from_dict(obj)
App.log.debug("Recreating from opened project an %s object: %s" % App.log.debug("Recreating from opened project an %s object: %s" %
(obj['kind'].capitalize(), obj['options']['name'])) (obj['kind'].capitalize(), obj['options']['name']))
# for some reason, setting ui_title does not work when this method is called from Tcl Shell # for some reason, setting ui_title does not work when this method is called from Tcl Shell
# it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled)
if cli is None: if cli is None:
self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"), self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"),
obj['kind'].upper(), obj['kind'].upper(),
obj['options']['name'] obj['options']['name']
) )
) )
self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=plot) self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=plot)
except Exception as e:
print('App.open_project() --> ' + str(e))
self.inform.emit('[success] %s: %s' % self.inform.emit('[success] %s: %s' % (_("Project loaded from"), filename))
(_("Project loaded from"), filename))
self.should_we_save = False self.should_we_save = False
self.file_opened.emit("project", filename) self.file_opened.emit("project", filename)
@ -12350,6 +12382,68 @@ class App(QtCore.QObject):
# Clear pool to free memory # Clear pool to free memory
self.clear_pool() self.clear_pool()
def on_set_color_action_triggered(self):
new_color = self.defaults['global_plot_fill']
act_name = self.sender().text().lower()
sel_obj_list = self.collection.get_selected()
if not sel_obj_list:
return
if act_name == 'red':
new_color = '#FF0000' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'blue':
new_color = '#0000FF' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'yellow':
new_color = '#FFDF00' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'green':
new_color = '#00FF00' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'purple':
new_color = '#FF00FF' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'brown':
new_color = '#A52A2A' + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
if act_name == 'custom':
new_color = QtGui.QColor(self.defaults['global_plot_fill'][:7])
c_dialog = QtWidgets.QColorDialog()
plot_fill_color = c_dialog.getColor(initial=new_color)
if plot_fill_color.isValid() is False:
return
new_color = str(plot_fill_color.name()) + \
str(hex(self.ui.general_defaults_form.general_gui_group.pf_color_alpha_slider.value())[2:])
new_line_color = color_variant(new_color[:7], 0.7)
for sel_obj in sel_obj_list:
if self.is_legacy is False:
sel_obj.fill_color = new_color
sel_obj.outline_color = new_line_color
sel_obj.shapes.redraw(
update_colors=(new_color, new_line_color)
)
else:
sel_obj.fill_color = new_color
sel_obj.outline_color = new_line_color
sel_obj.shapes.redraw(
update_colors=(new_color, new_line_color)
)
def on_grid_snap_triggered(self, state):
if state:
self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_filled_16.png'))
else:
self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_16.png'))
def generate_cnc_job(self, objects): def generate_cnc_job(self, objects):
self.report_usage("generate_cnc_job()") self.report_usage("generate_cnc_job()")

View File

@ -474,7 +474,7 @@ class ToolsDB(QtWidgets.QWidget):
self.on_tool_request = callback_on_tool_request self.on_tool_request = callback_on_tool_request
self.offset_item_options = ["Path", "In", "Out", "Custom"] self.offset_item_options = ["Path", "In", "Out", "Custom"]
self.type_item_options = [_("Iso"), _("Rough"), _("Finish")] self.type_item_options = ["Iso", "Rough", "Finish"]
self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
''' '''
@ -760,13 +760,16 @@ class ToolsDB(QtWidgets.QWidget):
self.table_widget.setRowCount(len(self.db_tool_dict)) self.table_widget.setRowCount(len(self.db_tool_dict))
nr_crt = 0 nr_crt = 0
for toolid, dict_val in self.db_tool_dict.items(): for toolid, dict_val in self.db_tool_dict.items():
row = nr_crt row = nr_crt
nr_crt += 1 nr_crt += 1
t_name = dict_val['name'] t_name = dict_val['name']
self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val) try:
self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val)
except Exception as e:
self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e))
vertical_header = self.table_widget.verticalHeader() vertical_header = self.table_widget.verticalHeader()
vertical_header.hide() vertical_header.hide()
@ -920,7 +923,7 @@ class ToolsDB(QtWidgets.QWidget):
dwelltime_item = FCDoubleSpinner() dwelltime_item = FCDoubleSpinner()
dwelltime_item.set_precision(self.decimals) dwelltime_item.set_precision(self.decimals)
dwelltime_item.set_range(0.0, 9999.9999) dwelltime_item.set_range(0.0000, 9999.9999)
dwelltime_item.set_value(float(data['dwelltime'])) dwelltime_item.set_value(float(data['dwelltime']))
widget.setCellWidget(row, 18, dwelltime_item) widget.setCellWidget(row, 18, dwelltime_item)
@ -936,7 +939,7 @@ class ToolsDB(QtWidgets.QWidget):
ecut_length_item = FCDoubleSpinner() ecut_length_item = FCDoubleSpinner()
ecut_length_item.set_precision(self.decimals) ecut_length_item.set_precision(self.decimals)
ecut_length_item.set_range(0.0, 9999.9999) ecut_length_item.set_range(0.0000, 9999.9999)
ecut_length_item.set_value(data['extracut_length']) ecut_length_item.set_value(data['extracut_length'])
widget.setCellWidget(row, 21, ecut_length_item) widget.setCellWidget(row, 21, ecut_length_item)
@ -977,11 +980,8 @@ class ToolsDB(QtWidgets.QWidget):
Add a tool in the DB Tool Table Add a tool in the DB Tool Table
:return: None :return: None
""" """
new_toolid = len(self.db_tool_dict) + 1
dict_elem = dict()
default_data = dict() default_data = dict()
default_data.update({ default_data.update({
"cutz": float(self.app.defaults["geometry_cutz"]), "cutz": float(self.app.defaults["geometry_cutz"]),
"multidepth": self.app.defaults["geometry_multidepth"], "multidepth": self.app.defaults["geometry_multidepth"],
@ -997,7 +997,7 @@ class ToolsDB(QtWidgets.QWidget):
"dwelltime": float(self.app.defaults["geometry_dwelltime"]), "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"], "extracut_length": float(self.app.defaults["geometry_extracut_length"]),
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"], "toolchangexy": self.app.defaults["geometry_toolchangexy"],
"toolchangez": float(self.app.defaults["geometry_toolchangez"]), "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
@ -1005,20 +1005,17 @@ class ToolsDB(QtWidgets.QWidget):
"endz": float(self.app.defaults["geometry_endz"]) "endz": float(self.app.defaults["geometry_endz"])
}) })
dict_elem = dict()
dict_elem['name'] = 'new_tool' dict_elem['name'] = 'new_tool'
dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"] dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
dict_elem['offset'] = 'Path' dict_elem['offset'] = 'Path'
dict_elem['offset_value'] = 0.0 dict_elem['offset_value'] = 0.0
dict_elem['type'] = _('Rough') dict_elem['type'] = 'Rough'
dict_elem['tool_type'] = 'C1' dict_elem['tool_type'] = 'C1'
dict_elem['data'] = default_data dict_elem['data'] = default_data
self.db_tool_dict.update( new_toolid = len(self.db_tool_dict) + 1
{ self.db_tool_dict[new_toolid] = deepcopy(dict_elem)
new_toolid: deepcopy(dict_elem)
}
)
# add the new entry to the Tools DB table # add the new entry to the Tools DB table
self.build_db_ui() self.build_db_ui()
@ -1253,59 +1250,59 @@ class ToolsDB(QtWidgets.QWidget):
new_toolid = row + 1 new_toolid = row + 1
for col in range(self.table_widget.columnCount()): for col in range(self.table_widget.columnCount()):
column_header_text = self.table_widget.horizontalHeaderItem(col).text() column_header_text = self.table_widget.horizontalHeaderItem(col).text()
if column_header_text == 'Tool Name': if column_header_text == _('Tool Name'):
dict_elem['name'] = self.table_widget.item(row, col).text() dict_elem['name'] = self.table_widget.item(row, col).text()
elif column_header_text == 'Tool Dia': elif column_header_text == _('Tool Dia'):
dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value() dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Tool Offset': elif column_header_text == _('Tool Offset'):
dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value() dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Custom Offset': elif column_header_text == _('Custom Offset'):
dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value() dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Tool Type': elif column_header_text == _('Tool Type'):
dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value() dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Tool Shape': elif column_header_text == _('Tool Shape'):
dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value() dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value()
else: else:
if column_header_text == 'Cut Z': if column_header_text == _('Cut Z'):
default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value() default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'MultiDepth': elif column_header_text == _('MultiDepth'):
default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value() default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'DPP': elif column_header_text == _('DPP'):
default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value() default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'V-Dia': elif column_header_text == _('V-Dia'):
default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value() default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'V-Angle': elif column_header_text == _('V-Angle'):
default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value() default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Travel Z': elif column_header_text == _('Travel Z'):
default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value() default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'FR': elif column_header_text == _('FR'):
default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value() default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'FR Z': elif column_header_text == _('FR Z'):
default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value() default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'FR Rapids': elif column_header_text == _('FR Rapids'):
default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value() default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Spindle Speed': elif column_header_text == _('Spindle Speed'):
default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value() default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Dwell': elif column_header_text == _('Dwell'):
default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value() default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Dwelltime': elif column_header_text == _('Dwelltime'):
default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value() default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Preprocessor': elif column_header_text == _('Preprocessor'):
default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value() default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'ExtraCut': elif column_header_text == _('ExtraCut'):
default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value() default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == "E-Cut Length": elif column_header_text == _("E-Cut Length"):
default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value() default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Toolchange': elif column_header_text == _('Toolchange'):
default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value() default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Toolchange XY': elif column_header_text == _('Toolchange XY'):
default_data['toolchangexy'] = self.table_widget.item(row, col).text() default_data['toolchangexy'] = self.table_widget.item(row, col).text()
elif column_header_text == 'Toolchange Z': elif column_header_text == _('Toolchange Z'):
default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value() default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Start Z': elif column_header_text == _('Start Z'):
default_data['startz'] = float(self.table_widget.item(row, col).text()) \ default_data['startz'] = float(self.table_widget.item(row, col).text()) \
if self.table_widget.item(row, col).text() is not '' else None if self.table_widget.item(row, col).text() is not '' else None
elif column_header_text == 'End Z': elif column_header_text == _('End Z'):
default_data['endz'] = self.table_widget.cellWidget(row, col).get_value() default_data['endz'] = self.table_widget.cellWidget(row, col).get_value()
dict_elem['data'] = default_data dict_elem['data'] = default_data
@ -1355,3 +1352,33 @@ class ToolsDB(QtWidgets.QWidget):
def closeEvent(self, QCloseEvent): def closeEvent(self, QCloseEvent):
super().closeEvent(QCloseEvent) super().closeEvent(QCloseEvent)
def color_variant(hex_color, bright_factor=1):
"""
Takes a color in HEX format #FF00FF and produces a lighter or darker variant
:param hex_color: color to change
:param bright_factor: factor to change the color brightness [0 ... 1]
:return: modified color
"""
if len(hex_color) != 7:
print("Color is %s, but needs to be in #FF00FF format. Returning original color." % hex_color)
return hex_color
if bright_factor > 1.0:
bright_factor = 1.0
if bright_factor < 0.0:
bright_factor = 0.0
rgb_hex = [hex_color[x:x + 2] for x in [1, 3, 5]]
new_rgb = list()
for hex_value in rgb_hex:
# adjust each color channel and turn it into a INT suitable as argument for hex()
mod_color = round(int(hex_value, 16) * bright_factor)
# make sure that each color channel has two digits without the 0x prefix
mod_color_hex = str(hex(mod_color)[2:]).zfill(2)
new_rgb.append(mod_color_hex)
return "#" + "".join([i for i in new_rgb])

View File

@ -15,7 +15,9 @@ from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, Line
from shapely.ops import cascaded_union from shapely.ops import cascaded_union
import shapely.affinity as affinity import shapely.affinity as affinity
from copy import deepcopy, copy from copy import deepcopy
from copy import copy
from io import StringIO from io import StringIO
import traceback import traceback
import inspect # TODO: For debugging only. import inspect # TODO: For debugging only.
@ -30,6 +32,8 @@ from flatcamParsers.ParseGerber import Gerber
from camlib import Geometry, CNCjob from camlib import Geometry, CNCjob
import FlatCAMApp import FlatCAMApp
from flatcamGUI.VisPyVisuals import ShapeCollection
import tkinter as tk import tkinter as tk
import os, sys, itertools import os, sys, itertools
import ezdxf import ezdxf
@ -104,6 +108,7 @@ class FlatCAMObj(QtCore.QObject):
if self.app.is_legacy is False: if self.app.is_legacy is False:
self.shapes = self.app.plotcanvas.new_shape_group() self.shapes = self.app.plotcanvas.new_shape_group()
# self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2)
else: else:
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name) self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name)
@ -123,6 +128,9 @@ class FlatCAMObj(QtCore.QObject):
self.isHovering = False self.isHovering = False
self.notHovering = True self.notHovering = True
# Flag to show if a selection shape is drawn
self.selection_shape_drawn = False
# self.units = 'IN' # self.units = 'IN'
self.units = self.app.defaults['units'] self.units = self.app.defaults['units']
@ -591,7 +599,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"])) self.circle_steps = int(self.app.defaults["gerber_circle_steps"])
Gerber.__init__(self, steps_per_circle=self.circle_steps)
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
self.kind = "gerber" self.kind = "gerber"
@ -649,10 +659,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.units_found = self.app.defaults['units'] self.units_found = self.app.defaults['units']
self.fill_color = self.app.defaults['global_plot_fill']
self.outline_color = self.app.defaults['global_plot_line']
# Attributes to be included in serialization # Attributes to be included in serialization
# Always append to it because it carries contents # Always append to it because it carries contents
# from predecessors. # from predecessors.
self.ser_attrs += ['options', 'kind'] self.ser_attrs += ['options', 'kind', 'fill_color', 'outline_color']
def set_ui(self, ui): def set_ui(self, ui):
""" """
@ -736,6 +749,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.aperture_table_visibility_cb.hide() self.ui.aperture_table_visibility_cb.hide()
self.ui.milling_type_label.hide() self.ui.milling_type_label.hide()
self.ui.milling_type_radio.hide() self.ui.milling_type_radio.hide()
self.ui.iso_type_label.hide()
self.ui.iso_type_radio.hide() self.ui.iso_type_radio.hide()
self.ui.follow_cb.hide() self.ui.follow_cb.hide()
@ -1665,12 +1679,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if 'color' in kwargs: if 'color' in kwargs:
color = kwargs['color'] color = kwargs['color']
else: else:
color = self.app.defaults['global_plot_line'] color = self.outline_color
if 'face_color' in kwargs: if 'face_color' in kwargs:
face_color = kwargs['face_color'] face_color = kwargs['face_color']
else: else:
face_color = self.app.defaults['global_plot_fill'] face_color = self.fill_color
if 'visible' not in kwargs: if 'visible' not in kwargs:
visible = self.options['plot'] visible = self.options['plot']
@ -1740,7 +1754,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
for el in g: for el in g:
self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black', self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black',
visible=visible) visible=visible)
self.shapes.redraw() self.shapes.redraw(
# update_colors=(self.fill_color, self.outline_color),
# indexes=self.app.plotcanvas.shape_collection.data.keys()
)
except (ObjectDeleted, AttributeError): except (ObjectDeleted, AttributeError):
self.shapes.clear(update=True) self.shapes.clear(update=True)
except Exception as e: except Exception as e:
@ -2184,6 +2201,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point) Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
self.replotApertures.emit() self.replotApertures.emit()
def buffer(self, distance, join):
Gerber.buffer(self, distance=distance, join=join)
self.replotApertures.emit()
def serialize(self): def serialize(self):
return { return {
"options": self.options, "options": self.options,
@ -2202,7 +2223,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
Excellon.__init__(self, geo_steps_per_circle=self.circle_steps)
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
self.kind = "excellon" self.kind = "excellon"
@ -3530,8 +3553,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def __init__(self, name): def __init__(self, name):
self.decimals = self.app.decimals self.decimals = self.app.decimals
self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
FlatCAMObj.__init__(self, name) FlatCAMObj.__init__(self, name)
Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) Geometry.__init__(self, geo_steps_per_circle=self.circle_steps)
self.kind = "geometry" self.kind = "geometry"
@ -3612,6 +3638,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# this variable can be updated by the Object that generates the geometry # this variable can be updated by the Object that generates the geometry
self.tool_type = 'C1' self.tool_type = 'C1'
# save here the old value for the Cut Z before it is changed by selecting a V-shape type tool in the tool table
self.old_cutz = self.app.defaults["geometry_cutz"]
# Attributes to be included in serialization # Attributes to be included in serialization
# Always append to it because it carries contents # Always append to it because it carries contents
# from predecessors. # from predecessors.
@ -3760,6 +3789,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.name_entry.set_value(self.options['name']) self.ui.name_entry.set_value(self.options['name'])
self.ui_connect() self.ui_connect()
self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \
self.ui.e_cut_entry.setDisabled(True)
def set_ui(self, ui): def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui) FlatCAMObj.set_ui(self, ui)
@ -3847,15 +3879,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if def_key == opt_key: if def_key == opt_key:
self.default_data[def_key] = deepcopy(opt_val) self.default_data[def_key] = deepcopy(opt_val)
try: if type(self.options["cnctooldia"]) == float:
temp_tools = self.options["cnctooldia"].split(",") tools_list = [self.options["cnctooldia"]]
tools_list = [ else:
float(eval(dia)) for dia in temp_tools if dia != '' try:
] temp_tools = self.options["cnctooldia"].split(",")
except Exception as e: tools_list = [
log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Geometry General -> " float(eval(dia)) for dia in temp_tools if dia != ''
"Tool dia. %s" % str(e)) ]
return except Exception as e:
log.error("FlatCAMGeometry.set_ui() -> At least one tool diameter needed. "
"Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e))
return
self.tooluid += 1 self.tooluid += 1
@ -3936,7 +3971,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else: else:
self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced')) self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
self.ui.e_cut_entry.setDisabled(True) self.ui.e_cut_entry.setDisabled(False) if self.app.defaults['geometry_extracut'] else \
self.ui.e_cut_entry.setDisabled(True)
self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click) self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
@ -3949,6 +3986,10 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked) self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed)
def on_cut_z_changed(self):
self.old_cutz = self.ui.cutz_entry.get_value()
def set_tool_offset_visibility(self, current_row): def set_tool_offset_visibility(self, current_row):
if current_row is None: if current_row is None:
@ -4589,6 +4630,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if cb_txt == 'V': if cb_txt == 'V':
idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso')) idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso'))
self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx) self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
else:
self.ui.cutz_entry.set_value(self.old_cutz)
self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText()) self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText())
def update_form(self, dict_storage): def update_form(self, dict_storage):

View File

@ -321,6 +321,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
sel = len(self.view.selectedIndexes()) > 0 sel = len(self.view.selectedIndexes()) > 0
self.app.ui.menuprojectenable.setEnabled(sel) self.app.ui.menuprojectenable.setEnabled(sel)
self.app.ui.menuprojectdisable.setEnabled(sel) self.app.ui.menuprojectdisable.setEnabled(sel)
self.app.ui.menuprojectcolor.setEnabled(sel)
self.app.ui.menuprojectviewsource.setEnabled(sel) self.app.ui.menuprojectviewsource.setEnabled(sel)
self.app.ui.menuprojectcopy.setEnabled(sel) self.app.ui.menuprojectcopy.setEnabled(sel)
@ -334,8 +335,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.app.ui.menuprojectedit.setVisible(True) self.app.ui.menuprojectedit.setVisible(True)
self.app.ui.menuprojectsave.setVisible(True) self.app.ui.menuprojectsave.setVisible(True)
self.app.ui.menuprojectviewsource.setVisible(True) self.app.ui.menuprojectviewsource.setVisible(True)
self.app.ui.menuprojectcolor.setEnabled(False)
for obj in self.get_selected(): for obj in self.get_selected():
if type(obj) == FlatCAMGerber:
self.app.ui.menuprojectcolor.setEnabled(True)
if type(obj) != FlatCAMGeometry: if type(obj) != FlatCAMGeometry:
self.app.ui.menuprojectgeneratecnc.setVisible(False) self.app.ui.menuprojectgeneratecnc.setVisible(False)
if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMGerber: if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMGerber:

View File

@ -9,10 +9,39 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
23.12.2019
- some fixes in the Legacy(2D) graphic mode regarding the possibility of changing the color of the Gerber objects
- added a method to darken the outline color for Gerber objects when they have the color set
- when Printing as PDF Gerber objects now the rendered color is the print color
- speed up the plotting in OpenGL(3D) graphic mode
- spped up the color setting for Gerber object when using the OpenGL(3D) graphic mode
- setting color for Gerber objects work on a selection of Gerber objects
- ~~when the selection is changed in the Project Tree the selection shape on canvas is deleted~~
- if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape
- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters
- solved issue #355 - when the tool diameter field in the Edit → Preferences → Geometry → Geometry General → Tools → Tool dia is only one the app failed to read it
- solved issue #356 - in Tools DB can not be added more than one tool if a translation is active
22.12.2019
- added a new option for the Gerber objects: on the project context menu now can be chosen a color for the selected Gerber object
- fixed issue in Gerber UI where a label was not hidden when in Basic mode
- added the color parameters of the objects to the serializable attributes
- fixed Gerber object color set for Legacy(2D) graphic engine; glitch on the OpenGL(3D) graphic engine
- fixed the above mentioned glitch in the OpenGL(3D) graphic engine when an Gerber object has been set with a color
21.12.2019
- fixed a typo in Distance Tool
20.12.2019 20.12.2019
- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab) - fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab)
- Print function is now printing a PDF file for a selection of objects in the colors from canvas - Print function is now printing a PDF file for a selection of objects in the colors from canvas
- added an icon in the infobar that will show more clearly the status of the grid snapping
- in Geometry Object UI (selected tab) when a tool type is changed from no matter what to V-shape, the cut_z value is saved and when the tool type is changed back to something different than V-shape, this saved cut-z value is restored
- fixed re-cut length entry not staying disabled when the re-cut cb is not checked
19.12.2019 19.12.2019

View File

@ -2118,6 +2118,69 @@ class Geometry(object):
# self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y, # self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
# origin=(px, py)) # origin=(px, py))
def buffer(self, distance, join):
"""
:param distance:
:param join:
:return:
"""
log.debug("camlib.Geometry.buffer()")
if distance == 0:
return
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
self.el_count += 1
disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
if self.old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
self.old_disp_number = disp_number
return obj.buffer(distance, resolution=self.geo_steps_per_circle, join_style=join)
except AttributeError:
return obj
try:
if self.multigeo is True:
for tool in self.tools:
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.tools[tool]['solid_geometry']:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
self.tools[tool]['solid_geometry'] = buffer_geom(self.tools[tool]['solid_geometry'])
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.solid_geometry:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
self.solid_geometry = buffer_geom(self.solid_geometry)
self.app.inform.emit('[success] %s...' % _('Object was buffered'))
except AttributeError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to buffer. No object selected"))
self.app.proc_container.new_text = ''
class AttrDict(dict): class AttrDict(dict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -2744,6 +2744,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# start with GRID toolbar activated # start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False: if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger() self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
self.app.ui.popmenu_disable.setVisible(False) self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False) self.app.ui.cmenu_newmenu.menuAction().setVisible(False)

View File

@ -3679,6 +3679,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# start with GRID toolbar activated # start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False: if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger() self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
def on_buffer_tool(self): def on_buffer_tool(self):
buff_tool = BufferSelectionTool(self.app, self) buff_tool = BufferSelectionTool(self.app, self)

View File

@ -3530,6 +3530,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# start with GRID toolbar activated # start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False: if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger() self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
# adjust the visibility of some of the canvas context menu # adjust the visibility of some of the canvas context menu
self.app.ui.popmenu_edit.setVisible(False) self.app.ui.popmenu_edit.setVisible(False)

View File

@ -634,11 +634,39 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# ########################## Project Tab Context Menu # ################## # ########################## Project Tab Context Menu # ##################
# ######################################################################## # ########################################################################
self.menuproject = QtWidgets.QMenu() self.menuproject = QtWidgets.QMenu()
self.menuprojectenable = self.menuproject.addAction( self.menuprojectenable = self.menuproject.addAction(
QtGui.QIcon(self.app.resource_location + '/replot32.png'), _('Enable Plot')) QtGui.QIcon(self.app.resource_location + '/replot32.png'), _('Enable Plot'))
self.menuprojectdisable = self.menuproject.addAction( self.menuprojectdisable = self.menuproject.addAction(
QtGui.QIcon(self.app.resource_location + '/clear_plot32.png'), _('Disable Plot')) QtGui.QIcon(self.app.resource_location + '/clear_plot32.png'), _('Disable Plot'))
self.menuproject.addSeparator() self.menuproject.addSeparator()
self.menuprojectcolor = self.menuproject.addMenu(
QtGui.QIcon(self.app.resource_location + '/set_color32.png'), _('Set Color'))
self.menuproject_red = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/red32.png'), _('Red'))
self.menuproject_blue = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/blue32.png'), _('Blue'))
self.menuproject_yellow = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/yellow32.png'), _('Yellow'))
self.menuproject_green = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/green32.png'), _('Green'))
self.menuproject_purple = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/violet32.png'), _('Purple'))
self.menuproject_brown = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/brown32.png'), _('Brown'))
self.menuproject_custom = self.menuprojectcolor.addAction(
QtGui.QIcon(self.app.resource_location + '/set_color32.png'), _('Custom'))
self.menuproject.addSeparator()
self.menuprojectgeneratecnc = self.menuproject.addAction( self.menuprojectgeneratecnc = self.menuproject.addAction(
QtGui.QIcon(self.app.resource_location + '/cnc32.png'), _('Generate CNC')) QtGui.QIcon(self.app.resource_location + '/cnc32.png'), _('Generate CNC'))
self.menuprojectviewsource = self.menuproject.addAction( self.menuprojectviewsource = self.menuproject.addAction(
@ -2149,6 +2177,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.fcinfo = FlatCAMInfoBar(app=self.app) self.fcinfo = FlatCAMInfoBar(app=self.app)
self.infobar.addWidget(self.fcinfo, stretch=1) self.infobar.addWidget(self.fcinfo, stretch=1)
self.snap_infobar_label = QtWidgets.QLabel()
self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_16.png'))
self.infobar.addWidget(self.snap_infobar_label)
self.rel_position_label = QtWidgets.QLabel( self.rel_position_label = QtWidgets.QLabel(
"<b>Dx</b>: 0.0000&nbsp;&nbsp; <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;") "<b>Dx</b>: 0.0000&nbsp;&nbsp; <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;")
self.rel_position_label.setMinimumWidth(110) self.rel_position_label.setMinimumWidth(110)

View File

@ -1577,7 +1577,7 @@ class GeometryObjectUI(ObjectUI):
self.cncfeedrate_rapid_entry.hide() self.cncfeedrate_rapid_entry.hide()
# Cut over 1st point in path # Cut over 1st point in path
self.extracut_cb = FCCheckBox('%s' % _('Re-cut')) self.extracut_cb = FCCheckBox('%s:' % _('Re-cut'))
self.extracut_cb.setToolTip( self.extracut_cb.setToolTip(
_("In order to remove possible\n" _("In order to remove possible\n"
"copper leftovers where first cut\n" "copper leftovers where first cut\n"
@ -1599,8 +1599,6 @@ class GeometryObjectUI(ObjectUI):
self.grid3.addWidget(self.extracut_cb, 13, 0) self.grid3.addWidget(self.extracut_cb, 13, 0)
self.grid3.addWidget(self.e_cut_entry, 13, 1) self.grid3.addWidget(self.e_cut_entry, 13, 1)
self.ois_e_cut = OptionalInputSection(self.extracut_cb, [self.e_cut_entry])
# Spindlespeed # Spindlespeed
spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed')) spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
spdlabel.setToolTip( spdlabel.setToolTip(

View File

@ -957,9 +957,17 @@ class ShapeCollectionLegacy:
:param linewidth: the width of the line :param linewidth: the width of the line
:return: :return:
""" """
self._color = color[:-2] if color is not None else None self._color = color if color is not None else "#006E20"
self._face_color = face_color[:-2] if face_color is not None else None self._face_color = face_color if face_color is not None else "#BBF268"
self._alpha = int(face_color[-2:], 16) / 255 if face_color is not None else 0.75
if len(self._color) > 7:
self._color = self._color[:7]
if len(self._face_color) > 7:
self._face_color = self._face_color[:7]
# self._alpha = int(self._face_color[-2:], 16) / 255
self._alpha = 0.75
if alpha is not None: if alpha is not None:
self._alpha = alpha self._alpha = alpha
@ -1033,7 +1041,7 @@ class ShapeCollectionLegacy:
if update is True: if update is True:
self.redraw() self.redraw()
def redraw(self): def redraw(self, update_colors=None):
""" """
This draw the shapes in the shapes collection, on canvas This draw the shapes in the shapes collection, on canvas
@ -1087,7 +1095,6 @@ class ShapeCollectionLegacy:
self.axes.plot(x, y, local_shapes[element]['color'], self.axes.plot(x, y, local_shapes[element]['color'],
linestyle='-', linestyle='-',
linewidth=local_shapes[element]['linewidth']) linewidth=local_shapes[element]['linewidth'])
elif obj_type == 'gerber': elif obj_type == 'gerber':
if self.obj.options["multicolored"]: if self.obj.options["multicolored"]:
linespec = '-' linespec = '-'
@ -1095,16 +1102,25 @@ class ShapeCollectionLegacy:
linespec = 'k-' linespec = 'k-'
if self.obj.options["solid"]: if self.obj.options["solid"]:
if update_colors:
gerber_fill_color = update_colors[0]
gerber_outline_color = update_colors[1]
else:
gerber_fill_color = local_shapes[element]['face_color']
gerber_outline_color = local_shapes[element]['color']
try: try:
patch = PolygonPatch(local_shapes[element]['shape'], patch = PolygonPatch(local_shapes[element]['shape'],
facecolor=local_shapes[element]['face_color'], facecolor=gerber_fill_color,
edgecolor=local_shapes[element]['color'], edgecolor=gerber_outline_color,
alpha=local_shapes[element]['alpha'], alpha=local_shapes[element]['alpha'],
zorder=2) zorder=2)
self.axes.add_patch(patch) self.axes.add_patch(patch)
except AssertionError: except AssertionError:
FlatCAMApp.App.log.warning("A geometry component was not a polygon:") FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
FlatCAMApp.App.log.warning(str(element)) FlatCAMApp.App.log.warning(str(element))
except Exception as e:
FlatCAMApp.App.log.debug("PlotCanvasLegacy.ShepeCollectionLegacy.redraw() --> %s" % str(e))
else: else:
x, y = local_shapes[element]['shape'].exterior.xy x, y = local_shapes[element]['shape'].exterior.xy
self.axes.plot(x, y, linespec) self.axes.plot(x, y, linespec)

View File

@ -5378,7 +5378,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.skewy_label, 4, 0) grid0.addWidget(self.skewy_label, 4, 0)
grid0.addWidget(self.skewy_entry, 4, 1) grid0.addWidget(self.skewy_entry, 4, 1)
# ## Scale factor on X axis # ## Scale
scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale")) scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
grid0.addWidget(scale_title_lbl, 5, 0, 1, 2) grid0.addWidget(scale_title_lbl, 5, 0, 1, 2)
@ -5425,7 +5425,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
) )
grid0.addWidget(self.reference_cb, 8, 1) grid0.addWidget(self.reference_cb, 8, 1)
# ## Offset distance on X axis # ## Offset
offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset")) offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset"))
grid0.addWidget(offset_title_lbl, 9, 0, 1, 2) grid0.addWidget(offset_title_lbl, 9, 0, 1, 2)
@ -5454,6 +5454,10 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.offy_label, 11, 0) grid0.addWidget(self.offy_label, 11, 0)
grid0.addWidget(self.offy_entry, 11, 1) grid0.addWidget(self.offy_entry, 11, 1)
# ## Mirror
mirror_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Mirror"))
grid0.addWidget(mirror_title_lbl, 12, 0, 1, 2)
# ## Mirror (Flip) Reference Point # ## Mirror (Flip) Reference Point
self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference")) self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
self.mirror_reference_cb.setToolTip( self.mirror_reference_cb.setToolTip(
@ -5466,9 +5470,9 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
"Then click Add button to insert coordinates.\n" "Then click Add button to insert coordinates.\n"
"Or enter the coords in format (x, y) in the\n" "Or enter the coords in format (x, y) in the\n"
"Point Entry field and click Flip on X(Y)")) "Point Entry field and click Flip on X(Y)"))
grid0.addWidget(self.mirror_reference_cb, 12, 0, 1, 2) grid0.addWidget(self.mirror_reference_cb, 13, 0, 1, 2)
self.flip_ref_label = QtWidgets.QLabel('<b>%s</b>' % _("Mirror Reference point")) self.flip_ref_label = QtWidgets.QLabel('%s' % _("Mirror Reference point"))
self.flip_ref_label.setToolTip( self.flip_ref_label.setToolTip(
_("Coordinates in format (x, y) used as reference for mirroring.\n" _("Coordinates in format (x, y) used as reference for mirroring.\n"
"The 'x' in (x, y) will be used when using Flip on X and\n" "The 'x' in (x, y) will be used when using Flip on X and\n"
@ -5476,8 +5480,42 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
) )
self.flip_ref_entry = EvalEntry2("(0, 0)") self.flip_ref_entry = EvalEntry2("(0, 0)")
grid0.addWidget(self.flip_ref_label, 13, 0, 1, 2) grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
grid0.addWidget(self.flip_ref_entry, 14, 0, 1, 2) grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)
# ## Buffer
buffer_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Buffer"))
grid0.addWidget(buffer_title_lbl, 16, 0, 1, 2)
self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased with the 'distance'.")
)
self.buffer_entry = FCDoubleSpinner()
self.buffer_entry.set_precision(self.decimals)
self.buffer_entry.setSingleStep(0.1)
self.buffer_entry.setWrapping(True)
self.buffer_entry.set_range(-9999.9999, 9999.9999)
grid0.addWidget(self.buffer_label, 17, 0)
grid0.addWidget(self.buffer_entry, 17, 1)
self.buffer_rounded_cb = FCCheckBox()
self.buffer_rounded_cb.setText('%s' % _("Rounded"))
self.buffer_rounded_cb.setToolTip(
_("If checked then the buffer will surround the buffered shape,\n"
"every corner will be rounded.\n"
"If not checked then the buffer will follow the exact geometry\n"
"of the buffered shape.")
)
grid0.addWidget(self.buffer_rounded_cb, 18, 0, 1, 2)
grid0.addWidget(QtWidgets.QLabel(''), 19, 0, 1, 2)
self.layout.addStretch() self.layout.addStretch()

View File

@ -17,10 +17,9 @@ from flatcamGUI.VisPyTesselators import GLUTess
class FlatCAMLineVisual(LineVisual): class FlatCAMLineVisual(LineVisual):
def __init__(self, pos=None, color=(0.5, 0.5, 0.5, 1), width=1, connect='strip', def __init__(self, pos=None, color=(0.5, 0.5, 0.5, 1), width=1, connect='strip', method='gl', antialias=False):
method='gl', antialias=False): LineVisual.__init__(self, pos=pos, color=color, width=width, connect=connect,
LineVisual.__init__(self, pos=None, color=(0.5, 0.5, 0.5, 1), width=1, connect='strip', method=method, antialias=True)
method='gl', antialias=True)
def clear_data(self): def clear_data(self):
self._bounds = None self._bounds = None
@ -46,44 +45,48 @@ def _update_shape_buffers(data, triangulation='glu'):
geo, color, face_color, tolerance = data['geometry'], data['color'], data['face_color'], data['tolerance'] geo, color, face_color, tolerance = data['geometry'], data['color'], data['face_color'], data['tolerance']
if geo is not None and not geo.is_empty: if geo is not None and not geo.is_empty:
simple = geo.simplify(tolerance) if tolerance else geo # Simplified shape simplified_geo = geo.simplify(tolerance) if tolerance else geo # Simplified shape
pts = [] # Shape line points pts = [] # Shape line points
tri_pts = [] # Mesh vertices tri_pts = [] # Mesh vertices
tri_tris = [] # Mesh faces tri_tris = [] # Mesh faces
if type(geo) == LineString: if type(geo) == LineString:
# Prepare lines # Prepare lines
pts = _linestring_to_segments(list(simple.coords)) pts = _linestring_to_segments(list(simplified_geo.coords))
elif type(geo) == LinearRing: elif type(geo) == LinearRing:
# Prepare lines # Prepare lines
pts = _linearring_to_segments(list(simple.coords)) pts = _linearring_to_segments(list(simplified_geo.coords))
elif type(geo) == Polygon: elif type(geo) == Polygon:
# Prepare polygon faces # Prepare polygon faces
if face_color is not None: if face_color is not None:
if triangulation == 'glu': if triangulation == 'glu':
gt = GLUTess() gt = GLUTess()
tri_tris, tri_pts = gt.triangulate(simple) tri_tris, tri_pts = gt.triangulate(simplified_geo)
else: else:
print("Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation) print("Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation)
# Prepare polygon edges # Prepare polygon edges
if color is not None: if color is not None:
pts = _linearring_to_segments(list(simple.exterior.coords)) pts = _linearring_to_segments(list(simplified_geo.exterior.coords))
for ints in simple.interiors: for ints in simplified_geo.interiors:
pts += _linearring_to_segments(list(ints.coords)) pts += _linearring_to_segments(list(ints.coords))
# Appending data for mesh # Appending data for mesh
if len(tri_pts) > 0 and len(tri_tris) > 0: if len(tri_pts) > 0 and len(tri_tris) > 0:
mesh_tris += tri_tris mesh_tris += tri_tris
mesh_vertices += tri_pts mesh_vertices += tri_pts
mesh_colors += [Color(face_color).rgba] * (len(tri_tris) // 3) face_color_rgba = Color(face_color).rgba
# mesh_colors += [face_color_rgba] * (len(tri_tris) // 3)
mesh_colors += [face_color_rgba for __ in range(len(tri_tris) // 3)]
# Appending data for line # Appending data for line
if len(pts) > 0: if len(pts) > 0:
line_pts += pts line_pts += pts
line_colors += [Color(color).rgba] * len(pts) colo_rgba = Color(color).rgba
# line_colors += [colo_rgba] * len(pts)
line_colors += [colo_rgba for __ in range(len(pts))]
# Store buffers # Store buffers
data['line_pts'] = line_pts data['line_pts'] = line_pts
@ -158,11 +161,14 @@ class ShapeGroup(object):
if update: if update:
self._collection.redraw([]) # Skip waiting results self._collection.redraw([]) # Skip waiting results
def redraw(self): def redraw(self, update_colors=None):
""" """
Redraws shape collection Redraws shape collection
""" """
self._collection.redraw(self._indexes) if update_colors:
self._collection.redraw(self._indexes, update_colors=update_colors)
else:
self._collection.redraw(self._indexes)
@property @property
def visible(self): def visible(self):
@ -228,9 +234,9 @@ class ShapeCollectionVisual(CompoundVisual):
pass pass
m.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) m.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False)
for l in self._lines: for lne in self._lines:
pass pass
l.set_gl_state(blend=True) lne.set_gl_state(blend=True)
self.freeze() self.freeze()
@ -245,6 +251,8 @@ class ShapeCollectionVisual(CompoundVisual):
Line/edge color Line/edge color
:param face_color: str, tuple :param face_color: str, tuple
Polygon face color Polygon face color
:param alpha: str
Polygon transparency
:param visible: bool :param visible: bool
Shape visibility Shape visibility
:param update: bool :param update: bool
@ -271,11 +279,11 @@ class ShapeCollectionVisual(CompoundVisual):
# Add data to process pool if pool exists # Add data to process pool if pool exists
try: try:
self.results[key] = self.pool.map_async(_update_shape_buffers, [self.data[key]]) self.results[key] = self.pool.map_async(_update_shape_buffers, [self.data[key]])
except Exception as e: except Exception:
self.data[key] = _update_shape_buffers(self.data[key]) self.data[key] = _update_shape_buffers(self.data[key])
if update: if update:
self.redraw() # redraw() waits for pool process end self.redraw() # redraw() waits for pool process end
return key return key
@ -309,6 +317,134 @@ class ShapeCollectionVisual(CompoundVisual):
if update: if update:
self.__update() self.__update()
def update_color(self, new_mesh_color=None, new_line_color=None, indexes=None):
if new_mesh_color is None and new_line_color is None:
return
if not self.data:
return
# if a new color is empty string then make it None so it will not be updated
# if a new color is valid then transform it here in a format palatable
mesh_color_rgba = None
line_color_rgba = None
if new_mesh_color:
if new_mesh_color != '':
mesh_color_rgba = Color(new_mesh_color).rgba
else:
new_mesh_color = None
if new_line_color:
if new_line_color != '':
line_color_rgba = Color(new_line_color).rgba
else:
new_line_color = None
mesh_colors = [[] for _ in range(0, len(self._meshes))] # Face colors
line_colors = [[] for _ in range(0, len(self._meshes))] # Line colors
line_pts = [[] for _ in range(0, len(self._lines))] # Vertices for line
# Lock sub-visuals updates
self.update_lock.acquire(True)
# Merge shapes buffers
if indexes is None:
for k, data in list(self.data.items()):
if data['visible'] and 'line_pts' in data:
if new_mesh_color and new_mesh_color != '':
dim_mesh_tris = (len(data['mesh_tris']) // 3)
if dim_mesh_tris != 0:
try:
mesh_colors[data['layer']] += [mesh_color_rgba] * dim_mesh_tris
self.data[k]['face_color'] = new_mesh_color
data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create mesh colors --> Data error. %s" % str(e))
if new_line_color and new_line_color != '':
dim_line_pts = (len(data['line_pts']))
if dim_line_pts != 0:
try:
line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += [line_color_rgba] * dim_line_pts
self.data[k]['color'] = new_line_color
data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create line colors --> Data error. %s" % str(e))
else:
for k, data in list(self.data.items()):
if data['visible'] and 'line_pts' in data:
dim_mesh_tris = (len(data['mesh_tris']) // 3)
dim_line_pts = (len(data['line_pts']))
if k in indexes:
if new_mesh_color and new_mesh_color != '':
if dim_mesh_tris != 0:
try:
mesh_colors[data['layer']] += [mesh_color_rgba] * dim_mesh_tris
self.data[k]['face_color'] = new_mesh_color
data['mesh_colors'] = [mesh_color_rgba for __ in range(len(data['mesh_colors']))]
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create mesh colors --> Data error. %s" % str(e))
if new_line_color and new_line_color != '':
if dim_line_pts != 0:
try:
line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += [line_color_rgba] * dim_line_pts
self.data[k]['color'] = new_line_color
data['line_colors'] = [mesh_color_rgba for __ in range(len(data['line_colors']))]
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create line colors --> Data error. %s" % str(e))
else:
if dim_mesh_tris != 0:
try:
mesh_colors[data['layer']] += [Color(data['face_color']).rgba] * dim_mesh_tris
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create mesh colors --> Data error. %s" % str(e))
if dim_line_pts != 0:
try:
line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += [Color(data['color']).rgba] * dim_line_pts
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Create line colors --> Data error. %s" % str(e))
# Updating meshes
if new_mesh_color and new_mesh_color != '':
for i, mesh in enumerate(self._meshes):
if mesh_colors[i]:
try:
mesh._meshdata.set_face_colors(colors=np.asarray(mesh_colors[i]))
mesh.mesh_data_changed()
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Apply mesh colors --> Data error. %s" % str(e))
# Updating lines
if new_line_color and new_line_color != '':
for i, line in enumerate(self._lines):
if len(line_pts[i]) > 0:
try:
line._color = np.asarray(line_colors[i])
line._changed['color'] = True
line.update()
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.update_color(). "
"Apply line colors --> Data error. %s" % str(e))
else:
line.clear_data()
self.update_lock.release()
def __update(self): def __update(self):
""" """
Merges internal buffers, sets data to visuals, redraws collection on scene Merges internal buffers, sets data to visuals, redraws collection on scene
@ -328,20 +464,23 @@ class ShapeCollectionVisual(CompoundVisual):
try: try:
line_pts[data['layer']] += data['line_pts'] line_pts[data['layer']] += data['line_pts']
line_colors[data['layer']] += data['line_colors'] line_colors[data['layer']] += data['line_colors']
mesh_tris[data['layer']] += [x + len(mesh_vertices[data['layer']])
for x in data['mesh_tris']]
mesh_tris[data['layer']] += [x + len(mesh_vertices[data['layer']]) for x in data['mesh_tris']]
mesh_vertices[data['layer']] += data['mesh_vertices'] mesh_vertices[data['layer']] += data['mesh_vertices']
mesh_colors[data['layer']] += data['mesh_colors'] mesh_colors[data['layer']] += data['mesh_colors']
except Exception as e: except Exception as e:
print("Data error", e) print("VisPyVisuals.ShapeCollectionVisual._update() --> Data error. %s" % str(e))
# Updating meshes # Updating meshes
for i, mesh in enumerate(self._meshes): for i, mesh in enumerate(self._meshes):
if len(mesh_vertices[i]) > 0: if len(mesh_vertices[i]) > 0:
set_state(polygon_offset_fill=False) set_state(polygon_offset_fill=False)
mesh.set_data(np.asarray(mesh_vertices[i]), np.asarray(mesh_tris[i], dtype=np.uint32) faces_array = np.asarray(mesh_tris[i], dtype=np.uint32)
.reshape((-1, 3)), face_colors=np.asarray(mesh_colors[i])) mesh.set_data(
vertices=np.asarray(mesh_vertices[i]),
faces=faces_array.reshape((-1, 3)),
face_colors=np.asarray(mesh_colors[i])
)
else: else:
mesh.set_data() mesh.set_data()
@ -350,17 +489,20 @@ class ShapeCollectionVisual(CompoundVisual):
# Updating lines # Updating lines
for i, line in enumerate(self._lines): for i, line in enumerate(self._lines):
if len(line_pts[i]) > 0: if len(line_pts[i]) > 0:
line.set_data(np.asarray(line_pts[i]), np.asarray(line_colors[i]), self._line_width, 'segments') line.set_data(
pos=np.asarray(line_pts[i]),
color=np.asarray(line_colors[i]),
width=self._line_width,
connect='segments')
else: else:
line.clear_data() line.clear_data()
line._bounds_changed() line._bounds_changed()
self._bounds_changed() self._bounds_changed()
self.update_lock.release() self.update_lock.release()
def redraw(self, indexes=None): def redraw(self, indexes=None, update_colors=None):
""" """
Redraws collection Redraws collection
:param indexes: list :param indexes: list
@ -369,19 +511,30 @@ class ShapeCollectionVisual(CompoundVisual):
# Only one thread can update data # Only one thread can update data
self.results_lock.acquire(True) self.results_lock.acquire(True)
for i in list(self.data.copy().keys()) if not indexes else indexes: for i in list(self.data.keys()) if not indexes else indexes:
if i in list(self.results.copy().keys()): if i in list(self.results.keys()):
try: try:
self.results[i].wait() # Wait for process results self.results[i].wait() # Wait for process results
if i in self.data: if i in self.data:
self.data[i] = self.results[i].get()[0] # Store translated data self.data[i] = self.results[i].get()[0] # Store translated data
del self.results[i] del self.results[i]
except Exception as e: except Exception as e:
print(e, indexes) print("VisPyVisuals.ShapeCollectionVisual.redraw() --> Data error = %s. Indexes = %s" %
(str(e), str(indexes)))
self.results_lock.release() self.results_lock.release()
self.__update() if update_colors is None:
self.__update()
else:
try:
self.update_color(
new_mesh_color=update_colors[0],
new_line_color=update_colors[1],
indexes=indexes
)
except Exception as e:
print("VisPyVisuals.ShapeCollectionVisual.redraw() --> Update colors error = %s." % str(e))
def lock_updates(self): def lock_updates(self):
self.update_lock.acquire(True) self.update_lock.acquire(True)
@ -489,7 +642,7 @@ class TextCollectionVisual(TextVisual):
self.lock.release() self.lock.release()
# Prepare data for translation # Prepare data for translation
self.data[key] = {'text': text, 'pos': pos, 'visible': visible,'font_size': font_size, 'color': color} self.data[key] = {'text': text, 'pos': pos, 'visible': visible, 'font_size': font_size, 'color': color}
if update: if update:
self.redraw() self.redraw()
@ -537,7 +690,7 @@ class TextCollectionVisual(TextVisual):
font_s = data['font_size'] font_s = data['font_size']
color = data['color'] color = data['color']
except Exception as e: except Exception as e:
print("Data error", e) print("VisPyVisuals.TextCollectionVisual._update() --> Data error. %s" % str(e))
# Updating text # Updating text
if len(labels) > 0: if len(labels) > 0:

View File

@ -1457,4 +1457,35 @@ class Excellon(Geometry):
slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py)) slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py))
self.create_geometry() self.create_geometry()
self.app.proc_container.new_text = '' self.app.proc_container.new_text = ''
def buffer(self, distance, join):
"""
:param distance:
:param join:
:return:
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
if distance == 0:
return
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
return obj.buffer(distance, resolution=self.geo_steps_per_circle)
except AttributeError:
return obj
# buffer solid_geometry
for tool, tool_dict in list(self.tools.items()):
self.tools[tool]['solid_geometry'] = buffer_geom(tool_dict['solid_geometry'])
self.tools[tool]['C'] += distance
self.create_geometry()

View File

@ -2169,6 +2169,87 @@ class Gerber(Geometry):
_("Gerber Rotate done.")) _("Gerber Rotate done."))
self.app.proc_container.new_text = '' self.app.proc_container.new_text = ''
def buffer(self, distance, join):
"""
:param distance:
:return:
"""
log.debug("parseGerber.Gerber.buffer()")
if distance == 0:
return
# variables to display the percentage of work done
self.geo_len = 0
try:
for __ in self.solid_geometry:
self.geo_len += 1
except TypeError:
self.geo_len = 1
self.old_disp_number = 0
self.el_count = 0
def buffer_geom(obj):
if type(obj) is list:
new_obj = []
for g in obj:
new_obj.append(buffer_geom(g))
return new_obj
else:
try:
self.el_count += 1
disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
if self.old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
self.old_disp_number = disp_number
return obj.buffer(distance, resolution=self.steps_per_circle, join_style=join)
except AttributeError:
return obj
self.solid_geometry = buffer_geom(self.solid_geometry)
# we need to buffer the geometry stored in the Gerber apertures, too
try:
for apid in self.apertures:
new_geometry = list()
if 'geometry' in self.apertures[apid]:
for geo_el in self.apertures[apid]['geometry']:
new_geo_el = dict()
if 'solid' in geo_el:
new_geo_el['solid'] = buffer_geom(geo_el['solid'])
if 'follow' in geo_el:
new_geo_el['follow'] = buffer_geom(geo_el['follow'])
if 'clear' in geo_el:
new_geo_el['clear'] = buffer_geom(geo_el['clear'])
new_geometry.append(new_geo_el)
self.apertures[apid]['geometry'] = deepcopy(new_geometry)
try:
if str(self.apertures[apid]['type']) == 'R' or str(self.apertures[apid]['type']) == 'O':
self.apertures[apid]['width'] += (distance * 2)
self.apertures[apid]['height'] += (distance * 2)
elif str(self.apertures[apid]['type']) == 'P':
self.apertures[apid]['diam'] += (distance * 2)
self.apertures[apid]['nVertices'] += (distance * 2)
except KeyError:
pass
try:
if self.apertures[apid]['size'] is not None:
self.apertures[apid]['size'] = float(self.apertures[apid]['size'] + (distance * 2))
except KeyError:
pass
except Exception as e:
log.debug('camlib.Gerber.buffer() Exception --> %s' % str(e))
return 'fail'
self.app.inform.emit('[success] %s' % _("Gerber Buffer done."))
self.app.proc_container.new_text = ''
def parse_gerber_number(strnumber, int_digits, frac_digits, zeros): def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
""" """

View File

@ -349,7 +349,7 @@ class Distance(FlatCAMTool):
d = math.sqrt(dx ** 2 + dy ** 2) d = math.sqrt(dx ** 2 + dy ** 2)
self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | (tx3} = {d_z}".format( self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
tx1=_("MEASURING"), tx1=_("MEASURING"),
tx2=_("Result"), tx2=_("Result"),
tx3=_("Distance"), tx3=_("Distance"),

View File

@ -27,6 +27,7 @@ class ToolTransform(FlatCAMTool):
scaleName = _("Scale") scaleName = _("Scale")
flipName = _("Mirror (Flip)") flipName = _("Mirror (Flip)")
offsetName = _("Offset") offsetName = _("Offset")
bufferName = _("Buffer")
def __init__(self, app): def __init__(self, app):
FlatCAMTool.__init__(self, app) FlatCAMTool.__init__(self, app)
@ -255,11 +256,11 @@ class ToolTransform(FlatCAMTool):
grid0.addWidget(self.offy_entry, 14, 1) grid0.addWidget(self.offy_entry, 14, 1)
grid0.addWidget(self.offy_button, 14, 2) grid0.addWidget(self.offy_button, 14, 2)
grid0.addWidget(QtWidgets.QLabel('')) grid0.addWidget(QtWidgets.QLabel(''), 15, 0, 1, 3)
# ## Flip Title # ## Flip Title
flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName) flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
self.transform_lay.addWidget(flip_title_label) grid0.addWidget(flip_title_label, 16, 0, 1, 3)
self.flipx_button = FCButton() self.flipx_button = FCButton()
self.flipx_button.set_value(_("Flip on X")) self.flipx_button.set_value(_("Flip on X"))
@ -274,7 +275,7 @@ class ToolTransform(FlatCAMTool):
) )
hlay0 = QtWidgets.QHBoxLayout() hlay0 = QtWidgets.QHBoxLayout()
self.transform_lay.addLayout(hlay0) grid0.addLayout(hlay0, 17, 0, 1, 3)
hlay0.addWidget(self.flipx_button) hlay0.addWidget(self.flipx_button)
hlay0.addWidget(self.flipy_button) hlay0.addWidget(self.flipy_button)
@ -293,7 +294,7 @@ class ToolTransform(FlatCAMTool):
"Or enter the coords in format (x, y) in the\n" "Or enter the coords in format (x, y) in the\n"
"Point Entry field and click Flip on X(Y)")) "Point Entry field and click Flip on X(Y)"))
self.transform_lay.addWidget(self.flip_ref_cb) grid0.addWidget(self.flip_ref_cb, 18, 0, 1, 3)
self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point")) self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
self.flip_ref_label.setToolTip( self.flip_ref_label.setToolTip(
@ -315,12 +316,60 @@ class ToolTransform(FlatCAMTool):
self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True) self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
hlay1 = QtWidgets.QHBoxLayout() hlay1 = QtWidgets.QHBoxLayout()
self.transform_lay.addLayout(hlay1) grid0.addLayout(hlay1, 19, 0, 1, 3)
hlay1.addWidget(self.flip_ref_label) hlay1.addWidget(self.flip_ref_label)
hlay1.addWidget(self.flip_ref_entry) hlay1.addWidget(self.flip_ref_entry)
self.transform_lay.addWidget(self.flip_ref_button) grid0.addWidget(self.flip_ref_button, 20, 0, 1, 3)
grid0.addWidget(QtWidgets.QLabel(''), 21, 0, 1, 3)
# ## Buffer Title
buffer_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.bufferName)
grid0.addWidget(buffer_title_label, 22, 0, 1, 3)
self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased with the 'distance'.")
)
self.buffer_entry = FCDoubleSpinner()
self.buffer_entry.set_precision(self.decimals)
self.buffer_entry.setSingleStep(0.1)
self.buffer_entry.setWrapping(True)
self.buffer_entry.set_range(-9999.9999, 9999.9999)
# self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.buffer_button = FCButton()
self.buffer_button.set_value(_("Buffer"))
self.buffer_button.setToolTip(
_("Create the buffer effect on each geometry,\n"
"element from the selected object.")
)
self.buffer_button.setMinimumWidth(90)
grid0.addWidget(self.buffer_label, 23, 0)
grid0.addWidget(self.buffer_entry, 23, 1)
grid0.addWidget(self.buffer_button, 23, 2)
self.buffer_rounded_cb = FCCheckBox()
self.buffer_rounded_cb.setText('%s' % _("Rounded"))
self.buffer_rounded_cb.setToolTip(
_("If checked then the buffer will surround the buffered shape,\n"
"every corner will be rounded.\n"
"If not checked then the buffer will follow the exact geometry\n"
"of the buffered shape.")
)
grid0.addWidget(self.buffer_rounded_cb, 24, 0, 1, 3)
grid0.addWidget(QtWidgets.QLabel(''), 25, 0, 1, 3)
self.transform_lay.addStretch() self.transform_lay.addStretch()
# ## Signals # ## Signals
@ -334,14 +383,16 @@ class ToolTransform(FlatCAMTool):
self.flipx_button.clicked.connect(self.on_flipx) self.flipx_button.clicked.connect(self.on_flipx)
self.flipy_button.clicked.connect(self.on_flipy) self.flipy_button.clicked.connect(self.on_flipy)
self.flip_ref_button.clicked.connect(self.on_flip_add_coords) self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
self.buffer_button.clicked.connect(self.on_buffer)
self.rotate_entry.returnPressed.connect(self.on_rotate) # self.rotate_entry.returnPressed.connect(self.on_rotate)
self.skewx_entry.returnPressed.connect(self.on_skewx) # self.skewx_entry.returnPressed.connect(self.on_skewx)
self.skewy_entry.returnPressed.connect(self.on_skewy) # self.skewy_entry.returnPressed.connect(self.on_skewy)
self.scalex_entry.returnPressed.connect(self.on_scalex) # self.scalex_entry.returnPressed.connect(self.on_scalex)
self.scaley_entry.returnPressed.connect(self.on_scaley) # self.scaley_entry.returnPressed.connect(self.on_scaley)
self.offx_entry.returnPressed.connect(self.on_offx) # self.offx_entry.returnPressed.connect(self.on_offx)
self.offy_entry.returnPressed.connect(self.on_offy) # self.offy_entry.returnPressed.connect(self.on_offy)
# self.buffer_entry.returnPressed.connect(self.on_buffer)
def run(self, toggle=True): def run(self, toggle=True):
self.app.report_usage("ToolTransform()") self.app.report_usage("ToolTransform()")
@ -430,6 +481,16 @@ class ToolTransform(FlatCAMTool):
else: else:
self.flip_ref_entry.set_value((0, 0)) self.flip_ref_entry.set_value((0, 0))
if self.app.defaults["tools_transform_buffer_dis"]:
self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"])
else:
self.buffer_entry.set_value(0.0)
if self.app.defaults["tools_transform_buffer_corner"]:
self.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"])
else:
self.buffer_rounded_cb.set_value(True)
def on_rotate(self): def on_rotate(self):
value = float(self.rotate_entry.get_value()) value = float(self.rotate_entry.get_value())
if value == 0: if value == 0:
@ -511,8 +572,7 @@ class ToolTransform(FlatCAMTool):
def on_offx(self): def on_offx(self):
value = float(self.offx_entry.get_value()) value = float(self.offx_entry.get_value())
if value == 0: if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
_("Offset transformation can not be done for a value of 0."))
return return
axis = 'X' axis = 'X'
@ -522,14 +582,20 @@ class ToolTransform(FlatCAMTool):
def on_offy(self): def on_offy(self):
value = float(self.offy_entry.get_value()) value = float(self.offy_entry.get_value())
if value == 0: if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
_("Offset transformation can not be done for a value of 0."))
return return
axis = 'Y' axis = 'Y'
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
return return
def on_buffer(self):
value = self.buffer_entry.get_value()
join = 1 if self.buffer_rounded_cb.get_value() else 2
self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]})
return
def on_rotate_action(self, num): def on_rotate_action(self, num):
obj_list = self.app.collection.get_selected() obj_list = self.app.collection.get_selected()
xminlist = [] xminlist = []
@ -808,4 +874,40 @@ class ToolTransform(FlatCAMTool):
(_("Due of"), str(e), _("action was not executed."))) (_("Due of"), str(e), _("action was not executed.")))
return return
def on_buffer_action(self, value, join):
obj_list = self.app.collection.get_selected()
if not obj_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to buffer!"))
return
else:
with self.app.proc_container.new(_("Applying Buffer")):
try:
for sel_obj in obj_list:
if isinstance(sel_obj, FlatCAMCNCjob):
self.app.inform.emit(_("CNCJob objects can't be buffered."))
elif sel_obj.kind.lower() == 'gerber':
sel_obj.buffer(value, join)
sel_obj.source_file = self.app.export_gerber(obj_name=sel_obj.options['name'],
filename=None, local_use=sel_obj,
use_thread=False)
elif sel_obj.kind.lower() == 'excellon':
sel_obj.buffer(value, join)
sel_obj.source_file = self.app.export_excellon(obj_name=sel_obj.options['name'],
filename=None, local_use=sel_obj,
use_thread=False)
elif sel_obj.kind.lower() == 'geometry':
sel_obj.buffer(value, join)
self.app.object_changed.emit(sel_obj)
sel_obj.plot()
self.app.inform.emit('[success] %s...' % _('Buffer done'))
except Exception as e:
self.app.log.debug("ToolTransform.on_buffer_action() --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
(_("Due of"), str(e), _("action was not executed.")))
return
# end of file # end of file

BIN
share/blue32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/brown32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

BIN
share/green32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/red32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/set_color16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

BIN
share/set_color32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

BIN
share/snap_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

BIN
share/snap_filled_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

BIN
share/violet32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/yellow32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B