commit
d7aeb7b37f
428
FlatCAMApp.py
428
FlatCAMApp.py
@ -94,8 +94,8 @@ class App(QtCore.QObject):
|
||||
log.addHandler(handler)
|
||||
|
||||
# Version
|
||||
version = 8.915
|
||||
version_date = "2019/05/1"
|
||||
version = 8.917
|
||||
version_date = "2019/05/22"
|
||||
beta = True
|
||||
|
||||
# current date now
|
||||
@ -322,6 +322,9 @@ class App(QtCore.QObject):
|
||||
"global_project_autohide": self.ui.general_defaults_form.general_app_group.project_autohide_cb,
|
||||
"global_toggle_tooltips": self.ui.general_defaults_form.general_app_group.toggle_tooltips_cb,
|
||||
"global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb,
|
||||
"global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry,
|
||||
|
||||
"global_open_style": self.ui.general_defaults_form.general_app_group.open_style_cb,
|
||||
|
||||
"global_compression_level": self.ui.general_defaults_form.general_app_group.compress_combo,
|
||||
"global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb,
|
||||
@ -368,10 +371,19 @@ class App(QtCore.QObject):
|
||||
|
||||
# Gerber Advanced Options
|
||||
"gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb,
|
||||
"gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
|
||||
"gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
|
||||
# "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
|
||||
# "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
|
||||
"gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
|
||||
|
||||
# Gerber Export
|
||||
"gerber_exp_units": self.ui.gerber_defaults_form.gerber_exp_group.gerber_units_radio,
|
||||
"gerber_exp_integer": self.ui.gerber_defaults_form.gerber_exp_group.format_whole_entry,
|
||||
"gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry,
|
||||
"gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio,
|
||||
|
||||
# Gerber Editor
|
||||
"gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry,
|
||||
|
||||
# Excellon General
|
||||
"excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
|
||||
"excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
|
||||
@ -389,6 +401,7 @@ class App(QtCore.QObject):
|
||||
"excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
|
||||
"excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_entry,
|
||||
"excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry,
|
||||
"excellon_spindledir": self.ui.excellon_defaults_form.excellon_opt_group.spindledir_radio,
|
||||
"excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb,
|
||||
"excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry,
|
||||
"excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb,
|
||||
@ -427,6 +440,7 @@ class App(QtCore.QObject):
|
||||
"geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry,
|
||||
"geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.cncplunge_entry,
|
||||
"geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry,
|
||||
"geometry_spindledir": self.ui.geometry_defaults_form.geometry_opt_group.spindledir_radio,
|
||||
"geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb,
|
||||
"geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry,
|
||||
"geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb,
|
||||
@ -447,6 +461,9 @@ class App(QtCore.QObject):
|
||||
"geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry,
|
||||
"geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry,
|
||||
|
||||
# Geometry Editor
|
||||
"geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
|
||||
|
||||
# CNCJob General
|
||||
"cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb,
|
||||
"cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio,
|
||||
@ -594,6 +611,8 @@ class App(QtCore.QObject):
|
||||
"global_project_autohide": True,
|
||||
"global_toggle_tooltips": True,
|
||||
"global_worker_number": 2,
|
||||
"global_tolerance": 0.01,
|
||||
"global_open_style": True,
|
||||
"global_compression_level": 3,
|
||||
"global_save_compressed": True,
|
||||
|
||||
@ -653,7 +672,7 @@ class App(QtCore.QObject):
|
||||
"global_zdownrate": None,
|
||||
|
||||
# General GUI Settings
|
||||
"global_hover": True,
|
||||
"global_hover": False,
|
||||
"global_selection_shape": True,
|
||||
"global_layout": "compact",
|
||||
# Gerber General
|
||||
@ -671,7 +690,7 @@ class App(QtCore.QObject):
|
||||
"gerber_noncopperrounded": False,
|
||||
"gerber_bboxmargin": 0.1,
|
||||
"gerber_bboxrounded": False,
|
||||
"gerber_circle_steps": 64,
|
||||
"gerber_circle_steps": 128,
|
||||
"gerber_use_buffer_for_union": True,
|
||||
|
||||
# Gerber Advanced Options
|
||||
@ -680,6 +699,15 @@ class App(QtCore.QObject):
|
||||
"gerber_aperture_buffer_factor": 0.0,
|
||||
"gerber_follow": False,
|
||||
|
||||
# Gerber Export
|
||||
"gerber_exp_units": 'IN',
|
||||
"gerber_exp_integer": 2,
|
||||
"gerber_exp_decimals": 4,
|
||||
"gerber_exp_zeros": 'L',
|
||||
|
||||
# Gerber Editor
|
||||
"gerber_editor_sel_limit": 30,
|
||||
|
||||
# Excellon General
|
||||
"excellon_plot": True,
|
||||
"excellon_solid": True,
|
||||
@ -697,10 +725,11 @@ class App(QtCore.QObject):
|
||||
"excellon_travelz": 0.1,
|
||||
"excellon_feedrate": 3.0,
|
||||
"excellon_spindlespeed": None,
|
||||
"excellon_spindledir": 'CW',
|
||||
"excellon_dwell": False,
|
||||
"excellon_dwelltime": 1,
|
||||
"excellon_toolchange": False,
|
||||
"excellon_toolchangez": 1.0,
|
||||
"excellon_toolchangez": 0.5,
|
||||
"excellon_ppname_e": 'default',
|
||||
"excellon_tooldia": 0.016,
|
||||
"excellon_slot_tooldia": 0.016,
|
||||
@ -710,7 +739,7 @@ class App(QtCore.QObject):
|
||||
"excellon_offset": 0.0,
|
||||
"excellon_toolchangexy": "0.0, 0.0",
|
||||
"excellon_startz": None,
|
||||
"excellon_endz": 2.0,
|
||||
"excellon_endz": 0.5,
|
||||
"excellon_feedrate_rapid": 3.0,
|
||||
"excellon_z_pdepth": -0.02,
|
||||
"excellon_feedrate_probe": 3.0,
|
||||
@ -726,7 +755,7 @@ class App(QtCore.QObject):
|
||||
|
||||
# Geometry General
|
||||
"geometry_plot": True,
|
||||
"geometry_circle_steps": 64,
|
||||
"geometry_circle_steps": 128,
|
||||
"geometry_cnctooldia": 0.016,
|
||||
|
||||
# Geometry Options
|
||||
@ -735,10 +764,11 @@ class App(QtCore.QObject):
|
||||
"geometry_depthperpass": 0.002,
|
||||
"geometry_travelz": 0.1,
|
||||
"geometry_toolchange": False,
|
||||
"geometry_toolchangez": 1.0,
|
||||
"geometry_toolchangez": 0.5,
|
||||
"geometry_feedrate": 3.0,
|
||||
"geometry_feedrate_z": 3.0,
|
||||
"geometry_spindlespeed": None,
|
||||
"geometry_spindledir": 'CW',
|
||||
"geometry_dwell": False,
|
||||
"geometry_dwelltime": 1,
|
||||
"geometry_ppname_g": 'default',
|
||||
@ -746,7 +776,7 @@ class App(QtCore.QObject):
|
||||
# Geometry Advanced Options
|
||||
"geometry_toolchangexy": "0.0, 0.0",
|
||||
"geometry_startz": None,
|
||||
"geometry_endz": 2.0,
|
||||
"geometry_endz": 0.5,
|
||||
"geometry_feedrate_rapid": 3.0,
|
||||
"geometry_extracut": False,
|
||||
"geometry_z_pdepth": -0.02,
|
||||
@ -755,6 +785,9 @@ class App(QtCore.QObject):
|
||||
"geometry_segx": 0.0,
|
||||
"geometry_segy": 0.0,
|
||||
|
||||
# Geometry Editor
|
||||
"geometry_editor_sel_limit": 30,
|
||||
|
||||
# CNC Job General
|
||||
"cncjob_plot": True,
|
||||
"cncjob_plot_kind": 'all',
|
||||
@ -928,6 +961,7 @@ class App(QtCore.QObject):
|
||||
"excellon_travelz": self.ui.excellon_options_form.excellon_opt_group.travelz_entry,
|
||||
"excellon_feedrate": self.ui.excellon_options_form.excellon_opt_group.feedrate_entry,
|
||||
"excellon_spindlespeed": self.ui.excellon_options_form.excellon_opt_group.spindlespeed_entry,
|
||||
"excellon_spindledir": self.ui.excellon_options_form.excellon_opt_group.spindledir_radio,
|
||||
"excellon_dwell": self.ui.excellon_options_form.excellon_opt_group.dwell_cb,
|
||||
"excellon_dwelltime": self.ui.excellon_options_form.excellon_opt_group.dwelltime_entry,
|
||||
"excellon_toolchange": self.ui.excellon_options_form.excellon_opt_group.toolchange_cb,
|
||||
@ -949,6 +983,7 @@ class App(QtCore.QObject):
|
||||
"geometry_feedrate": self.ui.geometry_options_form.geometry_opt_group.cncfeedrate_entry,
|
||||
"geometry_feedrate_z": self.ui.geometry_options_form.geometry_opt_group.cncplunge_entry,
|
||||
"geometry_spindlespeed": self.ui.geometry_options_form.geometry_opt_group.cncspindlespeed_entry,
|
||||
"geometry_spindledir": self.ui.geometry_options_form.geometry_opt_group.spindledir_radio,
|
||||
"geometry_dwell": self.ui.geometry_options_form.geometry_opt_group.dwell_cb,
|
||||
"geometry_dwelltime": self.ui.geometry_options_form.geometry_opt_group.dwelltime_entry,
|
||||
"geometry_ppname_g": self.ui.geometry_options_form.geometry_opt_group.pp_geometry_name_cb,
|
||||
@ -1051,6 +1086,7 @@ class App(QtCore.QObject):
|
||||
"excellon_feedrate": 3.0,
|
||||
"excellon_feedrate_rapid": 3.0,
|
||||
"excellon_spindlespeed": None,
|
||||
"excellon_spindledir": 'CW',
|
||||
"excellon_dwell": True,
|
||||
"excellon_dwelltime": 1000,
|
||||
"excellon_toolchange": False,
|
||||
@ -1071,6 +1107,7 @@ class App(QtCore.QObject):
|
||||
"geometry_feedrate_z": 3.0,
|
||||
"geometry_feedrate_rapid": 3.0,
|
||||
"geometry_spindlespeed": None,
|
||||
"geometry_spindledir": 'CW',
|
||||
"geometry_dwell": True,
|
||||
"geometry_dwelltime": 1000,
|
||||
"geometry_cnctooldia": 0.016,
|
||||
@ -1297,7 +1334,7 @@ class App(QtCore.QObject):
|
||||
self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
|
||||
self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng)
|
||||
self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon)
|
||||
|
||||
self.ui.menufileexportgerber.triggered.connect(self.on_file_exportgerber)
|
||||
|
||||
self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf)
|
||||
|
||||
@ -1320,7 +1357,9 @@ class App(QtCore.QObject):
|
||||
self.ui.menueditdelete.triggered.connect(self.on_delete)
|
||||
|
||||
self.ui.menueditcopyobject.triggered.connect(self.on_copy_object)
|
||||
self.ui.menueditcopyobjectasgeom.triggered.connect(self.on_copy_object_as_geometry)
|
||||
self.ui.menueditconvert_any2geo.triggered.connect(self.convert_any2geo)
|
||||
self.ui.menueditconvert_any2gerber.triggered.connect(self.convert_any2gerber)
|
||||
|
||||
self.ui.menueditorigin.triggered.connect(self.on_set_origin)
|
||||
self.ui.menueditjump.triggered.connect(self.on_jump_to)
|
||||
|
||||
@ -1416,7 +1455,8 @@ class App(QtCore.QObject):
|
||||
self.ui.general_defaults_form.general_app_group.language_apply_btn.clicked.connect(
|
||||
lambda: fcTranslate.on_language_apply_click(self, restart=True)
|
||||
)
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(self.on_toggle_units)
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
|
||||
lambda :self.on_toggle_units(no_pref=False))
|
||||
|
||||
###############################
|
||||
### GUI PREFERENCES SIGNALS ###
|
||||
@ -1865,7 +1905,7 @@ class App(QtCore.QObject):
|
||||
'dim', 'mil', 'grb', 'top', 'bot', 'smt', 'smb', 'sst', 'ssb', 'spt', 'spb', 'pho', 'gdo',
|
||||
'art', 'gbd', 'gb0', 'gb1', 'gb2', 'gb3', 'g4', 'gb5', 'gb6', 'gb7', 'gb8', 'gb9'
|
||||
]
|
||||
self.exc_list = ['drl', 'txt', 'xln', 'drd', 'tap', 'exc']
|
||||
self.exc_list = ['drl', 'txt', 'xln', 'drd', 'tap', 'exc', 'ncd']
|
||||
self.gcode_list = ['nc', 'ncc', 'tap', 'gcode', 'cnc', 'ecs', 'fnc', 'dnc', 'ncg', 'gc', 'fan', 'fgc', 'din',
|
||||
'xpi', 'hnc', 'h', 'i', 'ncp', 'min', 'gcd', 'rol', 'mpr', 'ply', 'out', 'eia', 'plt', 'sbp',
|
||||
'mpf']
|
||||
@ -2169,6 +2209,9 @@ class App(QtCore.QObject):
|
||||
# set call source to the Editor we go into
|
||||
self.call_source = 'exc_editor'
|
||||
|
||||
if self.ui.splitter.sizes()[0] == 0:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
|
||||
elif isinstance(edited_object, FlatCAMGerber):
|
||||
# store the Gerber Editor Toolbar visibility before entering in the Editor
|
||||
self.grb_editor.toolbar_old_state = True if self.ui.grb_edit_toolbar.isVisible() else False
|
||||
@ -2177,6 +2220,9 @@ class App(QtCore.QObject):
|
||||
# set call source to the Editor we go into
|
||||
self.call_source = 'grb_editor'
|
||||
|
||||
if self.ui.splitter.sizes()[0] == 0:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
|
||||
# # 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)
|
||||
@ -2241,30 +2287,18 @@ class App(QtCore.QObject):
|
||||
self.inform.emit(_("[WARNING] Object empty after edit."))
|
||||
log.debug("App.editor2object() --> Geometry --> %s" % str(e))
|
||||
elif isinstance(edited_obj, FlatCAMGerber):
|
||||
new_obj = self.collection.get_active()
|
||||
obj_type = "Gerber"
|
||||
if cleanup is None:
|
||||
self.grb_editor.update_fcgerber(edited_obj)
|
||||
self.grb_editor.update_options(new_obj)
|
||||
self.grb_editor.update_fcgerber()
|
||||
self.grb_editor.update_options(edited_obj)
|
||||
self.grb_editor.deactivate_grb_editor()
|
||||
|
||||
# delete the old object (the source object) if it was an empty one
|
||||
if edited_obj.solid_geometry.is_empty:
|
||||
if len(edited_obj.solid_geometry) == 0:
|
||||
old_name = edited_obj.options['name']
|
||||
self.collection.set_active(old_name)
|
||||
self.collection.delete_active()
|
||||
else:
|
||||
# update the geo object options so it is including the bounding box values
|
||||
# but don't do this for objects that are made out of empty source objects, it will fail
|
||||
try:
|
||||
xmin, ymin, xmax, ymax = new_obj.bounds()
|
||||
new_obj.options['xmin'] = xmin
|
||||
new_obj.options['ymin'] = ymin
|
||||
new_obj.options['xmax'] = xmax
|
||||
new_obj.options['ymax'] = ymax
|
||||
except Exception as e:
|
||||
self.inform.emit(_("[WARNING] Object empty after edit."))
|
||||
log.debug("App.editor2object() --> Gerber --> %s" % str(e))
|
||||
|
||||
elif isinstance(edited_obj, FlatCAMExcellon):
|
||||
obj_type = "Excellon"
|
||||
if cleanup is None:
|
||||
@ -2757,7 +2791,8 @@ class App(QtCore.QObject):
|
||||
except:
|
||||
self.inform.emit(_("[ERROR_NOTCL] Failed to write defaults to file."))
|
||||
return
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("preferences", filename)
|
||||
self.file_saved.emit("preferences", filename)
|
||||
self.inform.emit("[success] Exported Defaults to %s" % filename)
|
||||
|
||||
@ -2961,6 +2996,7 @@ class App(QtCore.QObject):
|
||||
grb_obj.multigeo = False
|
||||
grb_obj.follow = False
|
||||
grb_obj.apertures = {}
|
||||
grb_obj.solid_geometry = []
|
||||
|
||||
try:
|
||||
grb_obj.options['xmin'] = 0
|
||||
@ -3513,7 +3549,7 @@ class App(QtCore.QObject):
|
||||
def set_screen_units(self, units):
|
||||
self.ui.units_label.setText("[" + self.defaults["units"].lower() + "]")
|
||||
|
||||
def on_toggle_units(self):
|
||||
def on_toggle_units(self, no_pref=False):
|
||||
"""
|
||||
Callback for the Units radio-button change in the Options tab.
|
||||
Changes the application's default units or the current project's units.
|
||||
@ -3651,13 +3687,14 @@ class App(QtCore.QObject):
|
||||
response = msgbox.clickedButton()
|
||||
|
||||
if response == bt_ok:
|
||||
self.options_read_form()
|
||||
scale_options(factor)
|
||||
self.options_write_form()
|
||||
if no_pref is False:
|
||||
self.options_read_form()
|
||||
scale_options(factor)
|
||||
self.options_write_form()
|
||||
|
||||
self.defaults_read_form()
|
||||
scale_defaults(factor)
|
||||
self.defaults_write_form()
|
||||
self.defaults_read_form()
|
||||
scale_defaults(factor)
|
||||
self.defaults_write_form()
|
||||
|
||||
self.should_we_save = True
|
||||
|
||||
@ -3669,8 +3706,8 @@ class App(QtCore.QObject):
|
||||
self.ui.grid_gap_x_entry.set_value(float(self.ui.grid_gap_x_entry.get_value()) * factor)
|
||||
self.ui.grid_gap_y_entry.set_value(float(self.ui.grid_gap_y_entry.get_value()) * factor)
|
||||
|
||||
units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
for obj in self.collection.get_list():
|
||||
units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
obj.convert_units(units)
|
||||
|
||||
# make that the properties stored in the object are also updated
|
||||
@ -3684,9 +3721,9 @@ class App(QtCore.QObject):
|
||||
current.to_form()
|
||||
|
||||
self.plot_all()
|
||||
self.inform.emit(_("[success] Converted units to %s") % self.defaults["units"])
|
||||
self.inform.emit(_("[success] Converted units to %s") % units)
|
||||
# self.ui.units_label.setText("[" + self.options["units"] + "]")
|
||||
self.set_screen_units(self.defaults["units"])
|
||||
self.set_screen_units(units)
|
||||
else:
|
||||
# Undo toggling
|
||||
self.toggle_units_ignore = True
|
||||
@ -3701,11 +3738,14 @@ class App(QtCore.QObject):
|
||||
self.defaults_read_form()
|
||||
|
||||
def on_toggle_units_click(self):
|
||||
if self.options["units"] == 'MM':
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect()
|
||||
if self.defaults["units"] == 'MM':
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.set_value("IN")
|
||||
else:
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.set_value("MM")
|
||||
self.on_toggle_units()
|
||||
self.on_toggle_units(no_pref=True)
|
||||
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
|
||||
lambda: self.on_toggle_units(no_pref=False))
|
||||
|
||||
def on_fullscreen(self):
|
||||
self.report_usage("on_fullscreen()")
|
||||
@ -4374,8 +4414,8 @@ class App(QtCore.QObject):
|
||||
return
|
||||
|
||||
# Just for adding it to the recent files list.
|
||||
self.file_opened.emit("cncjob", filename)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("cncjob", filename)
|
||||
self.file_saved.emit("cncjob", filename)
|
||||
self.inform.emit(_("Saved to: %s") % filename)
|
||||
|
||||
@ -4552,6 +4592,13 @@ class App(QtCore.QObject):
|
||||
self.report_usage("on_delete")
|
||||
|
||||
while (self.collection.get_active()):
|
||||
obj_active = self.collection.get_active()
|
||||
# if the deleted object is FlatCAMGerber then make sure to delete the possbile mark shapes
|
||||
if isinstance(obj_active, FlatCAMGerber):
|
||||
for el in obj_active.mark_shapes:
|
||||
obj_active.mark_shapes[el].clear(update=True)
|
||||
obj_active.mark_shapes[el].enabled = False
|
||||
obj_active.mark_shapes[el] = None
|
||||
self.delete_first_selected()
|
||||
|
||||
self.inform.emit(_("Object(s) deleted ..."))
|
||||
@ -4713,8 +4760,8 @@ class App(QtCore.QObject):
|
||||
except:
|
||||
log.warning("Could not rename the object in the list")
|
||||
|
||||
def on_copy_object_as_geometry(self):
|
||||
self.report_usage("on_copy_object_as_geometry()")
|
||||
def convert_any2geo(self):
|
||||
self.report_usage("convert_any2geo()")
|
||||
|
||||
def initialize(obj_init, app):
|
||||
obj_init.solid_geometry = obj.solid_geometry
|
||||
@ -4727,8 +4774,11 @@ class App(QtCore.QObject):
|
||||
except:
|
||||
pass
|
||||
|
||||
if obj.tools:
|
||||
obj_init.tools = obj.tools
|
||||
try:
|
||||
if obj.tools:
|
||||
obj_init.tools = obj.tools
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def initialize_excellon(obj_init, app):
|
||||
# objs = self.collection.get_selected()
|
||||
@ -4739,15 +4789,91 @@ class App(QtCore.QObject):
|
||||
solid_geo.append(geo)
|
||||
obj_init.solid_geometry = deepcopy(solid_geo)
|
||||
|
||||
if not self.collection.get_selected():
|
||||
log.warning("App.convert_any2geo --> No object selected")
|
||||
self.inform.emit(_("[WARNING_NOTCL] No object is selected. Select an object and try again."))
|
||||
return
|
||||
|
||||
for obj in self.collection.get_selected():
|
||||
|
||||
obj_name = obj.options["name"]
|
||||
|
||||
try:
|
||||
if isinstance(obj, FlatCAMExcellon):
|
||||
self.new_object("geometry", str(obj_name) + "_gcopy", initialize_excellon)
|
||||
self.new_object("geometry", str(obj_name) + "_conv", initialize_excellon)
|
||||
else:
|
||||
self.new_object("geometry", str(obj_name) + "_gcopy", initialize)
|
||||
self.new_object("geometry", str(obj_name) + "_conv", initialize)
|
||||
|
||||
except Exception as e:
|
||||
return "Operation failed: %s" % str(e)
|
||||
|
||||
def convert_any2gerber(self):
|
||||
self.report_usage("convert_any2gerber()")
|
||||
|
||||
def initialize_geometry(obj_init, app):
|
||||
apertures = {}
|
||||
apid = 0
|
||||
|
||||
apertures[str(apid)] = {}
|
||||
apertures[str(apid)]['geometry'] = []
|
||||
for obj_orig in obj.solid_geometry:
|
||||
new_elem = dict()
|
||||
new_elem['solid'] = obj_orig
|
||||
new_elem['follow'] = obj_orig.exterior
|
||||
apertures[str(apid)]['geometry'].append(deepcopy(new_elem))
|
||||
apertures[str(apid)]['size'] = 0.0
|
||||
apertures[str(apid)]['type'] = 'C'
|
||||
|
||||
obj_init.solid_geometry = deepcopy(obj.solid_geometry)
|
||||
obj_init.apertures = deepcopy(apertures)
|
||||
|
||||
def initialize_excellon(obj_init, app):
|
||||
apertures = {}
|
||||
|
||||
apid = 10
|
||||
for tool in obj.tools:
|
||||
apertures[str(apid)] = {}
|
||||
apertures[str(apid)]['geometry'] = []
|
||||
for geo in obj.tools[tool]['solid_geometry']:
|
||||
new_el = dict()
|
||||
new_el['solid'] = geo
|
||||
new_el['follow'] = geo.exterior
|
||||
apertures[str(apid)]['geometry'].append(deepcopy(new_el))
|
||||
|
||||
apertures[str(apid)]['size'] = float(obj.tools[tool]['C'])
|
||||
apertures[str(apid)]['type'] = 'C'
|
||||
apid += 1
|
||||
|
||||
# create solid_geometry
|
||||
solid_geometry = []
|
||||
for apid in apertures:
|
||||
for geo_el in apertures[apid]['geometry']:
|
||||
solid_geometry.append(geo_el['solid'])
|
||||
|
||||
solid_geometry = MultiPolygon(solid_geometry)
|
||||
solid_geometry = solid_geometry.buffer(0.0000001)
|
||||
|
||||
obj_init.solid_geometry = deepcopy(solid_geometry)
|
||||
obj_init.apertures = deepcopy(apertures)
|
||||
# clear the working objects (perhaps not necessary due of Python GC)
|
||||
apertures.clear()
|
||||
|
||||
if not self.collection.get_selected():
|
||||
log.warning("App.convert_any2gerber --> No object selected")
|
||||
self.inform.emit(_("[WARNING_NOTCL] No object is selected. Select an object and try again."))
|
||||
return
|
||||
|
||||
for obj in self.collection.get_selected():
|
||||
|
||||
obj_name = obj.options["name"]
|
||||
|
||||
try:
|
||||
if isinstance(obj, FlatCAMExcellon):
|
||||
self.new_object("gerber", str(obj_name) + "_conv", initialize_excellon)
|
||||
elif isinstance(obj, FlatCAMGeometry):
|
||||
self.new_object("gerber", str(obj_name) + "_conv", initialize_geometry)
|
||||
else:
|
||||
log.warning("App.convert_any2gerber --> This is no vaild object for conversion.")
|
||||
|
||||
except Exception as e:
|
||||
return "Operation failed: %s" % str(e)
|
||||
@ -4776,6 +4902,7 @@ class App(QtCore.QObject):
|
||||
obj.options['ymax'] = d
|
||||
# self.plot_all(zoom=False)
|
||||
self.inform.emit(_('[success] Origin set ...'))
|
||||
self.plotcanvas.fit_view()
|
||||
self.plotcanvas.vis_disconnect('mouse_press', self.on_set_zero_click)
|
||||
self.should_we_save = True
|
||||
|
||||
@ -5818,7 +5945,7 @@ class App(QtCore.QObject):
|
||||
self.report_usage("on_fileopenexcellon")
|
||||
App.log.debug("on_fileopenexcellon()")
|
||||
|
||||
_filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc);;" \
|
||||
_filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \
|
||||
"All Files (*.*)"
|
||||
|
||||
try:
|
||||
@ -5968,6 +6095,8 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
self.export_svg(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("SVG", filename)
|
||||
self.file_saved.emit("SVG", filename)
|
||||
|
||||
def on_file_exportpng(self):
|
||||
@ -5997,6 +6126,8 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
write_png(filename, data)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("png", filename)
|
||||
self.file_saved.emit("png", filename)
|
||||
|
||||
def on_file_savegerber(self):
|
||||
@ -6036,6 +6167,8 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
self.save_source_file(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Gerber", filename)
|
||||
self.file_saved.emit("Gerber", filename)
|
||||
|
||||
def on_file_saveexcellon(self):
|
||||
@ -6075,11 +6208,13 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
self.save_source_file(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Excellon", filename)
|
||||
self.file_saved.emit("Excellon", filename)
|
||||
|
||||
def on_file_exportexcellon(self):
|
||||
"""
|
||||
Callback for menu item File->Export SVG.
|
||||
Callback for menu item File->Export->Excellon.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
@ -6114,8 +6249,51 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
self.export_excellon(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Excellon", filename)
|
||||
self.file_saved.emit("Excellon", filename)
|
||||
|
||||
def on_file_exportgerber(self):
|
||||
"""
|
||||
Callback for menu item File->Export->Gerber.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.report_usage("on_file_exportgerber")
|
||||
App.log.debug("on_file_exportgerber()")
|
||||
|
||||
obj = self.collection.get_active()
|
||||
if obj is None:
|
||||
self.inform.emit(_("[WARNING_NOTCL] No object selected. Please Select an Gerber object to export."))
|
||||
return
|
||||
|
||||
# Check for more compatible types and add as required
|
||||
if not isinstance(obj, FlatCAMGerber):
|
||||
self.inform.emit(_("[ERROR_NOTCL] Failed. Only Gerber objects can be saved as Gerber files..."))
|
||||
return
|
||||
|
||||
name = self.collection.get_active().options["name"]
|
||||
|
||||
filter = "Gerber File (*.GBR);;All Files (*.*)"
|
||||
try:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export Gerber"),
|
||||
directory=self.get_last_save_folder() + '/' + name,
|
||||
filter=filter)
|
||||
except TypeError:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Gerber"), filter=filter)
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if filename == "":
|
||||
self.inform.emit(_("[WARNING_NOTCL] Export Gerber cancelled."))
|
||||
return
|
||||
else:
|
||||
self.export_gerber(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Gerber", filename)
|
||||
self.file_saved.emit("Gerber", filename)
|
||||
|
||||
def on_file_exportdxf(self):
|
||||
"""
|
||||
Callback for menu item File->Export DXF.
|
||||
@ -6165,6 +6343,8 @@ class App(QtCore.QObject):
|
||||
return
|
||||
else:
|
||||
self.export_dxf(name, filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("DXF", filename)
|
||||
self.file_saved.emit("DXF", filename)
|
||||
|
||||
def on_file_importsvg(self, type_of_obj):
|
||||
@ -6419,8 +6599,8 @@ class App(QtCore.QObject):
|
||||
else:
|
||||
self.worker_task.emit({'fcn': self.save_project,
|
||||
'params': [self.project_filename]})
|
||||
|
||||
self.file_opened.emit("project", self.project_filename)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("project", self.project_filename)
|
||||
self.file_saved.emit("project", self.project_filename)
|
||||
|
||||
self.should_we_save = False
|
||||
@ -6465,8 +6645,8 @@ class App(QtCore.QObject):
|
||||
self.save_project(filename, quit)
|
||||
|
||||
# self.save_project(filename)
|
||||
self.file_opened.emit("project", filename)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("project", filename)
|
||||
self.file_saved.emit("project", filename)
|
||||
if not make_copy:
|
||||
self.project_filename = filename
|
||||
@ -6524,7 +6704,8 @@ class App(QtCore.QObject):
|
||||
svgcode = parse_xml_string(svg_elem)
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(svgcode.toprettyxml())
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("SVG", filename)
|
||||
self.file_saved.emit("SVG", filename)
|
||||
self.inform.emit(_("[success] SVG file exported to %s") % filename)
|
||||
|
||||
@ -6629,7 +6810,8 @@ class App(QtCore.QObject):
|
||||
fp.write(doc.toprettyxml())
|
||||
|
||||
self.progress.emit(100)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("SVG", filename)
|
||||
self.file_saved.emit("SVG", filename)
|
||||
self.inform.emit(_("[success] SVG file exported to %s") % filename)
|
||||
|
||||
@ -6743,7 +6925,8 @@ class App(QtCore.QObject):
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(doc.toprettyxml())
|
||||
self.progress.emit(100)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("SVG", filename)
|
||||
self.file_saved.emit("SVG", filename)
|
||||
self.inform.emit(_("[success] SVG file exported to %s") % filename)
|
||||
|
||||
@ -6893,7 +7076,8 @@ class App(QtCore.QObject):
|
||||
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(exported_excellon)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Excellon", filename)
|
||||
self.file_saved.emit("Excellon", filename)
|
||||
self.inform.emit(_("[success] Excellon file exported to %s") % filename)
|
||||
except Exception as e:
|
||||
@ -6917,6 +7101,123 @@ class App(QtCore.QObject):
|
||||
self.inform.emit(_('[ERROR_NOTCL] Could not export Excellon file.'))
|
||||
return
|
||||
|
||||
def export_gerber(self, obj_name, filename, use_thread=True):
|
||||
"""
|
||||
Exports a Gerber Object to an Gerber file.
|
||||
|
||||
:param filename: Path to the Gerber file to save to.
|
||||
:return:
|
||||
"""
|
||||
self.report_usage("export_gerber()")
|
||||
|
||||
if filename is None:
|
||||
filename = self.defaults["global_last_save_folder"]
|
||||
|
||||
self.log.debug("export_gerber()")
|
||||
|
||||
try:
|
||||
obj = self.collection.get_by_name(str(obj_name))
|
||||
except:
|
||||
# TODO: The return behavior has not been established... should raise exception?
|
||||
return "Could not retrieve object: %s" % obj_name
|
||||
|
||||
# updated units
|
||||
gunits = self.defaults["gerber_exp_units"]
|
||||
gwhole = self.defaults["gerber_exp_integer"]
|
||||
gfract = self.defaults["gerber_exp_decimals"]
|
||||
gzeros = self.defaults["gerber_exp_zeros"]
|
||||
|
||||
fc_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
if fc_units == 'MM':
|
||||
factor = 1 if gunits == 'MM' else 0.03937
|
||||
else:
|
||||
factor = 25.4 if gunits == 'MM' else 1
|
||||
|
||||
def make_gerber():
|
||||
try:
|
||||
time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
|
||||
|
||||
header = 'G04*\n'
|
||||
header += 'G04 RS-274X GERBER GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % \
|
||||
(str(self.version), str(self.version_date))
|
||||
|
||||
header += 'G04 Filename: %s*' % str(obj_name) + '\n'
|
||||
header += 'G04 Created on : %s*' % time_str + '\n'
|
||||
header += '%%FS%sAX%s%sY%s%s*%%\n' % (gzeros, gwhole, gfract, gwhole, gfract)
|
||||
header += "%MO{units}*%\n".format(units=gunits)
|
||||
|
||||
for apid in obj.apertures:
|
||||
if obj.apertures[apid]['type'] == 'C':
|
||||
header += "%ADD{apid}{type},{size}*%\n".format(
|
||||
apid=str(apid),
|
||||
type='C',
|
||||
size=(factor * obj.apertures[apid]['size'])
|
||||
)
|
||||
elif obj.apertures[apid]['type'] == 'R':
|
||||
header += "%ADD{apid}{type},{width}X{height}*%\n".format(
|
||||
apid=str(apid),
|
||||
type='R',
|
||||
width=(factor * obj.apertures[apid]['width']),
|
||||
height=(factor * obj.apertures[apid]['height'])
|
||||
)
|
||||
elif obj.apertures[apid]['type'] == 'O':
|
||||
header += "%ADD{apid}{type},{width}X{height}*%\n".format(
|
||||
apid=str(apid),
|
||||
type='O',
|
||||
width=(factor * obj.apertures[apid]['width']),
|
||||
height=(factor * obj.apertures[apid]['height'])
|
||||
)
|
||||
|
||||
header += '\n'
|
||||
|
||||
# obsolete units but some software may need it
|
||||
if gunits == 'IN':
|
||||
header += 'G70*\n'
|
||||
else:
|
||||
header += 'G71*\n'
|
||||
|
||||
# Absolute Mode
|
||||
header += 'G90*\n'
|
||||
|
||||
header += 'G01*\n'
|
||||
# positive polarity
|
||||
header += '%LPD*%\n'
|
||||
|
||||
footer = 'M02*\n'
|
||||
|
||||
gerber_code = obj.export_gerber(gwhole, gfract, g_zeros=gzeros, factor=factor)
|
||||
|
||||
exported_gerber = header
|
||||
exported_gerber += gerber_code
|
||||
exported_gerber += footer
|
||||
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(exported_gerber)
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("Gerber", filename)
|
||||
self.file_saved.emit("Gerber", filename)
|
||||
self.inform.emit(_("[success] Gerber file exported to %s") % filename)
|
||||
except Exception as e:
|
||||
log.debug("App.export_gerber.make_gerber() --> %s" % str(e))
|
||||
return 'fail'
|
||||
|
||||
if use_thread is True:
|
||||
|
||||
with self.proc_container.new(_("Exporting Gerber")) as proc:
|
||||
|
||||
def job_thread_exc(app_obj):
|
||||
ret = make_gerber()
|
||||
if ret == 'fail':
|
||||
self.inform.emit(_('[ERROR_NOTCL] Could not export Gerber file.'))
|
||||
return
|
||||
|
||||
self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]})
|
||||
else:
|
||||
ret = make_gerber()
|
||||
if ret == 'fail':
|
||||
self.inform.emit(_('[ERROR_NOTCL] Could not export Gerber file.'))
|
||||
return
|
||||
|
||||
def export_dxf(self, obj_name, filename, use_thread=True):
|
||||
"""
|
||||
Exports a Geometry Object to an DXF file.
|
||||
@ -6951,7 +7252,8 @@ class App(QtCore.QObject):
|
||||
try:
|
||||
dxf_code = obj.export_dxf()
|
||||
dxf_code.saveas(filename)
|
||||
|
||||
if self.defaults["global_open_style"] is False:
|
||||
self.file_opened.emit("DXF", filename)
|
||||
self.file_saved.emit("DXF", filename)
|
||||
self.inform.emit(_("[success] DXF file exported to %s") % filename)
|
||||
except:
|
||||
|
478
FlatCAMObj.py
478
FlatCAMObj.py
@ -17,9 +17,9 @@ import itertools
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
import builtins
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
@ -35,9 +35,9 @@ class ValidationError(Exception):
|
||||
|
||||
self.errors = errors
|
||||
|
||||
########################################
|
||||
## FlatCAMObj ##
|
||||
########################################
|
||||
# #######################################
|
||||
# # FlatCAMObj ##
|
||||
# #######################################
|
||||
|
||||
|
||||
class FlatCAMObj(QtCore.QObject):
|
||||
@ -73,14 +73,19 @@ class FlatCAMObj(QtCore.QObject):
|
||||
# self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene)
|
||||
self.shapes = self.app.plotcanvas.new_shape_group()
|
||||
|
||||
self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
|
||||
# self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
|
||||
self.mark_shapes = {}
|
||||
|
||||
self.item = None # Link with project view item
|
||||
|
||||
self.muted_ui = False
|
||||
self.deleted = False
|
||||
|
||||
self._drawing_tolerance = 0.01
|
||||
try:
|
||||
self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \
|
||||
self.app.defaults["global_tolerance"] else 0.01
|
||||
except ValueError:
|
||||
self._drawing_tolerance = 0.01
|
||||
|
||||
self.isHovering = False
|
||||
self.notHovering = True
|
||||
@ -117,7 +122,8 @@ class FlatCAMObj(QtCore.QObject):
|
||||
try:
|
||||
setattr(self, attr, d[attr])
|
||||
except KeyError:
|
||||
log.debug("FlatCAMObj.from_dict() --> KeyError: %s. Means that we are loading an old project that don't"
|
||||
log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
|
||||
"Means that we are loading an old project that don't"
|
||||
"have all attributes in the latest FlatCAM." % str(attr))
|
||||
pass
|
||||
|
||||
@ -198,8 +204,8 @@ class FlatCAMObj(QtCore.QObject):
|
||||
self.app.report_usage("obj_on_offset_button")
|
||||
|
||||
self.read_form()
|
||||
vect = self.ui.offsetvector_entry.get_value()
|
||||
self.offset(vect)
|
||||
vector_val = self.ui.offsetvector_entry.get_value()
|
||||
self.offset(vector_val)
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
|
||||
@ -214,9 +220,9 @@ class FlatCAMObj(QtCore.QObject):
|
||||
def on_skew_button_click(self):
|
||||
self.app.report_usage("obj_on_skew_button")
|
||||
self.read_form()
|
||||
xangle = self.ui.xangle_entry.get_value()
|
||||
yangle = self.ui.yangle_entry.get_value()
|
||||
self.skew(xangle, yangle)
|
||||
x_angle = self.ui.xangle_entry.get_value()
|
||||
y_angle = self.ui.yangle_entry.get_value()
|
||||
self.skew(x_angle, y_angle)
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
|
||||
@ -321,11 +327,11 @@ class FlatCAMObj(QtCore.QObject):
|
||||
key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
|
||||
return key
|
||||
|
||||
def add_mark_shape(self, **kwargs):
|
||||
def add_mark_shape(self, apid, **kwargs):
|
||||
if self.deleted:
|
||||
raise ObjectDeleted()
|
||||
else:
|
||||
key = self.mark_shapes.add(tolerance=self.drawing_tolerance, **kwargs)
|
||||
key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, **kwargs)
|
||||
return key
|
||||
|
||||
@property
|
||||
@ -415,7 +421,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
if option is not 'name':
|
||||
try:
|
||||
grb_final.options[option] = grb.options[option]
|
||||
except:
|
||||
except KeyError:
|
||||
log.warning("Failed to copy option.", option)
|
||||
|
||||
try:
|
||||
@ -435,10 +441,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
# and finally made string because the apertures dict keys are strings
|
||||
max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1)
|
||||
grb_final.apertures[max_ap] = {}
|
||||
grb_final.apertures[max_ap]['solid_geometry'] = []
|
||||
grb_final.apertures[max_ap]['geometry'] = []
|
||||
|
||||
for k, v in grb.apertures[ap].items():
|
||||
grb_final.apertures[max_ap][k] = v
|
||||
grb_final.apertures[max_ap][k] = deepcopy(v)
|
||||
|
||||
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
|
||||
grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
|
||||
@ -555,6 +561,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
))
|
||||
self.ui.padding_area_label.hide()
|
||||
|
||||
# add the shapes storage for marking apertures
|
||||
for ap_code in self.apertures:
|
||||
self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=2)
|
||||
|
||||
# set initial state of the aperture table and associated widgets
|
||||
self.on_aperture_table_visibility_change()
|
||||
|
||||
@ -576,12 +586,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
sort.append(int(k))
|
||||
sorted_apertures = sorted(sort)
|
||||
|
||||
sort = []
|
||||
for k, v in list(self.aperture_macros.items()):
|
||||
sort.append(k)
|
||||
sorted_macros = sorted(sort)
|
||||
# sort = []
|
||||
# for k, v in list(self.aperture_macros.items()):
|
||||
# sort.append(k)
|
||||
# sorted_macros = sorted(sort)
|
||||
|
||||
n = len(sorted_apertures) + len(sorted_macros)
|
||||
# n = len(sorted_apertures) + len(sorted_macros)
|
||||
n = len(sorted_apertures)
|
||||
self.ui.apertures_table.setRowCount(n)
|
||||
|
||||
for ap_code in sorted_apertures:
|
||||
@ -639,28 +650,28 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
|
||||
self.apertures_row += 1
|
||||
|
||||
for ap_code in sorted_macros:
|
||||
ap_code = str(ap_code)
|
||||
|
||||
ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1))
|
||||
ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item) # Tool name/id
|
||||
|
||||
ap_code_item = QtWidgets.QTableWidgetItem(ap_code)
|
||||
|
||||
ap_type_item = QtWidgets.QTableWidgetItem('AM')
|
||||
ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
mark_item = FCCheckBox()
|
||||
mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
# if self.ui.aperture_table_visibility_cb.isChecked():
|
||||
# mark_item.setChecked(True)
|
||||
|
||||
self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item) # Aperture Code
|
||||
self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item) # Aperture Type
|
||||
self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
|
||||
|
||||
self.apertures_row += 1
|
||||
# for ap_code in sorted_macros:
|
||||
# ap_code = str(ap_code)
|
||||
#
|
||||
# ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1))
|
||||
# ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
# self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item) # Tool name/id
|
||||
#
|
||||
# ap_code_item = QtWidgets.QTableWidgetItem(ap_code)
|
||||
#
|
||||
# ap_type_item = QtWidgets.QTableWidgetItem('AM')
|
||||
# ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
#
|
||||
# mark_item = FCCheckBox()
|
||||
# mark_item.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
# # if self.ui.aperture_table_visibility_cb.isChecked():
|
||||
# # mark_item.setChecked(True)
|
||||
#
|
||||
# self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item) # Aperture Code
|
||||
# self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item) # Aperture Type
|
||||
# self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item)
|
||||
#
|
||||
# self.apertures_row += 1
|
||||
|
||||
self.ui.apertures_table.selectColumn(0)
|
||||
self.ui.apertures_table.resizeColumnsToContents()
|
||||
@ -675,7 +686,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
horizontal_header.setMinimumSectionSize(10)
|
||||
horizontal_header.setDefaultSectionSize(70)
|
||||
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
||||
horizontal_header.resizeSection(0, 20)
|
||||
horizontal_header.resizeSection(0, 27)
|
||||
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||
horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||
horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
|
||||
@ -692,7 +703,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
# update the 'mark' checkboxes state according with what is stored in the self.marked_rows list
|
||||
if self.marked_rows:
|
||||
for row in range(self.ui.apertures_table.rowCount()):
|
||||
self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row])
|
||||
try:
|
||||
self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row])
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
self.ui_connect()
|
||||
|
||||
@ -999,6 +1013,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
def on_aperture_table_visibility_change(self):
|
||||
if self.ui.aperture_table_visibility_cb.isChecked():
|
||||
self.ui.apertures_table.setVisible(True)
|
||||
for ap in self.mark_shapes:
|
||||
self.mark_shapes[ap].enabled = True
|
||||
|
||||
self.ui.mark_all_cb.setVisible(True)
|
||||
self.ui.mark_all_cb.setChecked(False)
|
||||
@ -1012,6 +1028,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
self.ui.apertures_table.cellWidget(row, 5).set_value(False)
|
||||
self.clear_plot_apertures()
|
||||
|
||||
for ap in self.mark_shapes:
|
||||
self.mark_shapes[ap].enabled = False
|
||||
|
||||
def convert_units(self, units):
|
||||
"""
|
||||
Converts the units of the object by scaling dimensions in all geometry
|
||||
@ -1078,8 +1097,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
elif type(g) == Point:
|
||||
pass
|
||||
else:
|
||||
for el in g:
|
||||
self.add_shape(shape=el, color=color,
|
||||
try:
|
||||
for el in g:
|
||||
self.add_shape(shape=el, color=color,
|
||||
face_color=random_color() if self.options['multicolored']
|
||||
else face_color, visible=self.options['plot'])
|
||||
except TypeError:
|
||||
self.add_shape(shape=g, color=color,
|
||||
face_color=random_color() if self.options['multicolored']
|
||||
else face_color, visible=self.options['plot'])
|
||||
else:
|
||||
@ -1098,14 +1122,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
self.shapes.clear(update=True)
|
||||
|
||||
# experimental plot() when the solid_geometry is stored in the self.apertures
|
||||
def plot_apertures(self, **kwargs):
|
||||
def plot_aperture(self, **kwargs):
|
||||
"""
|
||||
|
||||
:param kwargs: color and face_color
|
||||
:return:
|
||||
"""
|
||||
|
||||
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_apertures()")
|
||||
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_aperture()")
|
||||
|
||||
# Does all the required setup and returns False
|
||||
# if the 'ptint' option is set to False.
|
||||
@ -1135,20 +1159,20 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
|
||||
def job_thread(app_obj):
|
||||
self.app.progress.emit(30)
|
||||
|
||||
try:
|
||||
if aperture_to_plot_mark in self.apertures:
|
||||
if type(self.apertures[aperture_to_plot_mark]['solid_geometry']) is not list:
|
||||
self.apertures[aperture_to_plot_mark]['solid_geometry'] = \
|
||||
[self.apertures[aperture_to_plot_mark]['solid_geometry']]
|
||||
for geo in self.apertures[aperture_to_plot_mark]['solid_geometry']:
|
||||
if type(geo) == Polygon or type(geo) == LineString:
|
||||
self.add_mark_shape(shape=geo, color=color, face_color=color, visible=visibility)
|
||||
else:
|
||||
for el in geo:
|
||||
self.add_mark_shape(shape=el, color=color, face_color=color, visible=visibility)
|
||||
for elem in self.apertures[aperture_to_plot_mark]['geometry']:
|
||||
if 'solid' in elem:
|
||||
geo = elem['solid']
|
||||
if type(geo) == Polygon or type(geo) == LineString:
|
||||
self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
|
||||
face_color=color, visible=visibility)
|
||||
else:
|
||||
for el in geo:
|
||||
self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
|
||||
face_color=color, visible=visibility)
|
||||
|
||||
self.mark_shapes.redraw()
|
||||
self.mark_shapes[aperture_to_plot_mark].redraw()
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except (ObjectDeleted, AttributeError):
|
||||
@ -1156,34 +1180,47 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
|
||||
|
||||
def clear_plot_apertures(self):
|
||||
self.mark_shapes.clear(update=True)
|
||||
def clear_plot_apertures(self, aperture='all'):
|
||||
"""
|
||||
|
||||
:param aperture: string; aperture for which to clear the mark shapes
|
||||
:return:
|
||||
"""
|
||||
if aperture == 'all':
|
||||
for apid in self.apertures:
|
||||
self.mark_shapes[apid].clear(update=True)
|
||||
else:
|
||||
self.mark_shapes[aperture].clear(update=True)
|
||||
|
||||
def clear_mark_all(self):
|
||||
self.ui.mark_all_cb.set_value(False)
|
||||
self.marked_rows[:] = []
|
||||
|
||||
def on_mark_cb_click_table(self):
|
||||
"""
|
||||
Will mark aperture geometries on canvas or delete the markings depending on the checkbox state
|
||||
:return:
|
||||
"""
|
||||
|
||||
self.ui_disconnect()
|
||||
# cw = self.sender()
|
||||
# cw_index = self.ui.apertures_table.indexAt(cw.pos())
|
||||
# cw_row = cw_index.row()
|
||||
check_row = 0
|
||||
cw = self.sender()
|
||||
try:
|
||||
cw_index = self.ui.apertures_table.indexAt(cw.pos())
|
||||
cw_row = cw_index.row()
|
||||
except AttributeError:
|
||||
cw_row = 0
|
||||
|
||||
self.clear_plot_apertures()
|
||||
self.marked_rows[:] = []
|
||||
aperture = self.ui.apertures_table.item(cw_row, 1).text()
|
||||
|
||||
for row in range(self.ui.apertures_table.rowCount()):
|
||||
if self.ui.apertures_table.cellWidget(row, 5).isChecked():
|
||||
self.marked_rows.append(True)
|
||||
|
||||
aperture = self.ui.apertures_table.item(row, 1).text()
|
||||
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
|
||||
self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
|
||||
else:
|
||||
self.marked_rows.append(False)
|
||||
|
||||
self.mark_shapes.redraw()
|
||||
if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked():
|
||||
self.marked_rows.append(True)
|
||||
# self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
|
||||
self.plot_aperture(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
|
||||
self.mark_shapes[aperture].redraw()
|
||||
else:
|
||||
self.marked_rows.append(False)
|
||||
self.clear_plot_apertures(aperture=aperture)
|
||||
|
||||
# make sure that the Mark All is disabled if one of the row mark's are disabled and
|
||||
# if all the row mark's are enabled also enable the Mark All checkbox
|
||||
@ -1215,13 +1252,261 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||
|
||||
if mark_all:
|
||||
for aperture in self.apertures:
|
||||
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
|
||||
self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
|
||||
# self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
|
||||
self.plot_aperture(color=self.app.defaults['global_sel_draw_color'],
|
||||
marked_aperture=aperture, visible=True)
|
||||
# HACK: enable/disable the grid for a better look
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
else:
|
||||
self.clear_plot_apertures()
|
||||
|
||||
self.ui_connect()
|
||||
|
||||
def export_gerber(self, whole, fract, g_zeros='L', factor=1):
|
||||
"""
|
||||
|
||||
:return: Gerber_code
|
||||
"""
|
||||
|
||||
def tz_format(x, y ,fac):
|
||||
x_c = x * fac
|
||||
y_c = y * fac
|
||||
|
||||
x_form = "{:.{dec}f}".format(x_c, dec=fract)
|
||||
y_form = "{:.{dec}f}".format(y_c, dec=fract)
|
||||
|
||||
# extract whole part and decimal part
|
||||
x_form = x_form.partition('.')
|
||||
y_form = y_form.partition('.')
|
||||
|
||||
# left padd the 'whole' part with zeros
|
||||
x_whole = x_form[0].rjust(whole, '0')
|
||||
y_whole = y_form[0].rjust(whole, '0')
|
||||
|
||||
# restore the coordinate padded in the left with 0 and added the decimal part
|
||||
# without the decinal dot
|
||||
x_form = x_whole + x_form[2]
|
||||
y_form = y_whole + y_form[2]
|
||||
return x_form, y_form
|
||||
|
||||
def lz_format(x, y, fac):
|
||||
x_c = x * fac
|
||||
y_c = y * fac
|
||||
|
||||
x_form = "{:.{dec}f}".format(x_c, dec=fract).replace('.', '')
|
||||
y_form = "{:.{dec}f}".format(y_c, dec=fract).replace('.', '')
|
||||
|
||||
# pad with rear zeros
|
||||
x_form.ljust(length, '0')
|
||||
y_form.ljust(length, '0')
|
||||
|
||||
return x_form, y_form
|
||||
|
||||
# Gerber code is stored here
|
||||
gerber_code = ''
|
||||
|
||||
# apertures processing
|
||||
try:
|
||||
length = whole + fract
|
||||
if '0' in self.apertures:
|
||||
if 'geometry' in self.apertures['0']:
|
||||
for geo_elem in self.apertures['0']['geometry']:
|
||||
if 'solid' in geo_elem:
|
||||
geo = geo_elem['solid']
|
||||
if not geo.is_empty:
|
||||
gerber_code += 'G36*\n'
|
||||
geo_coords = list(geo.exterior.coords)
|
||||
# first command is a move with pen-up D02 at the beginning of the geo
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
for coord in geo_coords[1:]:
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
gerber_code += 'D02*\n'
|
||||
gerber_code += 'G37*\n'
|
||||
|
||||
clear_list = list(geo.interiors)
|
||||
if clear_list:
|
||||
gerber_code += '%LPC*%\n'
|
||||
for clear_geo in clear_list:
|
||||
gerber_code += 'G36*\n'
|
||||
geo_coords = list(clear_geo.coords)
|
||||
|
||||
# first command is a move with pen-up D02 at the beginning of the geo
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
|
||||
prev_coord = geo_coords[0]
|
||||
for coord in geo_coords[1:]:
|
||||
if coord != prev_coord:
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
prev_coord = coord
|
||||
|
||||
gerber_code += 'D02*\n'
|
||||
gerber_code += 'G37*\n'
|
||||
gerber_code += '%LPD*%\n'
|
||||
if 'clear' in geo_elem:
|
||||
geo = geo_elem['clear']
|
||||
if not geo.is_empty:
|
||||
gerber_code += '%LPC*%\n'
|
||||
gerber_code += 'G36*\n'
|
||||
geo_coords = list(geo.exterior.coords)
|
||||
# first command is a move with pen-up D02 at the beginning of the geo
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
|
||||
prev_coord = geo_coords[0]
|
||||
for coord in geo_coords[1:]:
|
||||
if coord != prev_coord:
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
prev_coord = coord
|
||||
|
||||
gerber_code += 'D02*\n'
|
||||
gerber_code += 'G37*\n'
|
||||
gerber_code += '%LPD*%\n'
|
||||
|
||||
for apid in self.apertures:
|
||||
if apid == '0':
|
||||
continue
|
||||
else:
|
||||
gerber_code += 'D%s*\n' % str(apid)
|
||||
if 'geometry' in self.apertures[apid]:
|
||||
for geo_elem in self.apertures[apid]['geometry']:
|
||||
if 'follow' in geo_elem:
|
||||
geo = geo_elem['follow']
|
||||
if not geo.is_empty:
|
||||
if isinstance(geo, Point):
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
|
||||
gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
|
||||
gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
geo_coords = list(geo.coords)
|
||||
# first command is a move with pen-up D02 at the beginning of the geo
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
|
||||
prev_coord = geo_coords[0]
|
||||
for coord in geo_coords[1:]:
|
||||
if coord != prev_coord:
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
prev_coord = coord
|
||||
|
||||
# gerber_code += "D02*\n"
|
||||
|
||||
if 'clear' in geo_elem:
|
||||
gerber_code += '%LPC*%\n'
|
||||
|
||||
geo = geo_elem['clear']
|
||||
if not geo.is_empty:
|
||||
if isinstance(geo, Point):
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(geo.x, geo.y, factor)
|
||||
gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(geo.x, geo.y, factor)
|
||||
gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
geo_coords = list(geo.coords)
|
||||
# first command is a move with pen-up D02 at the beginning of the geo
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(
|
||||
geo_coords[0][0], geo_coords[0][1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
|
||||
prev_coord = geo_coords[0]
|
||||
for coord in geo_coords[1:]:
|
||||
if coord != prev_coord:
|
||||
if g_zeros == 'T':
|
||||
x_formatted, y_formatted = tz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
else:
|
||||
x_formatted, y_formatted = lz_format(coord[0], coord[1], factor)
|
||||
gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted,
|
||||
yform=y_formatted)
|
||||
|
||||
prev_coord = coord
|
||||
# gerber_code += "D02*\n"
|
||||
gerber_code += '%LPD*%\n'
|
||||
|
||||
except Exception as e:
|
||||
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> %s" % str(e))
|
||||
|
||||
if not self.apertures:
|
||||
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> Gerber Object is empty: no apertures.")
|
||||
return 'fail'
|
||||
|
||||
return gerber_code
|
||||
|
||||
def mirror(self, axis, point):
|
||||
Gerber.mirror(self, axis=axis, point=point)
|
||||
self.replotApertures.emit()
|
||||
@ -2232,6 +2517,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
||||
self.ui.feedrate_probe_entry.setVisible(False)
|
||||
self.ui.feedrate_probe_label.hide()
|
||||
|
||||
if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
|
||||
self.ui.feedrate_rapid_label.show()
|
||||
self.ui.feedrate_rapid_entry.show()
|
||||
else:
|
||||
self.ui.feedrate_rapid_label.hide()
|
||||
self.ui.feedrate_rapid_entry.hide()
|
||||
|
||||
def on_create_cncjob_button_click(self, *args):
|
||||
self.app.report_usage("excellon_on_create_cncjob_button")
|
||||
self.read_form()
|
||||
@ -2285,8 +2577,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
||||
job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
|
||||
|
||||
job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None
|
||||
job_obj.spindledir = self.app.defaults['excellon_spindledir']
|
||||
job_obj.dwell = self.options["dwell"]
|
||||
job_obj.dwelltime = float(self.options["dwelltime"])
|
||||
|
||||
job_obj.pp_excellon_name = pp_excellon_name
|
||||
|
||||
job_obj.toolchange_xy_type = "excellon"
|
||||
@ -3876,6 +4170,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
self.ui.feedrate_probe_entry.setVisible(False)
|
||||
self.ui.feedrate_probe_label.hide()
|
||||
|
||||
if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower():
|
||||
self.ui.fr_rapidlabel.show()
|
||||
self.ui.cncfeedrate_rapid_entry.show()
|
||||
else:
|
||||
self.ui.fr_rapidlabel.hide()
|
||||
self.ui.cncfeedrate_rapid_entry.hide()
|
||||
|
||||
def on_generatecnc_button_click(self, *args):
|
||||
|
||||
self.app.report_usage("geometry_on_generatecnc_button")
|
||||
@ -4133,6 +4434,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
'offset_value': tool_offset
|
||||
})
|
||||
|
||||
spindledir = self.app.defaults['geometry_spindledir']
|
||||
|
||||
job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
|
||||
job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
|
||||
|
||||
@ -4152,7 +4455,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
self, tooldia=tooldia_val, offset=tool_offset, tolerance=0.0005,
|
||||
z_cut=z_cut, z_move=z_move,
|
||||
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
|
||||
spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
|
||||
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
|
||||
multidepth=multidepth, depthpercut=depthpercut,
|
||||
extracut=extracut, startz=startz, endz=endz,
|
||||
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
|
||||
@ -4376,12 +4679,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||
|
||||
app_obj.progress.emit(40)
|
||||
|
||||
spindledir = self.app.defaults['geometry_spindledir']
|
||||
|
||||
tool_solid_geometry = self.tools[current_uid]['solid_geometry']
|
||||
res = job_obj.generate_from_multitool_geometry(
|
||||
tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset,
|
||||
tolerance=0.0005, z_cut=z_cut, z_move=z_move,
|
||||
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
|
||||
spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
|
||||
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
|
||||
multidepth=multidepth, depthpercut=depthpercut,
|
||||
extracut=extracut, startz=startz, endz=endz,
|
||||
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
|
||||
@ -5251,6 +5556,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
||||
if gc == 'fail':
|
||||
return
|
||||
|
||||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("gcode", filename)
|
||||
self.app.file_saved.emit("gcode", filename)
|
||||
self.app.inform.emit(_("[success] Machine Code file saved to: %s") % filename)
|
||||
|
||||
@ -5470,7 +5777,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
||||
# lines = StringIO(self.gcode)
|
||||
lines = StringIO(g)
|
||||
|
||||
## Write
|
||||
# Write
|
||||
if filename is not None:
|
||||
try:
|
||||
with open(filename, 'w') as f:
|
||||
@ -5484,7 +5791,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
||||
return
|
||||
elif to_file is False:
|
||||
# Just for adding it to the recent files list.
|
||||
self.app.file_opened.emit("cncjob", filename)
|
||||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("cncjob", filename)
|
||||
self.app.file_saved.emit("cncjob", filename)
|
||||
|
||||
self.app.inform.emit("[success] Saved to: " + filename)
|
||||
|
136
README.md
136
README.md
@ -9,6 +9,138 @@ CAD program, and create G-Code for Isolation routing.
|
||||
|
||||
=================================================
|
||||
|
||||
22.05.2019
|
||||
|
||||
- Geo Editor - added a new editor tool, Eraser
|
||||
- some PEP8 cleanup of the Geo Editor
|
||||
- fixed some selection issues in the new tool Eraser in Geometry Editor
|
||||
- updated the translation files
|
||||
|
||||
21.05.2019
|
||||
|
||||
- added the file extension .ncd to the Excellon file extension list
|
||||
- solved parsing issue for Excellon files generated by older Eagle versions (v6.x)
|
||||
- Gerber Editor: finished a new tool: Eraser. It will erase certain parts of Gerber geometries having the shape of a selected shape.
|
||||
|
||||
20.05.2019
|
||||
|
||||
- more PEP8 changes in Gerber editor
|
||||
- Gerber Editor - started to work on a new editor tool: Eraser
|
||||
|
||||
19.05.2019
|
||||
|
||||
- fixed the Circle Steps parameter for both Gerber and Geometry objects not being applied and instead the app internal defaults were used.
|
||||
- fixed the Tcl command Geocutout issue that gave an error when using the 4 or 8 value for gaps parameter
|
||||
- made wider the '#' column for Apertures Table for Gerber Object and for Gerber Editor; in this way numbers with 3 digits can be seen
|
||||
- PEP8 corrections in FlatCAMGrbEditor.py
|
||||
- added a selection limit parameter for Geometry Editor
|
||||
- added entries in Edit -> Preferences for the new parameter Selection limit for both the Gerber and Geometry Editors.
|
||||
- set the buttons in the lower part of the Preferences Window to have a preferred minimum width instead of fixed width
|
||||
- updated the translation files
|
||||
|
||||
18.05.2019
|
||||
|
||||
- added a new toggle option in Edit -> Preferences -> General Tab -> App Preferences -> "Open" Behavior. It controls which path is used when opening a new file. If checked the last saved path is used when saving files and the last opened path is used when opening files. If unchecked then the path for the last action (either open or save) is used.
|
||||
- fixed App.convert_any2gerber to work with the new Gerber apertures data structure
|
||||
- fixed Tool Sub to work with the new Gerber apertures data structure
|
||||
- fixed Tool PDF to work with the new Gerber apertures data structure
|
||||
|
||||
17.05.2019
|
||||
|
||||
- remade the Tool Cutout to work on panels
|
||||
- remade the Tool Cutout such that on multiple applications on the same object it will yield the same result
|
||||
- fixed an issue in the remade Cutout Tool where when applied on a single Gerber object, the Freeform Cutout produced no cutout Geometry object
|
||||
- remade the Properties Tool such that it works with the new Gerber data structure in the obj.apertures. Also changed the view for the Gerber object in Properties
|
||||
- fixed issue with false warning that the Gerber object has no geometry after an empty Gerber was edited and added geometry elements
|
||||
|
||||
16.05.2019
|
||||
|
||||
- Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy
|
||||
- added a new parameter/feature: now the spindle can work in clockwise mode (CW) or counter clockwise mode (CCW)
|
||||
|
||||
15.05.2019
|
||||
|
||||
- rewrited the Gerber Parser in camlib - success
|
||||
- moved the self.apertures[aperture]['geometry'] processing for clear_geometry (geometry made with Gerber LPC command) in Gerber Editor
|
||||
- Gerber Editor: fixed the Poligonize Tool to work with new geometric structure and took care of a special case
|
||||
- Gerber Export is fixed to work with the new Gerber object data structure and it now works also for Gerber objects edited in Gerber Editor
|
||||
- Gerber Editor: fixed units conversion for obj.apertures keys that require it
|
||||
- camlib Gerber parser - made sure that we don't loose goemetry in regions
|
||||
- Gerber Editor - made sure that for some tools the added geometry is clean (the coordinates are non repeating)
|
||||
- covered some possible issues in Gerber Export
|
||||
|
||||
12.05.2019
|
||||
|
||||
- some modifications to ToolCutout
|
||||
|
||||
11.05.2019
|
||||
|
||||
- fixed issue in camlib.CNCjob.generate_from_excellon_by_tool() in the drill path optimization algorithm selection when selecting the MH algorithm. The new API's for Google OR-tools required some changes and also the time parameter can be now just an integer therefore I modified the GUI
|
||||
- made the Feedrate Rapids parameter to depend on the type of postprocessor choosed. It will be showed only for a postprocessor which the name contain 'marlin' and for any postprocessor's that have 'custom' in the name
|
||||
- fixed the camlib.Gerber functions of mirror, scale, offset, skew and rotate to work with the new data structure for apertures geometry
|
||||
- fixed Gerber Editor selection to work with the new Gerber data structure in self.apertures
|
||||
- fixed Gerber Editor FCPad class to work with the new Gerber data structure in self.apertures
|
||||
- fixed camlib.Gerber issues related to what happen after parsing rectangular apertures
|
||||
- wip in camblib.Gerber
|
||||
- completely converted the Gerber editor to the new data structure
|
||||
- Gerber Editor: added a threshold limit for how many elements a move selection can have. If above the threshold only a bounding box Poly will be painted on canvas as utility geometry.
|
||||
|
||||
10.05.2019
|
||||
|
||||
- Gerber Editor - working in conversion to the new data format
|
||||
- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. The menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
|
||||
- optimized Transform tool
|
||||
- RELEASE 8.916
|
||||
|
||||
9.05.2019
|
||||
|
||||
- reworked the Gerber parser
|
||||
|
||||
8.05.2019
|
||||
|
||||
- added zoom fit for Set Origin command
|
||||
- added move action for solid_geometry stored in the gerber_obj.apertures
|
||||
- fixed camlib.Gerber skew, rotate, offset, mirror functions to work for geometry stored in the Gerber apertures
|
||||
- fixed Gerber Editor follow_geometry reconstruction
|
||||
- Geometry Editor: made the tool to be able to continuously move until the tool is exited either by ESC key or by right mouse button click
|
||||
- Geometry Editor Move Tool: if no shape is selected when triggering this tool, now it is possible to make the selection inside the tool
|
||||
- Gerber editor Move Tool: fixed a bug that repeated the plotting function unnecessarily
|
||||
- Gerber editor Move Tool: if no shape is selected the tool will exit
|
||||
|
||||
7.05.2019
|
||||
|
||||
- remade the Tool Panelize GUI
|
||||
- work in Gerber Export: finished the header export
|
||||
- fixed the Gerber Object and Gerber Editor Apertures Table to not show extra rows when there are aperture macros in the object
|
||||
- work in Gerber Export: finished the body export but have some errors with clear geometry (LPC)
|
||||
- Gerber Export - finished
|
||||
|
||||
6.05.2019
|
||||
|
||||
- made units change from shortcut key 'Q' not to affect the preferences
|
||||
- made units change from Edit -> Toggle Units not to affect the preferences
|
||||
- remade the way the aperture marks are plotted in Gerber Object
|
||||
- fixed some bugs related to moving an Gerber object with the aperture table in view
|
||||
- added a new parameter in the Edit -> Preferences -> App Preferences named Geo Tolerance. This parameter control the level of geometric detail throughout FlatCAM. It directly influence the effect of Circle Steps parameter.
|
||||
- solved a bug in Excellon Editor that caused app crash when trying to edit a tool in Tool Table due of missing a tool offset
|
||||
- updated the ToolPanelize tool so the Gerber panel of type FlatCAMGerber can be isolated like any other FlatCAMGerber object
|
||||
- updated the ToolPanelize tool so it can be edited
|
||||
- modified the default values for toolchangez and endz parameters so they are now safe in all cases
|
||||
|
||||
5.05.2019
|
||||
|
||||
- another fix for bug in clear geometry processing for Gerber apertures
|
||||
- added a protection for the case that the aperture table is part of a deleted object
|
||||
- in Script Editor added support for auto-add closing parenthesis, brace and bracket
|
||||
- in Script Editor added support for "CTRL + / " key combo to comment/uncomment line
|
||||
|
||||
4.05.2019
|
||||
|
||||
- fixed bug in camlib.parse_lines() in the clear_geometry processing section for self.apertures
|
||||
- fixed bug in parsing Gerber regions (a point was added unnecessary)
|
||||
- renamed the menu entry Edit -> Copy as Geo to Convert Any to Geo and moved it in the Edit -> Conversion
|
||||
- created a new function named Convert Any to Gerber and installed it in Edit -> Conversion. It's doing what the name say: it will convert an Geometry or Excellon FlatCAM object to a Gerber object.
|
||||
|
||||
01.05.2019
|
||||
|
||||
- the project items color is now controlled from Foreground Role in ObjectCollection.data()
|
||||
@ -463,7 +595,7 @@ CAD program, and create G-Code for Isolation routing.
|
||||
- fixed mouse selection on canvas, mouse drag, mouse click and mouse double click
|
||||
- fixed Gerber Aperture Table dimensions
|
||||
- added a Mark All button in the Gerber aperture table.
|
||||
- because adding shapes to the shapes collection (when doing Mark or Mark All) is time consuming I made the plot_apertures() threaded.
|
||||
- because adding shapes to the shapes collection (when doing Mark or Mark All) is time consuming I made the plot_aperture() threaded.
|
||||
- made the polygon fusing in modified Gerber creation, a list comprehension in an attempt for optimization
|
||||
- when right clicking the files in Project tab, the Save option for Excellon no longer export it but really save the original.
|
||||
- in ToolChange Custom Code replacement, the Text Box in the CNCJob Selected tab will be active only if there is a 'toolchange_custom' in the name of the postprocessor file. This assume that it is, or was created having as template the Toolchange Custom postprocessor file.
|
||||
@ -635,7 +767,7 @@ CAD program, and create G-Code for Isolation routing.
|
||||
- finished Gerber aperture table display
|
||||
- made the Gerber aperture table not visible as default and added a checkbox that can toggle the visibility
|
||||
- fixed issue with plotting in CNCJob; with Plot kind set to something else than 'all' when toggling Plot, it was defaulting to kind = 'all'
|
||||
- added (and commented) an experimental FlatCAMObj.FlatCAMGerber.plot_apertures()
|
||||
- added (and commented) an experimental FlatCAMObj.FlatCAMGerber.plot_aperture()
|
||||
|
||||
12.02.2019
|
||||
|
||||
|
@ -1565,8 +1565,9 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
self.tool2tooldia[key_in_tool2tooldia] = current_table_dia_edited
|
||||
|
||||
# update the tool offset
|
||||
modified_offset = self.exc_obj.tool_offset.pop(dia_changed)
|
||||
self.exc_obj.tool_offset[current_table_dia_edited] = modified_offset
|
||||
modified_offset = self.exc_obj.tool_offset.pop(dia_changed ,None)
|
||||
if modified_offset is not None:
|
||||
self.exc_obj.tool_offset[current_table_dia_edited] = modified_offset
|
||||
|
||||
self.replot()
|
||||
else:
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -183,6 +183,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
)
|
||||
self.menufileexport.addAction(self.menufileexportexcellon)
|
||||
|
||||
self.menufileexportgerber = QtWidgets.QAction(QtGui.QIcon('share/flatcam_icon32.png'), _('Export &Gerber ...'),
|
||||
self)
|
||||
self.menufileexportgerber.setToolTip(
|
||||
_("Will export an Gerber Object as Gerber file,\n"
|
||||
"the coordinates format, the file units and zeros\n"
|
||||
"are set in Preferences -> Gerber Export.")
|
||||
)
|
||||
self.menufileexport.addAction(self.menufileexportgerber)
|
||||
|
||||
# Separator
|
||||
self.menufile.addSeparator()
|
||||
@ -265,13 +273,18 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
_( "Will convert a Geometry object from multi_geometry type\n"
|
||||
"to a single_geometry type.")
|
||||
)
|
||||
# Separator
|
||||
self.menuedit_convert.addSeparator()
|
||||
self.menueditconvert_any2geo = self.menuedit_convert.addAction(QtGui.QIcon('share/copy_geo.png'),
|
||||
_('Convert Any to Geo'))
|
||||
self.menueditconvert_any2gerber = self.menuedit_convert.addAction(QtGui.QIcon('share/copy_geo.png'),
|
||||
_('Convert Any to Gerber'))
|
||||
self.menuedit_convert.setToolTipsVisible(True)
|
||||
|
||||
# Separator
|
||||
self.menuedit.addSeparator()
|
||||
self.menueditcopyobject = self.menuedit.addAction(QtGui.QIcon('share/copy.png'), _('&Copy Object\tCTRL+C'))
|
||||
self.menueditcopyobjectasgeom = self.menuedit.addAction(QtGui.QIcon('share/copy_geo.png'),
|
||||
_('Copy as &Geom'))
|
||||
self.menueditcopyobject = self.menuedit.addAction(QtGui.QIcon('share/copy.png'), _('&Copy\tCTRL+C'))
|
||||
|
||||
# Separator
|
||||
self.menuedit.addSeparator()
|
||||
self.menueditdelete = self.menuedit.addAction(QtGui.QIcon('share/trash16.png'), _('&Delete\tDEL'))
|
||||
@ -673,6 +686,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
self.geo_add_text_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/text32.png'), _('Add Text'))
|
||||
self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Add Buffer'))
|
||||
self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
|
||||
self.geo_eraser_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
|
||||
|
||||
self.geo_edit_toolbar.addSeparator()
|
||||
self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
|
||||
@ -707,6 +721,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
|
||||
self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
|
||||
self.aperture_eraser_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
|
||||
|
||||
self.grb_edit_toolbar.addSeparator()
|
||||
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
|
||||
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
|
||||
@ -909,7 +925,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.pref_import_button = QtWidgets.QPushButton()
|
||||
self.pref_import_button.setText(_("Import Preferences"))
|
||||
self.pref_import_button.setFixedWidth(130)
|
||||
self.pref_import_button.setMinimumWidth(130)
|
||||
self.pref_import_button.setToolTip(
|
||||
_("Import a full set of FlatCAM settings from a file\n"
|
||||
"previously saved on HDD.\n\n"
|
||||
@ -919,7 +935,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.pref_export_button = QtWidgets.QPushButton()
|
||||
self.pref_export_button.setText(_("Export Preferences"))
|
||||
self.pref_export_button.setFixedWidth(130)
|
||||
self.pref_export_button.setMinimumWidth(130)
|
||||
self.pref_export_button.setToolTip(
|
||||
_( "Export a full set of FlatCAM settings in a file\n"
|
||||
"that is saved on HDD."))
|
||||
@ -927,7 +943,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.pref_open_button = QtWidgets.QPushButton()
|
||||
self.pref_open_button.setText(_("Open Pref Folder"))
|
||||
self.pref_open_button.setFixedWidth(130)
|
||||
self.pref_open_button.setMinimumWidth(130)
|
||||
self.pref_open_button.setToolTip(
|
||||
_("Open the folder where FlatCAM save the preferences files."))
|
||||
self.pref_tab_bottom_layout_1.addWidget(self.pref_open_button)
|
||||
@ -938,7 +954,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.pref_save_button = QtWidgets.QPushButton()
|
||||
self.pref_save_button.setText(_("Save Preferences"))
|
||||
self.pref_save_button.setFixedWidth(130)
|
||||
self.pref_save_button.setMinimumWidth(130)
|
||||
self.pref_save_button.setToolTip(
|
||||
_("Save the current settings in the 'current_defaults' file\n"
|
||||
"which is the file storing the working default preferences."))
|
||||
@ -1895,6 +1911,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'),
|
||||
_('Add Buffer'))
|
||||
self.geo_add_paint_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/paint20_1.png'), _('Paint Shape'))
|
||||
self.geo_eraser_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
|
||||
|
||||
|
||||
self.geo_edit_toolbar.addSeparator()
|
||||
self.geo_union_btn = self.geo_edit_toolbar.addAction(QtGui.QIcon('share/union32.png'), _('Polygon Union'))
|
||||
@ -1929,6 +1947,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
|
||||
self.aperture_scale_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/scale32.png'), _('Scale'))
|
||||
self.aperture_eraser_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/eraser26.png'), _('Eraser'))
|
||||
|
||||
self.grb_edit_toolbar.addSeparator()
|
||||
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
|
||||
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
|
||||
@ -2281,11 +2301,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||
|
||||
# Change Units
|
||||
if key == QtCore.Qt.Key_Q:
|
||||
if self.app.defaults["units"] == 'MM':
|
||||
self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("IN")
|
||||
else:
|
||||
self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("MM")
|
||||
self.app.on_toggle_units()
|
||||
# if self.app.defaults["units"] == 'MM':
|
||||
# self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("IN")
|
||||
# else:
|
||||
# self.app.ui.general_defaults_form.general_app_group.units_radio.set_value("MM")
|
||||
# self.app.on_toggle_units(no_pref=True)
|
||||
self.app.on_toggle_units_click()
|
||||
|
||||
# Rotate Object by 90 degree CW
|
||||
if key == QtCore.Qt.Key_R:
|
||||
@ -3131,12 +3152,22 @@ class GerberPreferencesUI(QtWidgets.QWidget):
|
||||
self.gerber_gen_group.setFixedWidth(250)
|
||||
self.gerber_opt_group = GerberOptPrefGroupUI()
|
||||
self.gerber_opt_group.setFixedWidth(230)
|
||||
self.gerber_exp_group = GerberExpPrefGroupUI()
|
||||
self.gerber_exp_group.setFixedWidth(230)
|
||||
self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
|
||||
self.gerber_adv_opt_group.setFixedWidth(200)
|
||||
self.gerber_editor_group = GerberEditorPrefGroupUI()
|
||||
self.gerber_editor_group.setFixedWidth(200)
|
||||
|
||||
|
||||
self.vlay = QtWidgets.QVBoxLayout()
|
||||
self.vlay.addWidget(self.gerber_opt_group)
|
||||
self.vlay.addWidget(self.gerber_exp_group)
|
||||
|
||||
self.layout.addWidget(self.gerber_gen_group)
|
||||
self.layout.addWidget(self.gerber_opt_group)
|
||||
self.layout.addLayout(self.vlay)
|
||||
self.layout.addWidget(self.gerber_adv_opt_group)
|
||||
self.layout.addWidget(self.gerber_editor_group)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
@ -3181,10 +3212,13 @@ class GeometryPreferencesUI(QtWidgets.QWidget):
|
||||
self.geometry_opt_group.setFixedWidth(250)
|
||||
self.geometry_adv_opt_group = GeometryAdvOptPrefGroupUI()
|
||||
self.geometry_adv_opt_group.setFixedWidth(250)
|
||||
self.geometry_editor_group = GeometryEditorPrefGroupUI()
|
||||
self.geometry_editor_group.setFixedWidth(250)
|
||||
|
||||
self.layout.addWidget(self.geometry_gen_group)
|
||||
self.layout.addWidget(self.geometry_opt_group)
|
||||
self.layout.addWidget(self.geometry_adv_opt_group)
|
||||
self.layout.addWidget(self.geometry_editor_group)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
@ -3316,12 +3350,12 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.workspace_lbl = QtWidgets.QLabel(_('Workspace:'))
|
||||
self.workspace_lbl.setToolTip(
|
||||
_( "Draw a delimiting rectangle on canvas.\n"
|
||||
"The purpose is to illustrate the limits for our work.")
|
||||
"The purpose is to illustrate the limits for our work.")
|
||||
)
|
||||
self.workspace_type_lbl = QtWidgets.QLabel(_('Wk. format:'))
|
||||
self.workspace_type_lbl.setToolTip(
|
||||
_( "Select the type of rectangle to be used on canvas,\n"
|
||||
"as valid workspace.")
|
||||
"as valid workspace.")
|
||||
)
|
||||
self.workspace_cb = FCCheckBox()
|
||||
self.wk_cb = FCComboBox()
|
||||
@ -3336,8 +3370,8 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.pf_color_label = QtWidgets.QLabel(_('Plot Fill:'))
|
||||
self.pf_color_label.setToolTip(
|
||||
_( "Set the fill color for plotted objects.\n"
|
||||
"First 6 digits are the color and the last 2\n"
|
||||
"digits are for alpha (transparency) level.")
|
||||
"First 6 digits are the color and the last 2\n"
|
||||
"digits are for alpha (transparency) level.")
|
||||
)
|
||||
self.pf_color_entry = FCEntry()
|
||||
self.pf_color_button = QtWidgets.QPushButton()
|
||||
@ -3723,18 +3757,18 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
# Units for FlatCAM
|
||||
self.unitslabel = QtWidgets.QLabel(_('<b>Units:</b>'))
|
||||
self.unitslabel.setToolTip(_("The default value for FlatCAM units.\n"
|
||||
"Whatever is selected here is set every time\n"
|
||||
"FLatCAM is started."))
|
||||
"Whatever is selected here is set every time\n"
|
||||
"FLatCAM is started."))
|
||||
self.units_radio = RadioSet([{'label': 'IN', 'value': 'IN'},
|
||||
{'label': 'MM', 'value': 'MM'}])
|
||||
|
||||
# Application Level for FlatCAM
|
||||
self.app_level_label = QtWidgets.QLabel(_('<b>APP. LEVEL:</b>'))
|
||||
self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
|
||||
"BASIC level -> reduced functionality, best for beginner's.\n"
|
||||
"ADVANCED level -> full functionality.\n\n"
|
||||
"The choice here will influence the parameters in\n"
|
||||
"the Selected Tab for all kinds of FlatCAM objects."))
|
||||
"BASIC level -> reduced functionality, best for beginner's.\n"
|
||||
"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'}])
|
||||
|
||||
@ -3756,24 +3790,24 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
self.shell_startup_label = QtWidgets.QLabel(_('Shell at StartUp:'))
|
||||
self.shell_startup_label.setToolTip(
|
||||
_("Check this box if you want the shell to\n"
|
||||
"start automatically at startup.")
|
||||
"start automatically at startup.")
|
||||
)
|
||||
self.shell_startup_cb = FCCheckBox(label='')
|
||||
self.shell_startup_cb.setToolTip(
|
||||
_("Check this box if you want the shell to\n"
|
||||
"start automatically at startup.")
|
||||
"start automatically at startup.")
|
||||
)
|
||||
|
||||
# Version Check CB
|
||||
self.version_check_label = QtWidgets.QLabel(_('Version Check:'))
|
||||
self.version_check_label.setToolTip(
|
||||
_("Check this box if you want to check\n"
|
||||
"for a new version automatically at startup.")
|
||||
"for a new version automatically at startup.")
|
||||
)
|
||||
self.version_check_cb = FCCheckBox(label='')
|
||||
self.version_check_cb.setToolTip(
|
||||
_("Check this box if you want to check\n"
|
||||
"for a new version automatically at startup.")
|
||||
"for a new version automatically at startup.")
|
||||
)
|
||||
|
||||
# Send Stats CB
|
||||
@ -3785,7 +3819,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
self.send_stats_cb= FCCheckBox(label='')
|
||||
self.send_stats_cb.setToolTip(
|
||||
_("Check this box if you agree to send anonymous\n"
|
||||
"stats automatically at startup, to help improve FlatCAM.")
|
||||
"stats automatically at startup, to help improve FlatCAM.")
|
||||
)
|
||||
|
||||
self.ois_version_check = OptionalInputSection(self.version_check_cb, [self.send_stats_cb])
|
||||
@ -3793,8 +3827,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
# Select mouse pan button
|
||||
self.panbuttonlabel = QtWidgets.QLabel(_('<b>Pan Button:</b>'))
|
||||
self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
|
||||
"- MMB --> Middle Mouse Button\n"
|
||||
"- RMB --> Right Mouse Button"))
|
||||
"- MMB --> Middle Mouse Button\n"
|
||||
"- RMB --> Right Mouse Button"))
|
||||
self.pan_button_radio = RadioSet([{'label': 'MMB', 'value': '3'},
|
||||
{'label': 'RMB', 'value': '2'}])
|
||||
|
||||
@ -3802,44 +3836,44 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
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'}])
|
||||
{'label': 'SHIFT', 'value': 'Shift'}])
|
||||
|
||||
# Project at StartUp CB
|
||||
self.project_startup_label = QtWidgets.QLabel(_('Project at StartUp:'))
|
||||
self.project_startup_label.setToolTip(
|
||||
_("Check this box if you want the project/selected/tool tab area to\n"
|
||||
"to be shown automatically at startup.")
|
||||
"to be shown automatically at startup.")
|
||||
)
|
||||
self.project_startup_cb = FCCheckBox(label='')
|
||||
self.project_startup_cb.setToolTip(
|
||||
_("Check this box if you want the project/selected/tool tab area to\n"
|
||||
"to be shown automatically at startup.")
|
||||
"to be shown automatically at startup.")
|
||||
)
|
||||
|
||||
# Project autohide CB
|
||||
self.project_autohide_label = QtWidgets.QLabel(_('Project AutoHide:'))
|
||||
self.project_autohide_label.setToolTip(
|
||||
_( "Check this box if you want the project/selected/tool tab area to\n"
|
||||
"hide automatically when there are no objects loaded and\n"
|
||||
"to show whenever a new object is created.")
|
||||
"hide automatically when there are no objects loaded and\n"
|
||||
"to show whenever a new object is created.")
|
||||
)
|
||||
self.project_autohide_cb = FCCheckBox(label='')
|
||||
self.project_autohide_cb.setToolTip(
|
||||
_("Check this box if you want the project/selected/tool tab area to\n"
|
||||
"hide automatically when there are no objects loaded and\n"
|
||||
"to show whenever a new object is created.")
|
||||
"hide automatically when there are no objects loaded and\n"
|
||||
"to show whenever a new object is created.")
|
||||
)
|
||||
|
||||
# Enable/Disable ToolTips globally
|
||||
self.toggle_tooltips_label = QtWidgets.QLabel(_('<b>Enable ToolTips:</b>'))
|
||||
self.toggle_tooltips_label.setToolTip(
|
||||
_( "Check this box if you want to have toolTips displayed\n"
|
||||
"when hovering with mouse over items throughout the App.")
|
||||
"when hovering with mouse over items throughout the App.")
|
||||
)
|
||||
self.toggle_tooltips_cb = FCCheckBox(label='')
|
||||
self.toggle_tooltips_cb.setToolTip(
|
||||
_( "Check this box if you want to have toolTips displayed\n"
|
||||
"when hovering with mouse over items throughout the App.")
|
||||
"when hovering with mouse over items throughout the App.")
|
||||
)
|
||||
self.worker_number_label = QtWidgets.QLabel(_('Workers number:'))
|
||||
self.worker_number_label.setToolTip(
|
||||
@ -3861,6 +3895,25 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
)
|
||||
self.worker_number_sb.set_range(2, 16)
|
||||
|
||||
# Geometric tolerance
|
||||
tol_label = QtWidgets.QLabel("Geo Tolerance:")
|
||||
tol_label.setToolTip(_(
|
||||
"This value can counter the effect of the Circle Steps\n"
|
||||
"parameter. Default value is 0.01.\n"
|
||||
"A lower value will increase the detail both in image\n"
|
||||
"and in Gcode for the circles, with a higher cost in\n"
|
||||
"performance. Higher value will provide more\n"
|
||||
"performance at the expense of level of detail."
|
||||
))
|
||||
self.tol_entry = FCEntry()
|
||||
self.tol_entry.setToolTip(_(
|
||||
"This value can counter the effect of the Circle Steps\n"
|
||||
"parameter. Default value is 0.01.\n"
|
||||
"A lower value will increase the detail both in image\n"
|
||||
"and in Gcode for the circles, with a higher cost in\n"
|
||||
"performance. Higher value will provide more\n"
|
||||
"performance at the expense of level of detail."
|
||||
))
|
||||
# Just to add empty rows
|
||||
self.spacelabel = QtWidgets.QLabel('')
|
||||
|
||||
@ -3881,6 +3934,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
self.form_box.addRow(self.project_autohide_label, self.project_autohide_cb)
|
||||
self.form_box.addRow(self.toggle_tooltips_label, self.toggle_tooltips_cb)
|
||||
self.form_box.addRow(self.worker_number_label, self.worker_number_sb)
|
||||
self.form_box.addRow(tol_label, self.tol_entry)
|
||||
|
||||
self.form_box.addRow(self.spacelabel, self.spacelabel)
|
||||
|
||||
@ -3888,15 +3942,22 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
# to the main layout of this TAB
|
||||
self.layout.addLayout(self.form_box)
|
||||
|
||||
# hlay = QtWidgets.QHBoxLayout()
|
||||
# self.layout.addLayout(hlay)
|
||||
# hlay.addStretch()
|
||||
# Save compressed project CB
|
||||
self.open_style_cb = FCCheckBox(_('"Open" behavior'))
|
||||
self.open_style_cb.setToolTip(
|
||||
_("When checked the path for the last saved file is used when saving files,\n"
|
||||
"and the path for the last opened file is used when opening files.\n\n"
|
||||
"When unchecked the path for opening files is the one used last: either the\n"
|
||||
"path for saving files or the path for opening files.")
|
||||
)
|
||||
# self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.layout.addWidget(self.open_style_cb)
|
||||
|
||||
# Save compressed project CB
|
||||
self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
|
||||
self.save_type_cb.setToolTip(
|
||||
_("Whether to save a compressed or uncompressed project.\n"
|
||||
"When checked it will save a compressed FlatCAM project.")
|
||||
"When checked it will save a compressed FlatCAM project.")
|
||||
)
|
||||
# self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.layout.addWidget(self.save_type_cb)
|
||||
@ -3909,8 +3970,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
self.compress_label = QtWidgets.QLabel(_('Compression Level:'))
|
||||
self.compress_label.setToolTip(
|
||||
_("The level of compression used when saving\n"
|
||||
"a FlatCAM project. Higher value means better compression\n"
|
||||
"but require more RAM usage and more processing time.")
|
||||
"a FlatCAM project. Higher value means better compression\n"
|
||||
"but require more RAM usage and more processing time.")
|
||||
)
|
||||
# self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.compress_combo.addItems([str(i) for i in range(10)])
|
||||
@ -4140,28 +4201,156 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(self.aperture_table_visibility_cb, 1, 0)
|
||||
|
||||
# Scale Aperture Factor
|
||||
self.scale_aperture_label = QtWidgets.QLabel(_('Ap. Scale Factor:'))
|
||||
self.scale_aperture_label.setToolTip(
|
||||
_("Change the size of the selected apertures.\n"
|
||||
"Factor by which to multiply\n"
|
||||
"geometric features of this object.")
|
||||
)
|
||||
grid0.addWidget(self.scale_aperture_label, 2, 0)
|
||||
|
||||
self.scale_aperture_entry = FloatEntry2()
|
||||
grid0.addWidget(self.scale_aperture_entry, 2, 1)
|
||||
# self.scale_aperture_label = QtWidgets.QLabel(_('Ap. Scale Factor:'))
|
||||
# self.scale_aperture_label.setToolTip(
|
||||
# _("Change the size of the selected apertures.\n"
|
||||
# "Factor by which to multiply\n"
|
||||
# "geometric features of this object.")
|
||||
# )
|
||||
# grid0.addWidget(self.scale_aperture_label, 2, 0)
|
||||
#
|
||||
# self.scale_aperture_entry = FloatEntry2()
|
||||
# grid0.addWidget(self.scale_aperture_entry, 2, 1)
|
||||
|
||||
# Buffer Aperture Factor
|
||||
self.buffer_aperture_label = QtWidgets.QLabel(_('Ap. Buffer Factor:'))
|
||||
self.buffer_aperture_label.setToolTip(
|
||||
_("Change the size of the selected apertures.\n"
|
||||
"Factor by which to expand/shrink\n"
|
||||
"geometric features of this object.")
|
||||
)
|
||||
grid0.addWidget(self.buffer_aperture_label, 3, 0)
|
||||
# self.buffer_aperture_label = QtWidgets.QLabel(_('Ap. Buffer Factor:'))
|
||||
# self.buffer_aperture_label.setToolTip(
|
||||
# _("Change the size of the selected apertures.\n"
|
||||
# "Factor by which to expand/shrink\n"
|
||||
# "geometric features of this object.")
|
||||
# )
|
||||
# grid0.addWidget(self.buffer_aperture_label, 3, 0)
|
||||
#
|
||||
# self.buffer_aperture_entry = FloatEntry2()
|
||||
# grid0.addWidget(self.buffer_aperture_entry, 3, 1)
|
||||
|
||||
self.buffer_aperture_entry = FloatEntry2()
|
||||
grid0.addWidget(self.buffer_aperture_entry, 3, 1)
|
||||
self.layout.addStretch()
|
||||
|
||||
|
||||
class GerberExpPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GerberExpPrefGroupUI, self).__init__(self)
|
||||
|
||||
self.setTitle(str(_("Gerber Export")))
|
||||
|
||||
# Plot options
|
||||
self.export_options_label = QtWidgets.QLabel(_("<b>Export Options:</b>"))
|
||||
self.export_options_label.setToolTip(
|
||||
_("The parameters set here are used in the file exported\n"
|
||||
"when using the File -> Export -> Export Gerber menu entry.")
|
||||
)
|
||||
self.layout.addWidget(self.export_options_label)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form)
|
||||
|
||||
# Gerber Units
|
||||
self.gerber_units_label = QtWidgets.QLabel(_('<b>Units</b>:'))
|
||||
self.gerber_units_label.setToolTip(
|
||||
_("The units used in the Gerber file.")
|
||||
)
|
||||
|
||||
self.gerber_units_radio = RadioSet([{'label': 'INCH', 'value': 'IN'},
|
||||
{'label': 'MM', 'value': 'MM'}])
|
||||
self.gerber_units_radio.setToolTip(
|
||||
_("The units used in the Gerber file.")
|
||||
)
|
||||
|
||||
form.addRow(self.gerber_units_label, self.gerber_units_radio)
|
||||
|
||||
# Gerber format
|
||||
self.digits_label = QtWidgets.QLabel(_("<b>Int/Decimals:</b>"))
|
||||
self.digits_label.setToolTip(
|
||||
_("The number of digits in the whole part of the number\n"
|
||||
"and in the fractional part of the number.")
|
||||
)
|
||||
|
||||
hlay1 = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.format_whole_entry = IntEntry()
|
||||
self.format_whole_entry.setMaxLength(1)
|
||||
self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.format_whole_entry.setFixedWidth(30)
|
||||
self.format_whole_entry.setToolTip(
|
||||
_("This numbers signify the number of digits in\n"
|
||||
"the whole part of Gerber coordinates.")
|
||||
)
|
||||
hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft)
|
||||
|
||||
gerber_separator_label= QtWidgets.QLabel(':')
|
||||
gerber_separator_label.setFixedWidth(5)
|
||||
hlay1.addWidget(gerber_separator_label, QtCore.Qt.AlignLeft)
|
||||
|
||||
self.format_dec_entry = IntEntry()
|
||||
self.format_dec_entry.setMaxLength(1)
|
||||
self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.format_dec_entry.setFixedWidth(30)
|
||||
self.format_dec_entry.setToolTip(
|
||||
_("This numbers signify the number of digits in\n"
|
||||
"the decimal part of Gerber coordinates.")
|
||||
)
|
||||
hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft)
|
||||
hlay1.addStretch()
|
||||
|
||||
form.addRow(self.digits_label, hlay1)
|
||||
|
||||
# Gerber Zeros
|
||||
self.zeros_label = QtWidgets.QLabel(_('<b>Zeros</b>:'))
|
||||
self.zeros_label.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.zeros_label.setToolTip(
|
||||
_("This sets the type of Gerber zeros.\n"
|
||||
"If LZ then Leading Zeros are removed and\n"
|
||||
"Trailing Zeros are kept.\n"
|
||||
"If TZ is checked then Trailing Zeros are removed\n"
|
||||
"and Leading Zeros are kept.")
|
||||
)
|
||||
|
||||
self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'},
|
||||
{'label': 'TZ', 'value': 'T'}])
|
||||
self.zeros_radio.setToolTip(
|
||||
_("This sets the type of Gerber zeros.\n"
|
||||
"If LZ then Leading Zeros are removed and\n"
|
||||
"Trailing Zeros are kept.\n"
|
||||
"If TZ is checked then Trailing Zeros are removed\n"
|
||||
"and Leading Zeros are kept.")
|
||||
)
|
||||
|
||||
form.addRow(self.zeros_label, self.zeros_radio)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
|
||||
class GerberEditorPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
|
||||
super(GerberEditorPrefGroupUI, self).__init__(self)
|
||||
|
||||
self.setTitle(str(_("Gerber Editor")))
|
||||
|
||||
# Advanced Gerber Parameters
|
||||
self.param_label = QtWidgets.QLabel(_("<b>Parameters:</b>"))
|
||||
self.param_label.setToolTip(
|
||||
_("A list of Gerber Editor parameters.")
|
||||
)
|
||||
self.layout.addWidget(self.param_label)
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
# Selection Limit
|
||||
self.sel_limit_label = QtWidgets.QLabel(_("Selection limit:"))
|
||||
self.sel_limit_label.setToolTip(
|
||||
_("Set the number of selected Gerber geometry\n"
|
||||
"items above which the utility geometry\n"
|
||||
"becomes just a selection rectangle.\n"
|
||||
"Increases the performance when moving a\n"
|
||||
"large number of geometric elements.")
|
||||
)
|
||||
self.sel_limit_entry = IntEntry()
|
||||
|
||||
grid0.addWidget(self.sel_limit_label, 0, 0)
|
||||
grid0.addWidget(self.sel_limit_entry, 0, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
@ -4391,7 +4580,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
)
|
||||
|
||||
self.optimization_time_entry = LengthEntry()
|
||||
self.optimization_time_entry = IntEntry()
|
||||
self.optimization_time_entry.setValidator(QtGui.QIntValidator(0, 999))
|
||||
form_box_excellon.addRow(self.optimization_time_label, self.optimization_time_entry)
|
||||
|
||||
current_platform = platform.architecture()[0]
|
||||
@ -4493,6 +4683,20 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
|
||||
self.spindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid2.addWidget(self.spindlespeed_entry, 5, 1)
|
||||
|
||||
# Spindle direction
|
||||
spindle_dir_label = QtWidgets.QLabel(_('Spindle dir.:'))
|
||||
spindle_dir_label.setToolTip(
|
||||
_("This sets the direction that the spindle is rotating.\n"
|
||||
"It can be either:\n"
|
||||
"- CW = clockwise or\n"
|
||||
"- CCW = counter clockwise")
|
||||
)
|
||||
|
||||
self.spindledir_radio = RadioSet([{'label': 'CW', 'value': 'CW'},
|
||||
{'label': 'CCW', 'value': 'CCW'}])
|
||||
grid2.addWidget(spindle_dir_label, 6, 0)
|
||||
grid2.addWidget(self.spindledir_radio, 6, 1)
|
||||
|
||||
# Dwell
|
||||
dwelllabel = QtWidgets.QLabel(_('Dwell:'))
|
||||
dwelllabel.setToolTip(
|
||||
@ -4505,10 +4709,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
|
||||
)
|
||||
self.dwell_cb = FCCheckBox()
|
||||
self.dwelltime_entry = FCEntry()
|
||||
grid2.addWidget(dwelllabel, 6, 0)
|
||||
grid2.addWidget(self.dwell_cb, 6, 1)
|
||||
grid2.addWidget(dwelltime, 7, 0)
|
||||
grid2.addWidget(self.dwelltime_entry, 7, 1)
|
||||
grid2.addWidget(dwelllabel, 7, 0)
|
||||
grid2.addWidget(self.dwell_cb, 7, 1)
|
||||
grid2.addWidget(dwelltime, 8, 0)
|
||||
grid2.addWidget(self.dwelltime_entry, 8, 1)
|
||||
|
||||
self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
|
||||
|
||||
@ -4518,10 +4722,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
|
||||
_("The postprocessor file that dictates\n"
|
||||
"gcode output.")
|
||||
)
|
||||
grid2.addWidget(pp_excellon_label, 8, 0)
|
||||
grid2.addWidget(pp_excellon_label, 9, 0)
|
||||
self.pp_excellon_name_cb = FCComboBox()
|
||||
self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
|
||||
grid2.addWidget(self.pp_excellon_name_cb, 8, 1)
|
||||
grid2.addWidget(self.pp_excellon_name_cb, 9, 1)
|
||||
|
||||
|
||||
#### Choose what to use for Gcode creation: Drills, Slots or Both
|
||||
@ -4535,8 +4739,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
|
||||
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)
|
||||
grid2.addWidget(excellon_gcode_type_label, 10, 0)
|
||||
grid2.addWidget(self.excellon_gcode_type_radio, 10, 1)
|
||||
|
||||
# until I decide to implement this feature those remain disabled
|
||||
excellon_gcode_type_label.hide()
|
||||
@ -4986,6 +5190,20 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
|
||||
self.cncspindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid1.addWidget(self.cncspindlespeed_entry, 8, 1)
|
||||
|
||||
# Spindle direction
|
||||
spindle_dir_label = QtWidgets.QLabel(_('Spindle dir.:'))
|
||||
spindle_dir_label.setToolTip(
|
||||
_("This sets the direction that the spindle is rotating.\n"
|
||||
"It can be either:\n"
|
||||
"- CW = clockwise or\n"
|
||||
"- CCW = counter clockwise")
|
||||
)
|
||||
|
||||
self.spindledir_radio = RadioSet([{'label': 'CW', 'value': 'CW'},
|
||||
{'label': 'CCW', 'value': 'CCW'}])
|
||||
grid1.addWidget(spindle_dir_label, 9, 0)
|
||||
grid1.addWidget(self.spindledir_radio, 9, 1)
|
||||
|
||||
# Dwell
|
||||
self.dwell_cb = FCCheckBox(label=_('Dwell:'))
|
||||
self.dwell_cb.setToolTip(
|
||||
@ -4997,9 +5215,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
|
||||
_("Number of milliseconds for spindle to dwell.")
|
||||
)
|
||||
self.dwelltime_entry = FCEntry()
|
||||
grid1.addWidget(self.dwell_cb, 9, 0)
|
||||
grid1.addWidget(dwelltime, 10, 0)
|
||||
grid1.addWidget(self.dwelltime_entry, 10, 1)
|
||||
grid1.addWidget(self.dwell_cb, 10, 0)
|
||||
grid1.addWidget(dwelltime, 11, 0)
|
||||
grid1.addWidget(self.dwelltime_entry, 11, 1)
|
||||
|
||||
self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
|
||||
|
||||
@ -5009,10 +5227,10 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
|
||||
_("The postprocessor file that dictates\n"
|
||||
"Machine Code output.")
|
||||
)
|
||||
grid1.addWidget(pp_label, 11, 0)
|
||||
grid1.addWidget(pp_label, 12, 0)
|
||||
self.pp_geometry_name_cb = FCComboBox()
|
||||
self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
|
||||
grid1.addWidget(self.pp_geometry_name_cb, 11, 1)
|
||||
grid1.addWidget(self.pp_geometry_name_cb, 12, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
@ -5146,6 +5364,40 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
self.layout.addStretch()
|
||||
|
||||
|
||||
class GeometryEditorPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
|
||||
super(GeometryEditorPrefGroupUI, self).__init__(self)
|
||||
|
||||
self.setTitle(str(_("Geometry Editor")))
|
||||
|
||||
# Advanced Geometry Parameters
|
||||
self.param_label = QtWidgets.QLabel(_("<b>Parameters:</b>"))
|
||||
self.param_label.setToolTip(
|
||||
_("A list of Geometry Editor parameters.")
|
||||
)
|
||||
self.layout.addWidget(self.param_label)
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
# Selection Limit
|
||||
self.sel_limit_label = QtWidgets.QLabel(_("Selection limit:"))
|
||||
self.sel_limit_label.setToolTip(
|
||||
_("Set the number of selected geometry\n"
|
||||
"items above which the utility geometry\n"
|
||||
"becomes just a selection rectangle.\n"
|
||||
"Increases the performance when moving a\n"
|
||||
"large number of geometric elements.")
|
||||
)
|
||||
self.sel_limit_entry = IntEntry()
|
||||
|
||||
grid0.addWidget(self.sel_limit_label, 0, 0)
|
||||
grid0.addWidget(self.sel_limit_entry, 0, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
|
||||
class CNCJobGenPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "CNC Job General Preferences", parent=None)
|
||||
|
@ -571,11 +571,48 @@ class FCTextAreaExtended(QtWidgets.QTextEdit):
|
||||
clip_text = clip_text.replace('\\', '/')
|
||||
self.insertPlainText(clip_text)
|
||||
|
||||
if modifier & Qt.ControlModifier and key == Qt.Key_Slash:
|
||||
self.comment()
|
||||
|
||||
tc = self.textCursor()
|
||||
if (key == Qt.Key_Tab or key == Qt.Key_Enter or key == Qt.Key_Return) and self.completer.popup().isVisible():
|
||||
self.completer.insertText.emit(self.completer.getSelected())
|
||||
self.completer.setCompletionMode(QCompleter.PopupCompletion)
|
||||
return
|
||||
elif key == Qt.Key_BraceLeft:
|
||||
tc.insertText('{}')
|
||||
self.moveCursor(QtGui.QTextCursor.Left)
|
||||
elif key == Qt.Key_BracketLeft:
|
||||
tc.insertText('[]')
|
||||
self.moveCursor(QtGui.QTextCursor.Left)
|
||||
elif key == Qt.Key_ParenLeft:
|
||||
tc.insertText('()')
|
||||
self.moveCursor(QtGui.QTextCursor.Left)
|
||||
|
||||
elif key == Qt.Key_BraceRight:
|
||||
tc.select(QtGui.QTextCursor.WordUnderCursor)
|
||||
if tc.selectedText() == '}':
|
||||
tc.movePosition(QTextCursor.Right)
|
||||
self.setTextCursor(tc)
|
||||
else:
|
||||
tc.clearSelection()
|
||||
self.textCursor().insertText('}')
|
||||
elif key == Qt.Key_BracketRight:
|
||||
tc.select(QtGui.QTextCursor.WordUnderCursor)
|
||||
if tc.selectedText() == ']':
|
||||
tc.movePosition(QTextCursor.Right)
|
||||
self.setTextCursor(tc)
|
||||
else:
|
||||
tc.clearSelection()
|
||||
self.textCursor().insertText(']')
|
||||
elif key == Qt.Key_ParenRight:
|
||||
tc.select(QtGui.QTextCursor.WordUnderCursor)
|
||||
if tc.selectedText() == ')':
|
||||
tc.movePosition(QTextCursor.Right)
|
||||
self.setTextCursor(tc)
|
||||
else:
|
||||
tc.clearSelection()
|
||||
self.textCursor().insertText(')')
|
||||
else:
|
||||
super(FCTextAreaExtended, self).keyPressEvent(event)
|
||||
|
||||
@ -594,6 +631,33 @@ class FCTextAreaExtended(QtWidgets.QTextEdit):
|
||||
else:
|
||||
self.completer.popup().hide()
|
||||
|
||||
def comment(self):
|
||||
"""
|
||||
Got it from here:
|
||||
https://stackoverflow.com/questions/49898820/how-to-get-text-next-to-cursor-in-qtextedit-in-pyqt4
|
||||
:return:
|
||||
"""
|
||||
pos = self.textCursor().position()
|
||||
self.moveCursor(QtGui.QTextCursor.StartOfLine)
|
||||
line_text = self.textCursor().block().text()
|
||||
if self.textCursor().block().text().startswith(" "):
|
||||
# skip the white space
|
||||
self.moveCursor(QtGui.QTextCursor.NextWord)
|
||||
self.moveCursor(QtGui.QTextCursor.NextCharacter,QtGui.QTextCursor.KeepAnchor)
|
||||
character = self.textCursor().selectedText()
|
||||
if character == "#":
|
||||
# delete #
|
||||
self.textCursor().deletePreviousChar()
|
||||
# delete white space
|
||||
self.moveCursor(QtGui.QTextCursor.NextWord,QtGui.QTextCursor.KeepAnchor)
|
||||
self.textCursor().removeSelectedText()
|
||||
else:
|
||||
self.moveCursor(QtGui.QTextCursor.PreviousCharacter,QtGui.QTextCursor.KeepAnchor)
|
||||
self.textCursor().insertText("# ")
|
||||
cursor = QtGui.QTextCursor(self.textCursor())
|
||||
cursor.setPosition(pos)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
|
||||
class FCComboBox(QtWidgets.QComboBox):
|
||||
|
||||
|
@ -674,6 +674,9 @@ class ExcellonObjectUI(ObjectUI):
|
||||
grid1.addWidget(self.feedrate_rapid_label, 7, 0)
|
||||
self.feedrate_rapid_entry = LengthEntry()
|
||||
grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
|
||||
# default values is to hide
|
||||
self.feedrate_rapid_label.hide()
|
||||
self.feedrate_rapid_entry.hide()
|
||||
|
||||
# Spindlespeed
|
||||
spdlabel = QtWidgets.QLabel(_('Spindle speed:'))
|
||||
@ -1188,6 +1191,9 @@ class GeometryObjectUI(ObjectUI):
|
||||
self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
|
||||
self.cncfeedrate_rapid_entry = LengthEntry()
|
||||
self.grid3.addWidget(self.cncfeedrate_rapid_entry, 12, 1)
|
||||
# default values is to hide
|
||||
self.fr_rapidlabel.hide()
|
||||
self.cncfeedrate_rapid_entry.hide()
|
||||
|
||||
# Cut over 1st point in path
|
||||
self.extracut_cb = FCCheckBox(_('Cut over 1st pt'))
|
||||
|
@ -5,9 +5,9 @@ from shapely.geometry import box
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
import builtins
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
@ -23,7 +23,7 @@ class CutOut(FlatCAMTool):
|
||||
self.app = app
|
||||
self.canvas = app.plotcanvas
|
||||
|
||||
## Title
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
@ -34,11 +34,11 @@ class CutOut(FlatCAMTool):
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
|
||||
## Type of object to be cutout
|
||||
# Type of object to be cutout
|
||||
self.type_obj_combo = QtWidgets.QComboBox()
|
||||
self.type_obj_combo.addItem("Gerber")
|
||||
self.type_obj_combo.addItem("Excellon")
|
||||
@ -53,14 +53,14 @@ class CutOut(FlatCAMTool):
|
||||
self.type_obj_combo_label = QtWidgets.QLabel(_("Obj Type:"))
|
||||
self.type_obj_combo_label.setToolTip(
|
||||
_("Specify the type of object to be cutout.\n"
|
||||
"It can be of type: Gerber or Geometry.\n"
|
||||
"What is selected here will dictate the kind\n"
|
||||
"of objects that will populate the 'Object' combobox.")
|
||||
"It can be of type: Gerber or Geometry.\n"
|
||||
"What is selected here will dictate the kind\n"
|
||||
"of objects that will populate the 'Object' combobox.")
|
||||
)
|
||||
self.type_obj_combo_label.setFixedWidth(60)
|
||||
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
||||
|
||||
## Object to be cutout
|
||||
# Object to be cutout
|
||||
self.obj_combo = QtWidgets.QComboBox()
|
||||
self.obj_combo.setModel(self.app.collection)
|
||||
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
@ -76,8 +76,8 @@ class CutOut(FlatCAMTool):
|
||||
self.dia = FCEntry()
|
||||
self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
|
||||
self.dia_label.setToolTip(
|
||||
_( "Diameter of the tool used to cutout\n"
|
||||
"the PCB shape out of the surrounding material.")
|
||||
_("Diameter of the tool used to cutout\n"
|
||||
"the PCB shape out of the surrounding material.")
|
||||
)
|
||||
form_layout.addRow(self.dia_label, self.dia)
|
||||
|
||||
@ -85,9 +85,9 @@ class CutOut(FlatCAMTool):
|
||||
self.margin = FCEntry()
|
||||
self.margin_label = QtWidgets.QLabel(_("Margin:"))
|
||||
self.margin_label.setToolTip(
|
||||
_( "Margin over bounds. A positive value here\n"
|
||||
"will make the cutout of the PCB further from\n"
|
||||
"the actual PCB border")
|
||||
_("Margin over bounds. A positive value here\n"
|
||||
"will make the cutout of the PCB further from\n"
|
||||
"the actual PCB border")
|
||||
)
|
||||
form_layout.addRow(self.margin_label, self.margin)
|
||||
|
||||
@ -95,10 +95,10 @@ class CutOut(FlatCAMTool):
|
||||
self.gapsize = FCEntry()
|
||||
self.gapsize_label = QtWidgets.QLabel(_("Gap size:"))
|
||||
self.gapsize_label.setToolTip(
|
||||
_( "The size of the bridge gaps in the cutout\n"
|
||||
"used to keep the board connected to\n"
|
||||
"the surrounding material (the one \n"
|
||||
"from which the PCB is cutout).")
|
||||
_("The size of the bridge gaps in the cutout\n"
|
||||
"used to keep the board connected to\n"
|
||||
"the surrounding material (the one \n"
|
||||
"from which the PCB is cutout).")
|
||||
)
|
||||
form_layout.addRow(self.gapsize_label, self.gapsize)
|
||||
|
||||
@ -114,18 +114,19 @@ class CutOut(FlatCAMTool):
|
||||
self.convex_box = FCCheckBox()
|
||||
self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
|
||||
self.convex_box_label.setToolTip(
|
||||
_("Create a convex shape surrounding the entire PCB.")
|
||||
_("Create a convex shape surrounding the entire PCB.\n"
|
||||
"Used only if the source object type is Gerber.")
|
||||
)
|
||||
form_layout.addRow(self.convex_box_label, self.convex_box)
|
||||
|
||||
## Title2
|
||||
# Title2
|
||||
title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
|
||||
title_param_label.setToolTip(
|
||||
_("This section handle creation of automatic bridge gaps.")
|
||||
)
|
||||
self.layout.addWidget(title_param_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form_layout_2 = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout_2)
|
||||
|
||||
@ -133,14 +134,14 @@ class CutOut(FlatCAMTool):
|
||||
gaps_label = QtWidgets.QLabel(_('Gaps:'))
|
||||
gaps_label.setToolTip(
|
||||
_("Number of gaps used for the Automatic cutout.\n"
|
||||
"There can be maximum 8 bridges/gaps.\n"
|
||||
"The choices are:\n"
|
||||
"- lr - left + right\n"
|
||||
"- tb - top + bottom\n"
|
||||
"- 4 - left + right +top + bottom\n"
|
||||
"- 2lr - 2*left + 2*right\n"
|
||||
"- 2tb - 2*top + 2*bottom\n"
|
||||
"- 8 - 2*left + 2*right +2*top + 2*bottom")
|
||||
"There can be maximum 8 bridges/gaps.\n"
|
||||
"The choices are:\n"
|
||||
"- lr - left + right\n"
|
||||
"- tb - top + bottom\n"
|
||||
"- 4 - left + right +top + bottom\n"
|
||||
"- 2lr - 2*left + 2*right\n"
|
||||
"- 2tb - 2*top + 2*bottom\n"
|
||||
"- 8 - 2*left + 2*right +2*top + 2*bottom")
|
||||
)
|
||||
gaps_label.setFixedWidth(60)
|
||||
|
||||
@ -151,14 +152,14 @@ class CutOut(FlatCAMTool):
|
||||
self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
|
||||
form_layout_2.addRow(gaps_label, self.gaps)
|
||||
|
||||
## Buttons
|
||||
# Buttons
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
|
||||
title_ff_label = QtWidgets.QLabel("<b>%s</b>" % _('FreeForm:'))
|
||||
title_ff_label.setToolTip(
|
||||
_("The cutout shape can be of ny shape.\n"
|
||||
"Useful when the PCB has a non-rectangular shape.")
|
||||
"Useful when the PCB has a non-rectangular shape.")
|
||||
)
|
||||
hlay.addWidget(title_ff_label)
|
||||
|
||||
@ -167,8 +168,8 @@ class CutOut(FlatCAMTool):
|
||||
self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
||||
self.ff_cutout_object_btn.setToolTip(
|
||||
_("Cutout the selected object.\n"
|
||||
"The cutout shape can be of any shape.\n"
|
||||
"Useful when the PCB has a non-rectangular shape.")
|
||||
"The cutout shape can be of any shape.\n"
|
||||
"Useful when the PCB has a non-rectangular shape.")
|
||||
)
|
||||
hlay.addWidget(self.ff_cutout_object_btn)
|
||||
|
||||
@ -178,8 +179,8 @@ class CutOut(FlatCAMTool):
|
||||
title_rct_label = QtWidgets.QLabel("<b>%s</b>" % _('Rectangular:'))
|
||||
title_rct_label.setToolTip(
|
||||
_("The resulting cutout shape is\n"
|
||||
"always a rectangle shape and it will be\n"
|
||||
"the bounding box of the Object.")
|
||||
"always a rectangle shape and it will be\n"
|
||||
"the bounding box of the Object.")
|
||||
)
|
||||
hlay2.addWidget(title_rct_label)
|
||||
|
||||
@ -187,26 +188,26 @@ class CutOut(FlatCAMTool):
|
||||
self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
||||
self.rect_cutout_object_btn.setToolTip(
|
||||
_("Cutout the selected object.\n"
|
||||
"The resulting cutout shape is\n"
|
||||
"always a rectangle shape and it will be\n"
|
||||
"the bounding box of the Object.")
|
||||
"The resulting cutout shape is\n"
|
||||
"always a rectangle shape and it will be\n"
|
||||
"the bounding box of the Object.")
|
||||
)
|
||||
hlay2.addWidget(self.rect_cutout_object_btn)
|
||||
|
||||
## Title5
|
||||
# Title5
|
||||
title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
|
||||
title_manual_label.setToolTip(
|
||||
_("This section handle creation of manual bridge gaps.\n"
|
||||
"This is done by mouse clicking on the perimeter of the\n"
|
||||
"Geometry object that is used as a cutout object. ")
|
||||
"This is done by mouse clicking on the perimeter of the\n"
|
||||
"Geometry object that is used as a cutout object. ")
|
||||
)
|
||||
self.layout.addWidget(title_manual_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form_layout_3 = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout_3)
|
||||
|
||||
## Manual Geo Object
|
||||
# Manual Geo Object
|
||||
self.man_object_combo = QtWidgets.QComboBox()
|
||||
self.man_object_combo.setModel(self.app.collection)
|
||||
self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
|
||||
@ -228,9 +229,9 @@ class CutOut(FlatCAMTool):
|
||||
self.man_geo_label = QtWidgets.QLabel(_("Manual Geo:"))
|
||||
self.man_geo_label.setToolTip(
|
||||
_("If the object to be cutout is a Gerber\n"
|
||||
"first create a Geometry that surrounds it,\n"
|
||||
"to be used as the cutout, if one doesn't exist yet.\n"
|
||||
"Select the source Gerber file in the top object combobox.")
|
||||
"first create a Geometry that surrounds it,\n"
|
||||
"to be used as the cutout, if one doesn't exist yet.\n"
|
||||
"Select the source Gerber file in the top object combobox.")
|
||||
)
|
||||
hlay3.addWidget(self.man_geo_label)
|
||||
|
||||
@ -238,9 +239,9 @@ class CutOut(FlatCAMTool):
|
||||
self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
||||
self.man_geo_creation_btn.setToolTip(
|
||||
_("If the object to be cutout is a Gerber\n"
|
||||
"first create a Geometry that surrounds it,\n"
|
||||
"to be used as the cutout, if one doesn't exist yet.\n"
|
||||
"Select the source Gerber file in the top object combobox.")
|
||||
"first create a Geometry that surrounds it,\n"
|
||||
"to be used as the cutout, if one doesn't exist yet.\n"
|
||||
"Select the source Gerber file in the top object combobox.")
|
||||
)
|
||||
hlay3.addWidget(self.man_geo_creation_btn)
|
||||
|
||||
@ -250,8 +251,8 @@ class CutOut(FlatCAMTool):
|
||||
self.man_bridge_gaps_label = QtWidgets.QLabel(_("Manual Add Bridge Gaps:"))
|
||||
self.man_bridge_gaps_label.setToolTip(
|
||||
_("Use the left mouse button (LMB) click\n"
|
||||
"to create a bridge gap to separate the PCB from\n"
|
||||
"the surrounding material.")
|
||||
"to create a bridge gap to separate the PCB from\n"
|
||||
"the surrounding material.")
|
||||
)
|
||||
hlay4.addWidget(self.man_bridge_gaps_label)
|
||||
|
||||
@ -259,10 +260,10 @@ class CutOut(FlatCAMTool):
|
||||
self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Generate Gap"))
|
||||
self.man_gaps_creation_btn.setToolTip(
|
||||
_("Use the left mouse button (LMB) click\n"
|
||||
"to create a bridge gap to separate the PCB from\n"
|
||||
"the surrounding material.\n"
|
||||
"The LMB click has to be done on the perimeter of\n"
|
||||
"the Geometry object used as a cutout geometry.")
|
||||
"to create a bridge gap to separate the PCB from\n"
|
||||
"the surrounding material.\n"
|
||||
"The LMB click has to be done on the perimeter of\n"
|
||||
"the Geometry object used as a cutout geometry.")
|
||||
)
|
||||
hlay4.addWidget(self.man_gaps_creation_btn)
|
||||
|
||||
@ -274,7 +275,9 @@ class CutOut(FlatCAMTool):
|
||||
# true if we want to repeat the gap without clicking again on the button
|
||||
self.repeat_gap = False
|
||||
|
||||
## Signals
|
||||
self.flat_geometry = []
|
||||
|
||||
# Signals
|
||||
self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
|
||||
self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
|
||||
|
||||
@ -325,9 +328,9 @@ class CutOut(FlatCAMTool):
|
||||
|
||||
def on_freeform_cutout(self):
|
||||
|
||||
def subtract_rectangle(obj_, x0, y0, x1, y1):
|
||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
obj_.subtract_polygon(pts)
|
||||
# def subtract_rectangle(obj_, x0, y0, x1, y1):
|
||||
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
# obj_.subtract_polygon(pts)
|
||||
|
||||
name = self.obj_combo.currentText()
|
||||
|
||||
@ -353,7 +356,6 @@ class CutOut(FlatCAMTool):
|
||||
"Add it and retry."))
|
||||
return
|
||||
|
||||
|
||||
if 0 in {dia}:
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
|
||||
return "Tool Diameter is zero value. Change it to a positive real number."
|
||||
@ -393,75 +395,89 @@ class CutOut(FlatCAMTool):
|
||||
|
||||
if cutout_obj.multigeo is True:
|
||||
self.app.inform.emit(_("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
|
||||
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
|
||||
"and after that perform Cutout."))
|
||||
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
|
||||
"and after that perform Cutout."))
|
||||
return
|
||||
|
||||
convex_box = self.convex_box.get_value()
|
||||
|
||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||
xmin, ymin, xmax, ymax = cutout_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax) + margin
|
||||
py = 0.5 * (ymin + ymax) + margin
|
||||
lenghtx = (xmax - xmin) + (margin * 2)
|
||||
lenghty = (ymax - ymin) + (margin * 2)
|
||||
|
||||
gapsize = gapsize / 2 + (dia / 2)
|
||||
|
||||
if isinstance(cutout_obj,FlatCAMGeometry):
|
||||
# rename the obj name so it can be identified as cutout
|
||||
cutout_obj.options["name"] += "_cutout"
|
||||
else:
|
||||
def geo_init(geo_obj, app_obj):
|
||||
def geo_init(geo_obj, app_obj):
|
||||
solid_geo = []
|
||||
|
||||
if isinstance(cutout_obj, FlatCAMGerber):
|
||||
if convex_box:
|
||||
geo = cutout_obj.solid_geometry.convex_hull
|
||||
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
|
||||
object_geo = cutout_obj.solid_geometry.convex_hull
|
||||
else:
|
||||
geo = cutout_obj.solid_geometry
|
||||
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
|
||||
object_geo = cutout_obj.solid_geometry
|
||||
else:
|
||||
object_geo = cutout_obj.solid_geometry
|
||||
|
||||
outname = cutout_obj.options["name"] + "_cutout"
|
||||
self.app.new_object('geometry', outname, geo_init)
|
||||
try:
|
||||
_ = iter(object_geo)
|
||||
except TypeError:
|
||||
object_geo = [object_geo]
|
||||
|
||||
cutout_obj = self.app.collection.get_by_name(outname)
|
||||
for geo in object_geo:
|
||||
if isinstance(cutout_obj, FlatCAMGerber):
|
||||
geo = (geo.buffer(margin + abs(dia / 2))).exterior
|
||||
|
||||
if gaps == '8' or gaps == '2LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||
xmin, ymin, xmax, ymax = geo.bounds
|
||||
px = 0.5 * (xmin + xmax) + margin
|
||||
py = 0.5 * (ymin + ymax) + margin
|
||||
lenx = (xmax - xmin) + (margin * 2)
|
||||
leny = (ymax - ymin) + (margin * 2)
|
||||
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
if gaps == '8' or gaps == '2LR':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + leny / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + leny / 4) # topright_y
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize - leny / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - leny / 4)
|
||||
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize + lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenx / 4,
|
||||
ymax + gapsize)
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize - lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
|
||||
try:
|
||||
for g in geo:
|
||||
solid_geo.append(g)
|
||||
except TypeError:
|
||||
solid_geo.append(geo)
|
||||
|
||||
geo_obj.solid_geometry = deepcopy(solid_geo)
|
||||
|
||||
outname = cutout_obj.options["name"] + "_cutout"
|
||||
self.app.new_object('geometry', outname, geo_init)
|
||||
|
||||
cutout_obj.plot()
|
||||
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
|
||||
@ -470,9 +486,9 @@ class CutOut(FlatCAMTool):
|
||||
|
||||
def on_rectangular_cutout(self):
|
||||
|
||||
def subtract_rectangle(obj_, x0, y0, x1, y1):
|
||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
obj_.subtract_polygon(pts)
|
||||
# def subtract_rectangle(obj_, x0, y0, x1, y1):
|
||||
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
# obj_.subtract_polygon(pts)
|
||||
|
||||
name = self.obj_combo.currentText()
|
||||
|
||||
@ -541,63 +557,82 @@ class CutOut(FlatCAMTool):
|
||||
return
|
||||
|
||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
||||
xmin, ymin, xmax, ymax = cutout_obj.bounds()
|
||||
geo = box(xmin, ymin, xmax, ymax)
|
||||
|
||||
px = 0.5 * (xmin + xmax) + margin
|
||||
py = 0.5 * (ymin + ymax) + margin
|
||||
lenghtx = (xmax - xmin) + (margin * 2)
|
||||
lenghty = (ymax - ymin) + (margin * 2)
|
||||
|
||||
gapsize = gapsize / 2 + (dia / 2)
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
|
||||
solid_geo = []
|
||||
object_geo = cutout_obj.solid_geometry
|
||||
|
||||
try:
|
||||
_ = iter(object_geo)
|
||||
except TypeError:
|
||||
object_geo = [object_geo]
|
||||
|
||||
for poly in object_geo:
|
||||
|
||||
xmin, ymin, xmax, ymax = poly.bounds
|
||||
geo = box(xmin, ymin, xmax, ymax)
|
||||
|
||||
# if Gerber create a buffer at a distance
|
||||
# if Geometry then cut through the geometry
|
||||
if isinstance(cutout_obj, FlatCAMGerber):
|
||||
geo = geo.buffer(margin + abs(dia / 2))
|
||||
|
||||
px = 0.5 * (xmin + xmax) + margin
|
||||
py = 0.5 * (ymin + ymax) + margin
|
||||
lenx = (xmax - xmin) + (margin * 2)
|
||||
leny = (ymax - ymin) + (margin * 2)
|
||||
|
||||
if gaps == '8' or gaps == '2LR':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + leny / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + leny / 4) # topright_y
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize - leny / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - leny / 4)
|
||||
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize + lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenx / 4,
|
||||
ymax + gapsize)
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize - lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
try:
|
||||
for g in geo:
|
||||
solid_geo.append(g)
|
||||
except TypeError:
|
||||
solid_geo.append(geo)
|
||||
|
||||
geo_obj.solid_geometry = deepcopy(solid_geo)
|
||||
|
||||
outname = cutout_obj.options["name"] + "_cutout"
|
||||
self.app.new_object('geometry', outname, geo_init)
|
||||
|
||||
cutout_obj = self.app.collection.get_by_name(outname)
|
||||
|
||||
if gaps == '8' or gaps == '2LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
|
||||
cutout_obj.plot()
|
||||
# cutout_obj.plot()
|
||||
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
||||
self.app.should_we_save = True
|
||||
@ -745,7 +780,14 @@ class CutOut(FlatCAMTool):
|
||||
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
|
||||
else:
|
||||
geo = cutout_obj.solid_geometry
|
||||
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)).exterior
|
||||
geo = geo.buffer(margin + abs(dia / 2))
|
||||
if isinstance(geo, Polygon):
|
||||
geo_obj.solid_geometry = geo.exterior
|
||||
elif isinstance(geo, MultiPolygon):
|
||||
solid_geo = []
|
||||
for poly in geo:
|
||||
solid_geo.append(poly.exterior)
|
||||
geo_obj.solid_geometry = deepcopy(solid_geo)
|
||||
|
||||
outname = cutout_obj.options["name"] + "_cutout"
|
||||
self.app.new_object('geometry', outname, geo_init)
|
||||
@ -819,5 +861,95 @@ class CutOut(FlatCAMTool):
|
||||
self.app.geo_editor.tool_shape.clear(update=True)
|
||||
self.app.geo_editor.tool_shape.enabled = False
|
||||
|
||||
def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
|
||||
"""
|
||||
Subtract polygon made from points from the given object.
|
||||
This only operates on the paths in the original geometry,
|
||||
i.e. it converts polygons into paths.
|
||||
|
||||
:param x0: x coord for lower left vertice of the polygon.
|
||||
:param y0: y coord for lower left vertice of the polygon.
|
||||
:param x1: x coord for upper right vertice of the polygon.
|
||||
:param y1: y coord for upper right vertice of the polygon.
|
||||
|
||||
:param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
|
||||
:return: none
|
||||
"""
|
||||
points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
|
||||
# pathonly should be allways True, otherwise polygons are not subtracted
|
||||
flat_geometry = flatten(geometry=solid_geo)
|
||||
|
||||
log.debug("%d paths" % len(flat_geometry))
|
||||
|
||||
polygon = Polygon(points)
|
||||
toolgeo = cascaded_union(polygon)
|
||||
diffs = []
|
||||
for target in flat_geometry:
|
||||
if type(target) == LineString or type(target) == LinearRing:
|
||||
diffs.append(target.difference(toolgeo))
|
||||
else:
|
||||
log.warning("Not implemented.")
|
||||
|
||||
return unary_union(diffs)
|
||||
|
||||
def reset_fields(self):
|
||||
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
|
||||
def flatten(geometry):
|
||||
"""
|
||||
Creates a list of non-iterable linear geometry objects.
|
||||
Polygons are expanded into its exterior and interiors.
|
||||
|
||||
Results are placed in self.flat_geometry
|
||||
|
||||
:param geometry: Shapely type or list or list of list of such.
|
||||
"""
|
||||
flat_geo = []
|
||||
try:
|
||||
for geo in geometry:
|
||||
if type(geo) == Polygon:
|
||||
flat_geo.append(geo.exterior)
|
||||
for subgeo in geo.interiors:
|
||||
flat_geo.append(subgeo)
|
||||
else:
|
||||
flat_geo.append(geo)
|
||||
except TypeError:
|
||||
if type(geometry) == Polygon:
|
||||
flat_geo.append(geometry.exterior)
|
||||
for subgeo in geometry.interiors:
|
||||
flat_geo.append(subgeo)
|
||||
else:
|
||||
flat_geo.append(geometry)
|
||||
|
||||
return flat_geo
|
||||
|
||||
|
||||
def recursive_bounds(geometry):
|
||||
"""
|
||||
Returns coordinates of rectangular bounds
|
||||
of geometry: (xmin, ymin, xmax, ymax).
|
||||
"""
|
||||
|
||||
# now it can get bounds for nested lists of objects
|
||||
|
||||
def bounds_rec(obj):
|
||||
try:
|
||||
minx = Inf
|
||||
miny = Inf
|
||||
maxx = -Inf
|
||||
maxy = -Inf
|
||||
|
||||
for k in obj:
|
||||
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
|
||||
minx = min(minx, minx_)
|
||||
miny = min(miny, miny_)
|
||||
maxx = max(maxx, maxx_)
|
||||
maxy = max(maxy, maxy_)
|
||||
return minx, miny, maxx, maxy
|
||||
except TypeError:
|
||||
# it's a Shapely object, return it's bounds
|
||||
return obj.bounds
|
||||
|
||||
return bounds_rec(geometry)
|
||||
|
@ -137,7 +137,7 @@ class ToolMove(FlatCAMTool):
|
||||
else:
|
||||
for sel_obj in obj_list:
|
||||
|
||||
# offset
|
||||
# offset solid_geometry
|
||||
sel_obj.offset((dx, dy))
|
||||
sel_obj.plot()
|
||||
|
||||
|
@ -97,7 +97,7 @@ class ToolPDF(FlatCAMTool):
|
||||
self.save_gs_re = re.compile(r'^q.*?$')
|
||||
|
||||
# detect restore graphic state from graphic stack
|
||||
self.restore_gs_re = re.compile(r'^Q.*$')
|
||||
self.restore_gs_re = re.compile(r'^.*Q.*$')
|
||||
|
||||
# graphic stack where we save parameters like transformation, line_width
|
||||
self.gs = dict()
|
||||
@ -219,8 +219,9 @@ class ToolPDF(FlatCAMTool):
|
||||
points = {}
|
||||
|
||||
def obj_init(exc_obj, app_obj):
|
||||
clear_geo = [geo_el['clear'] for geo_el in ap_dict['0']['geometry']]
|
||||
|
||||
for geo in ap_dict['0']['solid_geometry']:
|
||||
for geo in clear_geo:
|
||||
xmin, ymin, xmax, ymax = geo.bounds
|
||||
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
|
||||
|
||||
@ -280,12 +281,48 @@ class ToolPDF(FlatCAMTool):
|
||||
grb_obj.apertures = ap_dict
|
||||
|
||||
poly_buff = []
|
||||
follow_buf = []
|
||||
for ap in grb_obj.apertures:
|
||||
for k in grb_obj.apertures[ap]:
|
||||
if k == 'solid_geometry':
|
||||
poly_buff += ap_dict[ap][k]
|
||||
|
||||
if k == 'geometry':
|
||||
for geo_el in ap_dict[ap][k]:
|
||||
if 'solid' in geo_el:
|
||||
poly_buff.append(geo_el['solid'])
|
||||
if 'follow' in geo_el:
|
||||
follow_buf.append(geo_el['follow'])
|
||||
poly_buff = unary_union(poly_buff)
|
||||
|
||||
if '0' in grb_obj.apertures:
|
||||
global_clear_geo = []
|
||||
if 'geometry' in grb_obj.apertures['0']:
|
||||
for geo_el in ap_dict['0']['geometry']:
|
||||
if 'clear' in geo_el:
|
||||
global_clear_geo.append(geo_el['clear'])
|
||||
|
||||
if global_clear_geo:
|
||||
solid= []
|
||||
for apid in grb_obj.apertures:
|
||||
if 'geometry' in grb_obj.apertures[apid]:
|
||||
for elem in grb_obj.apertures[apid]['geometry']:
|
||||
if 'solid' in elem:
|
||||
solid_geo = deepcopy(elem['solid'])
|
||||
for clear_geo in global_clear_geo:
|
||||
# Make sure that the clear_geo is within the solid_geo otherwise we loose
|
||||
# the solid_geometry. We want for clear_geometry just to cut into solid_geometry
|
||||
# not to delete it
|
||||
if clear_geo.within(solid_geo):
|
||||
solid_geo = solid_geo.difference(clear_geo)
|
||||
if solid_geo.is_empty:
|
||||
solid_geo = elem['solid']
|
||||
try:
|
||||
for poly in solid_geo:
|
||||
solid.append(poly)
|
||||
except TypeError:
|
||||
solid.append(solid_geo)
|
||||
poly_buff = deepcopy(MultiPolygon(solid))
|
||||
|
||||
follow_buf = unary_union(follow_buf)
|
||||
|
||||
try:
|
||||
poly_buff = poly_buff.buffer(0.0000001)
|
||||
except ValueError:
|
||||
@ -296,6 +333,7 @@ class ToolPDF(FlatCAMTool):
|
||||
pass
|
||||
|
||||
grb_obj.solid_geometry = deepcopy(poly_buff)
|
||||
grb_obj.follow_geometry = deepcopy(follow_buf)
|
||||
|
||||
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
|
||||
|
||||
@ -416,7 +454,7 @@ class ToolPDF(FlatCAMTool):
|
||||
clear_apertures_dict['0'] = dict()
|
||||
clear_apertures_dict['0']['size'] = 0.0
|
||||
clear_apertures_dict['0']['type'] = 'C'
|
||||
clear_apertures_dict['0']['solid_geometry'] = []
|
||||
clear_apertures_dict['0']['geometry'] = []
|
||||
|
||||
# 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
|
||||
@ -430,7 +468,7 @@ class ToolPDF(FlatCAMTool):
|
||||
|
||||
for pline in lines:
|
||||
line_nr += 1
|
||||
# log.debug("line %d: %s" % (line_nr, pline))
|
||||
log.debug("line %d: %s" % (line_nr, pline))
|
||||
|
||||
# COLOR DETECTION / OBJECT DETECTION
|
||||
match = self.stroke_color_re.search(pline)
|
||||
@ -518,8 +556,6 @@ class ToolPDF(FlatCAMTool):
|
||||
# detect restore from graphic stack event
|
||||
match = self.restore_gs_re.search(pline)
|
||||
if match:
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> %s" % (line_nr, pline))
|
||||
try:
|
||||
restored_transform = self.gs['transform'].pop(-1)
|
||||
offset_geo = restored_transform[0]
|
||||
@ -535,6 +571,11 @@ class ToolPDF(FlatCAMTool):
|
||||
log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
|
||||
# nothing to remove
|
||||
pass
|
||||
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> "
|
||||
"restored_offset=[%f, %f] ||| restored_scale=[%f, %f]" %
|
||||
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
|
||||
# log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
|
||||
# log.debug("Restored Scale= [%f, %f]" % (scale_geo[0], scale_geo[1]))
|
||||
|
||||
@ -659,7 +700,7 @@ class ToolPDF(FlatCAMTool):
|
||||
subpath['lines'] = []
|
||||
subpath['bezier'] = []
|
||||
subpath['rectangle'] = []
|
||||
# it measns that we've already added the subpath to path and we need to delete it
|
||||
# it means that we've already added the subpath to path and we need to delete it
|
||||
# clipping path is usually either rectangle or lines
|
||||
if close_subpath is True:
|
||||
close_subpath = False
|
||||
@ -711,20 +752,25 @@ class ToolPDF(FlatCAMTool):
|
||||
if match:
|
||||
# scale the size here; some PDF printers apply transformation after the size is declared
|
||||
applied_size = size * scale_geo[0] * self.point_to_unit_factor
|
||||
|
||||
path_geo = list()
|
||||
if current_subpath == 'lines':
|
||||
if path['lines']:
|
||||
for subp in path['lines']:
|
||||
geo = copy(subp)
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['lines'] = []
|
||||
else:
|
||||
geo = copy(subpath['lines'])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['lines'] = []
|
||||
|
||||
if current_subpath == 'bezier':
|
||||
@ -733,30 +779,44 @@ class ToolPDF(FlatCAMTool):
|
||||
geo = []
|
||||
for b in subp:
|
||||
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2),
|
||||
resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['bezier'] = []
|
||||
else:
|
||||
geo = []
|
||||
for b in subpath['bezier']:
|
||||
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['bezier'] = []
|
||||
|
||||
if current_subpath == 'rectangle':
|
||||
if path['rectangle']:
|
||||
for subp in path['rectangle']:
|
||||
geo = copy(subp)
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2),
|
||||
resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['rectangle'] = []
|
||||
else:
|
||||
geo = copy(subpath['rectangle'])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
try:
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
path_geo.append(geo)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['rectangle'] = []
|
||||
|
||||
# store the found geometry
|
||||
@ -769,7 +829,18 @@ class ToolPDF(FlatCAMTool):
|
||||
break
|
||||
|
||||
if found_aperture:
|
||||
apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
|
||||
found_aperture = None
|
||||
else:
|
||||
if str(aperture) in apertures_dict.keys():
|
||||
@ -777,14 +848,36 @@ class ToolPDF(FlatCAMTool):
|
||||
apertures_dict[str(aperture)] = {}
|
||||
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
|
||||
apertures_dict[str(aperture)]['type'] = 'C'
|
||||
apertures_dict[str(aperture)]['solid_geometry'] = []
|
||||
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
|
||||
apertures_dict[str(aperture)]['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
apertures_dict[str(aperture)] = {}
|
||||
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
|
||||
apertures_dict[str(aperture)]['type'] = 'C'
|
||||
apertures_dict[str(aperture)]['solid_geometry'] = []
|
||||
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
|
||||
apertures_dict[str(aperture)]['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
|
||||
continue
|
||||
|
||||
@ -802,8 +895,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
path_geo.append(geo_el)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['lines'] = []
|
||||
else:
|
||||
@ -811,8 +907,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
path_geo.append(geo_el)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['lines'] = []
|
||||
|
||||
if current_subpath == 'bezier':
|
||||
@ -824,8 +923,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
path_geo.append(geo_el)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['bezier'] = []
|
||||
else:
|
||||
@ -833,8 +935,11 @@ class ToolPDF(FlatCAMTool):
|
||||
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
|
||||
if close_subpath is False:
|
||||
geo.append(start_point)
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['bezier'] = []
|
||||
|
||||
if current_subpath == 'rectangle':
|
||||
@ -844,8 +949,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# # 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# the path was painted therefore initialize it
|
||||
path['rectangle'] = []
|
||||
else:
|
||||
@ -853,32 +961,96 @@ class ToolPDF(FlatCAMTool):
|
||||
# # 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
subpath['rectangle'] = []
|
||||
|
||||
# we finished painting and also closed the path if it was the case
|
||||
close_subpath = True
|
||||
|
||||
# if there was a fill color change we look for circular geometries from which we can make drill holes
|
||||
# for the Excellon file
|
||||
# in case that a color change to white (transparent) occurred
|
||||
if flag_clear_geo is True:
|
||||
# we llok for circular geometries
|
||||
# if there was a fill color change we look for circular geometries from which we can make
|
||||
# drill holes for the Excellon file
|
||||
if current_subpath == 'bezier':
|
||||
# if there are geometries in the list
|
||||
if path_geo:
|
||||
clear_apertures_dict['0']['solid_geometry'] += path_geo
|
||||
else:
|
||||
# else, add the geometry as usual
|
||||
try:
|
||||
for g in path_geo:
|
||||
new_el = dict()
|
||||
new_el['clear'] = g
|
||||
clear_apertures_dict['0']['geometry'].append(new_el)
|
||||
except TypeError:
|
||||
new_el = dict()
|
||||
new_el['clear'] = path_geo
|
||||
clear_apertures_dict['0']['geometry'].append(new_el)
|
||||
|
||||
# now that we finished searching for drill holes (this is not very precise because holes in the
|
||||
# polygon pours may appear as drill too, but .. hey you can't have it all ...) we add
|
||||
# clear_geometry
|
||||
try:
|
||||
apertures_dict['0']['solid_geometry'] += path_geo
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['clear'] = poly
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['clear'] = pdf_geo
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
except KeyError:
|
||||
# in case there is no stroke width yet therefore no aperture
|
||||
apertures_dict['0'] = {}
|
||||
apertures_dict['0']['size'] = applied_size
|
||||
apertures_dict['0']['type'] = 'C'
|
||||
apertures_dict['0']['solid_geometry'] = []
|
||||
apertures_dict['0']['solid_geometry'] += path_geo
|
||||
apertures_dict['0']['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['clear'] = poly
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['clear'] = pdf_geo
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
# else, add the geometry as usual
|
||||
try:
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
except KeyError:
|
||||
# in case there is no stroke width yet therefore no aperture
|
||||
apertures_dict['0'] = {}
|
||||
apertures_dict['0']['size'] = applied_size
|
||||
apertures_dict['0']['type'] = 'C'
|
||||
apertures_dict['0']['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
continue
|
||||
|
||||
# Fill and Stroke the path
|
||||
@ -897,8 +1069,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
for subp in path['lines']:
|
||||
geo = copy(subp)
|
||||
@ -912,8 +1087,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
geo = copy(subpath['lines'])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
@ -931,8 +1109,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
for subp in path['bezier']:
|
||||
geo = []
|
||||
@ -948,8 +1129,11 @@ class ToolPDF(FlatCAMTool):
|
||||
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
|
||||
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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
geo = []
|
||||
for b in subpath['bezier']:
|
||||
@ -966,8 +1150,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# # 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
for subp in path['rectangle']:
|
||||
geo = copy(subp)
|
||||
@ -981,8 +1168,11 @@ class ToolPDF(FlatCAMTool):
|
||||
# # 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)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
fill_geo.append(geo_el)
|
||||
except ValueError:
|
||||
pass
|
||||
# stroke
|
||||
geo = copy(subpath['rectangle'])
|
||||
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
|
||||
@ -1002,7 +1192,18 @@ class ToolPDF(FlatCAMTool):
|
||||
break
|
||||
|
||||
if found_aperture:
|
||||
apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
|
||||
found_aperture = None
|
||||
else:
|
||||
if str(aperture) in apertures_dict.keys():
|
||||
@ -1010,25 +1211,102 @@ class ToolPDF(FlatCAMTool):
|
||||
apertures_dict[str(aperture)] = {}
|
||||
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
|
||||
apertures_dict[str(aperture)]['type'] = 'C'
|
||||
apertures_dict[str(aperture)]['solid_geometry'] = []
|
||||
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
|
||||
apertures_dict[str(aperture)]['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
apertures_dict[str(aperture)] = {}
|
||||
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
|
||||
apertures_dict[str(aperture)]['type'] = 'C'
|
||||
apertures_dict[str(aperture)]['solid_geometry'] = []
|
||||
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
|
||||
apertures_dict[str(aperture)]['geometry'] = []
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
|
||||
|
||||
# store the found geometry for filling the path
|
||||
try:
|
||||
apertures_dict['0']['solid_geometry'] += fill_geo
|
||||
except KeyError:
|
||||
# in case there is no stroke width yet therefore no aperture
|
||||
apertures_dict['0'] = {}
|
||||
apertures_dict['0']['size'] = round(applied_size, 5)
|
||||
apertures_dict['0']['type'] = 'C'
|
||||
apertures_dict['0']['solid_geometry'] = []
|
||||
apertures_dict['0']['solid_geometry'] += fill_geo
|
||||
# ###############################################
|
||||
# store the found geometry for filling the path #
|
||||
# ###############################################
|
||||
|
||||
# in case that a color change to white (transparent) occurred
|
||||
if flag_clear_geo is True:
|
||||
try:
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in fill_geo:
|
||||
new_el = dict()
|
||||
new_el['clear'] = poly
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['clear'] = pdf_geo
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
except KeyError:
|
||||
# in case there is no stroke width yet therefore no aperture
|
||||
apertures_dict['0'] = {}
|
||||
apertures_dict['0']['size'] = round(applied_size, 5)
|
||||
apertures_dict['0']['type'] = 'C'
|
||||
apertures_dict['0']['geometry'] = []
|
||||
for pdf_geo in fill_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['clear'] = poly
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['clear'] = pdf_geo
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
try:
|
||||
for pdf_geo in path_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in fill_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
except KeyError:
|
||||
# in case there is no stroke width yet therefore no aperture
|
||||
apertures_dict['0'] = {}
|
||||
apertures_dict['0']['size'] = round(applied_size, 5)
|
||||
apertures_dict['0']['type'] = 'C'
|
||||
apertures_dict['0']['geometry'] = []
|
||||
for pdf_geo in fill_geo:
|
||||
if isinstance(pdf_geo, MultiPolygon):
|
||||
for poly in pdf_geo:
|
||||
new_el = dict()
|
||||
new_el['solid'] = poly
|
||||
new_el['follow'] = poly.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
else:
|
||||
new_el = dict()
|
||||
new_el['solid'] = pdf_geo
|
||||
new_el['follow'] = pdf_geo.exterior
|
||||
apertures_dict['0']['geometry'].append(deepcopy(new_el))
|
||||
|
||||
continue
|
||||
|
||||
@ -1036,7 +1314,7 @@ class ToolPDF(FlatCAMTool):
|
||||
if apertures_dict:
|
||||
object_dict[layer_nr] = deepcopy(apertures_dict)
|
||||
|
||||
if clear_apertures_dict['0']['solid_geometry']:
|
||||
if clear_apertures_dict['0']['geometry']:
|
||||
object_dict[0] = deepcopy(clear_apertures_dict)
|
||||
|
||||
# delete keys (layers) with empty values
|
||||
|
@ -795,7 +795,11 @@ class ToolPaint(FlatCAMTool, Gerber):
|
||||
if event.button == 1:
|
||||
self.app.inform.emit(_("Painting polygon..."))
|
||||
self.app.plotcanvas.vis_disconnect('mouse_press', doit)
|
||||
|
||||
pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
|
||||
if self.app.grid_status():
|
||||
pos = self.app.geo_editor.snap(pos[0], pos[1])
|
||||
|
||||
self.paint_poly(self.paint_obj,
|
||||
inside_pt=[pos[0], pos[1]],
|
||||
tooldia=tooldia,
|
||||
@ -827,7 +831,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
||||
|
||||
# Which polygon.
|
||||
# poly = find_polygon(self.solid_geometry, inside_pt)
|
||||
poly = obj.find_polygon(inside_pt)
|
||||
poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
|
||||
paint_method = self.paintmethod_combo.get_value()
|
||||
|
||||
try:
|
||||
|
@ -13,9 +13,9 @@ import time
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
import builtins
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
@ -39,11 +39,11 @@ class Panelize(FlatCAMTool):
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
## Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
# Form Layout
|
||||
form_layout_0 = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout_0)
|
||||
|
||||
## Type of object to be panelized
|
||||
# Type of object to be panelized
|
||||
self.type_obj_combo = QtWidgets.QComboBox()
|
||||
self.type_obj_combo.addItem("Gerber")
|
||||
self.type_obj_combo.addItem("Excellon")
|
||||
@ -56,13 +56,13 @@ class Panelize(FlatCAMTool):
|
||||
self.type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
|
||||
self.type_obj_combo_label.setToolTip(
|
||||
_("Specify the type of object to be panelized\n"
|
||||
"It can be of type: Gerber, Excellon or Geometry.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Object combobox.")
|
||||
"It can be of type: Gerber, Excellon or Geometry.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Object combobox.")
|
||||
)
|
||||
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
||||
form_layout_0.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
||||
|
||||
## Object to be panelized
|
||||
# Object to be panelized
|
||||
self.object_combo = QtWidgets.QComboBox()
|
||||
self.object_combo.setModel(self.app.collection)
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
@ -71,11 +71,33 @@ class Panelize(FlatCAMTool):
|
||||
self.object_label = QtWidgets.QLabel(_("Object:"))
|
||||
self.object_label.setToolTip(
|
||||
_("Object to be panelized. This means that it will\n"
|
||||
"be duplicated in an array of rows and columns.")
|
||||
"be duplicated in an array of rows and columns.")
|
||||
)
|
||||
form_layout.addRow(self.object_label, self.object_combo)
|
||||
form_layout_0.addRow(self.object_label, self.object_combo)
|
||||
form_layout_0.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
## Type of Box Object to be used as an envelope for panelization
|
||||
# Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
|
||||
# Type of box Panel object
|
||||
self.reference_radio = RadioSet([{'label': 'Object', 'value': 'object'},
|
||||
{'label': 'Bounding Box', 'value': 'bbox'}])
|
||||
self.box_label = QtWidgets.QLabel(_("<b>Penelization Reference:</b>"))
|
||||
self.box_label.setToolTip(
|
||||
_("Choose the reference for panelization:\n"
|
||||
"- Object = the bounding box of a different object\n"
|
||||
"- Bounding Box = the bounding box of the object to be panelized\n"
|
||||
"\n"
|
||||
"The reference is useful when doing panelization for more than one\n"
|
||||
"object. The spacings (really offsets) will be applied in reference\n"
|
||||
"to this reference object therefore maintaining the panelized\n"
|
||||
"objects in sync.")
|
||||
)
|
||||
form_layout.addRow(self.box_label)
|
||||
form_layout.addRow(self.reference_radio)
|
||||
|
||||
# Type of Box Object to be used as an envelope for panelization
|
||||
self.type_box_combo = QtWidgets.QComboBox()
|
||||
self.type_box_combo.addItem("Gerber")
|
||||
self.type_box_combo.addItem("Excellon")
|
||||
@ -89,13 +111,13 @@ class Panelize(FlatCAMTool):
|
||||
self.type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
|
||||
self.type_box_combo_label.setToolTip(
|
||||
_("Specify the type of object to be used as an container for\n"
|
||||
"panelization. It can be: Gerber or Geometry type.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Box Object combobox.")
|
||||
"panelization. It can be: Gerber or Geometry type.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Box Object combobox.")
|
||||
)
|
||||
form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
|
||||
|
||||
## Box
|
||||
# Box
|
||||
self.box_combo = QtWidgets.QComboBox()
|
||||
self.box_combo.setModel(self.app.collection)
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
@ -104,29 +126,41 @@ class Panelize(FlatCAMTool):
|
||||
self.box_combo_label = QtWidgets.QLabel(_("Box Object:"))
|
||||
self.box_combo_label.setToolTip(
|
||||
_("The actual object that is used a container for the\n "
|
||||
"selected object that is to be panelized.")
|
||||
"selected object that is to be panelized.")
|
||||
)
|
||||
form_layout.addRow(self.box_combo_label, self.box_combo)
|
||||
form_layout.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
## Spacing Columns
|
||||
panel_data_label = QtWidgets.QLabel(_("<b>Panel Data:</b>"))
|
||||
panel_data_label.setToolTip(
|
||||
_("This informations will shape the resulting panel.\n"
|
||||
"The number of rows and columns will set how many\n"
|
||||
"duplicates of the original geometry will be generated.\n"
|
||||
"\n"
|
||||
"The spacings will set the distance between any two\n"
|
||||
"elements of the panel array.")
|
||||
)
|
||||
form_layout.addRow(panel_data_label)
|
||||
|
||||
# Spacing Columns
|
||||
self.spacing_columns = FCEntry()
|
||||
self.spacing_columns_label = QtWidgets.QLabel(_("Spacing cols:"))
|
||||
self.spacing_columns_label.setToolTip(
|
||||
_("Spacing between columns of the desired panel.\n"
|
||||
"In current units.")
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
||||
|
||||
## Spacing Rows
|
||||
# Spacing Rows
|
||||
self.spacing_rows = FCEntry()
|
||||
self.spacing_rows_label = QtWidgets.QLabel(_("Spacing rows:"))
|
||||
self.spacing_rows_label.setToolTip(
|
||||
_("Spacing between rows of the desired panel.\n"
|
||||
"In current units.")
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
||||
|
||||
## Columns
|
||||
# Columns
|
||||
self.columns = FCEntry()
|
||||
self.columns_label = QtWidgets.QLabel(_("Columns:"))
|
||||
self.columns_label.setToolTip(
|
||||
@ -134,34 +168,35 @@ class Panelize(FlatCAMTool):
|
||||
)
|
||||
form_layout.addRow(self.columns_label, self.columns)
|
||||
|
||||
## Rows
|
||||
# Rows
|
||||
self.rows = FCEntry()
|
||||
self.rows_label = QtWidgets.QLabel(_("Rows:"))
|
||||
self.rows_label.setToolTip(
|
||||
_("Number of rows of the desired panel")
|
||||
)
|
||||
form_layout.addRow(self.rows_label, self.rows)
|
||||
form_layout.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
## Type of resulting Panel object
|
||||
# Type of resulting Panel object
|
||||
self.panel_type_radio = RadioSet([{'label': 'Gerber', 'value': 'gerber'},
|
||||
{'label': 'Geometry', 'value': 'geometry'}])
|
||||
self.panel_type_label = QtWidgets.QLabel(_("Panel Type:"))
|
||||
{'label': 'Geometry', 'value': 'geometry'}])
|
||||
self.panel_type_label = QtWidgets.QLabel(_("<b>Panel Type:</b>"))
|
||||
self.panel_type_label.setToolTip(
|
||||
_("Choose the type of object for the panel object:\n"
|
||||
"- Geometry\n"
|
||||
"- Gerber")
|
||||
"- Geometry\n"
|
||||
"- Gerber")
|
||||
)
|
||||
form_layout.addRow(self.panel_type_label)
|
||||
form_layout.addRow(self.panel_type_radio)
|
||||
|
||||
## Constrains
|
||||
# Constrains
|
||||
self.constrain_cb = FCCheckBox(_("Constrain panel within:"))
|
||||
self.constrain_cb.setToolTip(
|
||||
_("Area define by DX and DY within to constrain the panel.\n"
|
||||
"DX and DY values are in current units.\n"
|
||||
"Regardless of how many columns and rows are desired,\n"
|
||||
"the final panel will have as many columns and rows as\n"
|
||||
"they fit completely within selected area.")
|
||||
"DX and DY values are in current units.\n"
|
||||
"Regardless of how many columns and rows are desired,\n"
|
||||
"the final panel will have as many columns and rows as\n"
|
||||
"they fit completely within selected area.")
|
||||
)
|
||||
form_layout.addRow(self.constrain_cb)
|
||||
|
||||
@ -169,7 +204,7 @@ class Panelize(FlatCAMTool):
|
||||
self.x_width_lbl = QtWidgets.QLabel(_("Width (DX):"))
|
||||
self.x_width_lbl.setToolTip(
|
||||
_("The width (DX) within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
||||
|
||||
@ -177,14 +212,14 @@ class Panelize(FlatCAMTool):
|
||||
self.y_height_lbl = QtWidgets.QLabel(_("Height (DY):"))
|
||||
self.y_height_lbl.setToolTip(
|
||||
_("The height (DY)within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.y_height_lbl, self.y_height_entry)
|
||||
|
||||
self.constrain_sel = OptionalInputSection(
|
||||
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
||||
|
||||
## Buttons
|
||||
# Buttons
|
||||
hlay_2 = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay_2)
|
||||
|
||||
@ -192,14 +227,15 @@ class Panelize(FlatCAMTool):
|
||||
self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
|
||||
self.panelize_object_button.setToolTip(
|
||||
_("Panelize the specified object around the specified box.\n"
|
||||
"In other words it creates multiple copies of the source object,\n"
|
||||
"arranged in a 2D array of rows and columns.")
|
||||
"In other words it creates multiple copies of the source object,\n"
|
||||
"arranged in a 2D array of rows and columns.")
|
||||
)
|
||||
hlay_2.addWidget(self.panelize_object_button)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
## Signals
|
||||
# Signals
|
||||
self.reference_radio.activated_custom.connect(self.on_reference_radio_changed)
|
||||
self.panelize_object_button.clicked.connect(self.on_panelize)
|
||||
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
self.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
||||
@ -241,6 +277,8 @@ class Panelize(FlatCAMTool):
|
||||
def set_tool_ui(self):
|
||||
self.reset_fields()
|
||||
|
||||
self.reference_radio.set_value('bbox')
|
||||
|
||||
sp_c = self.app.defaults["tools_panelize_spacing_columns"] if \
|
||||
self.app.defaults["tools_panelize_spacing_columns"] else 0.0
|
||||
self.spacing_columns.set_value(float(sp_c))
|
||||
@ -278,11 +316,32 @@ class Panelize(FlatCAMTool):
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.object_combo.setCurrentIndex(0)
|
||||
|
||||
# hide the panel type for Excellons, the panel can be only of type Geometry
|
||||
if self.type_obj_combo.currentText() != 'Excellon':
|
||||
self.panel_type_label.setDisabled(False)
|
||||
self.panel_type_radio.setDisabled(False)
|
||||
else:
|
||||
self.panel_type_label.setDisabled(True)
|
||||
self.panel_type_radio.setDisabled(True)
|
||||
self.panel_type_radio.set_value('geometry')
|
||||
|
||||
def on_type_box_index_changed(self):
|
||||
obj_type = self.type_box_combo.currentIndex()
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.setCurrentIndex(0)
|
||||
|
||||
def on_reference_radio_changed(self, current_val):
|
||||
if current_val == 'object':
|
||||
self.type_box_combo.setDisabled(False)
|
||||
self.type_box_combo_label.setDisabled(False)
|
||||
self.box_combo.setDisabled(False)
|
||||
self.box_combo_label.setDisabled(False)
|
||||
else:
|
||||
self.type_box_combo.setDisabled(True)
|
||||
self.type_box_combo_label.setDisabled(True)
|
||||
self.box_combo.setDisabled(True)
|
||||
self.box_combo_label.setDisabled(True)
|
||||
|
||||
def on_panelize(self):
|
||||
name = self.object_combo.currentText()
|
||||
|
||||
@ -308,7 +367,10 @@ class Panelize(FlatCAMTool):
|
||||
return "Could not retrieve object: %s" % boxname
|
||||
|
||||
if box is None:
|
||||
self.app.inform.emit(_("[WARNING]No object Box. Using instead %s") % panel_obj)
|
||||
self.app.inform.emit(_("[WARNING_NOTCL]No object Box. Using instead %s") % panel_obj)
|
||||
self.reference_radio.set_value('bbox')
|
||||
|
||||
if self.reference_radio.get_value() == 'bbox':
|
||||
box = panel_obj
|
||||
|
||||
self.outname = name + '_panelized'
|
||||
@ -387,7 +449,6 @@ class Panelize(FlatCAMTool):
|
||||
|
||||
panel_type = str(self.panel_type_radio.get_value())
|
||||
|
||||
|
||||
if 0 in {columns, rows}:
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Columns or Rows are zero value. Change them to a positive integer."))
|
||||
return "Columns or Rows are zero value. Change them to a positive integer."
|
||||
@ -471,7 +532,11 @@ class Panelize(FlatCAMTool):
|
||||
if type(geom) == list:
|
||||
geoms = list()
|
||||
for local_geom in geom:
|
||||
geoms.append(translate_recursion(local_geom))
|
||||
res_geo = translate_recursion(local_geom)
|
||||
try:
|
||||
geoms += (res_geo)
|
||||
except TypeError:
|
||||
geoms.append(res_geo)
|
||||
return geoms
|
||||
else:
|
||||
return affinity.translate(geom, xoff=currentx, yoff=currenty)
|
||||
@ -485,6 +550,16 @@ class Panelize(FlatCAMTool):
|
||||
for tool in panel_obj.tools:
|
||||
obj_fin.tools[tool]['solid_geometry'][:] = []
|
||||
|
||||
if isinstance(panel_obj, FlatCAMGerber):
|
||||
obj_fin.apertures = deepcopy(panel_obj.apertures)
|
||||
for ap in obj_fin.apertures:
|
||||
if 'solid_geometry' in obj_fin.apertures[ap]:
|
||||
obj_fin.apertures[ap]['solid_geometry'] = []
|
||||
if 'clear_geometry' in obj_fin.apertures[ap]:
|
||||
obj_fin.apertures[ap]['clear_geometry'] = []
|
||||
if 'follow_geometry' in obj_fin.apertures[ap]:
|
||||
obj_fin.apertures[ap]['follow_geometry'] = []
|
||||
|
||||
self.app.progress.emit(0)
|
||||
for row in range(rows):
|
||||
currentx = 0.0
|
||||
@ -493,21 +568,54 @@ class Panelize(FlatCAMTool):
|
||||
if isinstance(panel_obj, FlatCAMGeometry):
|
||||
if panel_obj.multigeo is True:
|
||||
for tool in panel_obj.tools:
|
||||
obj_fin.tools[tool]['solid_geometry'].append(translate_recursion(
|
||||
panel_obj.tools[tool]['solid_geometry'])
|
||||
)
|
||||
geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
|
||||
if isinstance(geo, list):
|
||||
obj_fin.tools[tool]['solid_geometry'] += geo
|
||||
else:
|
||||
obj_fin.tools[tool]['solid_geometry'].append(geo)
|
||||
else:
|
||||
obj_fin.solid_geometry.append(
|
||||
translate_recursion(panel_obj.solid_geometry)
|
||||
)
|
||||
geo = translate_recursion(panel_obj.solid_geometry)
|
||||
if isinstance(geo, list):
|
||||
obj_fin.solid_geometry += geo
|
||||
else:
|
||||
obj_fin.solid_geometry.append(geo)
|
||||
else:
|
||||
obj_fin.solid_geometry.append(
|
||||
translate_recursion(panel_obj.solid_geometry)
|
||||
)
|
||||
geo = translate_recursion(panel_obj.solid_geometry)
|
||||
if isinstance(geo, list):
|
||||
obj_fin.solid_geometry += geo
|
||||
else:
|
||||
obj_fin.solid_geometry.append(geo)
|
||||
|
||||
for apid in panel_obj.apertures:
|
||||
if 'solid_geometry' in panel_obj.apertures[apid]:
|
||||
geo_aper = translate_recursion(panel_obj.apertures[apid]['solid_geometry'])
|
||||
if isinstance(geo_aper, list):
|
||||
obj_fin.apertures[apid]['solid_geometry'] += geo_aper
|
||||
else:
|
||||
obj_fin.apertures[apid]['solid_geometry'].append(geo_aper)
|
||||
|
||||
if 'clear_geometry' in panel_obj.apertures[apid]:
|
||||
geo_aper = translate_recursion(panel_obj.apertures[apid]['clear_geometry'])
|
||||
if isinstance(geo_aper, list):
|
||||
obj_fin.apertures[apid]['clear_geometry'] += geo_aper
|
||||
else:
|
||||
obj_fin.apertures[apid]['clear_geometry'].append(geo_aper)
|
||||
|
||||
if 'follow_geometry' in panel_obj.apertures[apid]:
|
||||
geo_aper = translate_recursion(panel_obj.apertures[apid]['follow_geometry'])
|
||||
if isinstance(geo_aper, list):
|
||||
obj_fin.apertures[apid]['follow_geometry'] += geo_aper
|
||||
else:
|
||||
obj_fin.apertures[apid]['follow_geometry'].append(geo_aper)
|
||||
|
||||
currentx += lenghtx
|
||||
currenty += lenghty
|
||||
|
||||
app_obj.log.debug("Found %s geometries. Creating a panel geometry cascaded union ..." %
|
||||
len(obj_fin.solid_geometry))
|
||||
obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
|
||||
app_obj.log.debug("Finished creating a cascaded union for the panel.")
|
||||
|
||||
if isinstance(panel_obj, FlatCAMExcellon):
|
||||
self.app.progress.emit(50)
|
||||
self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
|
||||
@ -520,7 +628,8 @@ class Panelize(FlatCAMTool):
|
||||
self.app.inform.emit(_("[success] Panel done..."))
|
||||
else:
|
||||
self.constrain_flag = False
|
||||
self.app.inform.emit(_("[WARNING] Too big for the constrain area. Final panel has {col} columns and {row} rows").format(
|
||||
self.app.inform.emit(_("[WARNING] Too big for the constrain area. "
|
||||
"Final panel has {col} columns and {row} rows").format(
|
||||
col=columns, row=rows))
|
||||
|
||||
proc = self.app.proc_container.new(_("Generating panel ... Please wait."))
|
||||
|
@ -175,16 +175,27 @@ class Properties(FlatCAMTool):
|
||||
for ap in obj.apertures:
|
||||
temp_ap.clear()
|
||||
temp_ap = deepcopy(obj.apertures[ap])
|
||||
if obj.apertures[ap]['solid_geometry']:
|
||||
elems = len(obj.apertures[ap]['solid_geometry'])
|
||||
temp_ap['solid_geometry'] = '%s Polygons' % str(elems)
|
||||
try:
|
||||
if obj.apertures[ap]['follow_geometry']:
|
||||
elems = len(obj.apertures[ap]['follow_geometry'])
|
||||
temp_ap['follow_geometry'] = '%s Polygons' % str(elems)
|
||||
except KeyError:
|
||||
pass
|
||||
self.addChild(apertures, [str(ap), str(temp_ap)], True)
|
||||
temp_ap.pop('geometry', None)
|
||||
if obj.apertures[ap]['geometry']:
|
||||
solid_nr = 0
|
||||
follow_nr = 0
|
||||
clear_nr = 0
|
||||
|
||||
for el in obj.apertures[ap]['geometry']:
|
||||
if 'solid' in el:
|
||||
solid_nr += 1
|
||||
if 'follow' in el:
|
||||
follow_nr += 1
|
||||
if 'clear' in el:
|
||||
clear_nr += 1
|
||||
temp_ap['Solid_Geo'] = '%s Polygons' % str(solid_nr)
|
||||
temp_ap['Follow_Geo'] = '%s LineStrings' % str(follow_nr)
|
||||
temp_ap['Clear_Geo'] = '%s Polygons' % str(clear_nr)
|
||||
|
||||
apid = self.addParent(apertures, str(ap), expanded=False, color=QtGui.QColor("#000000"), font=font)
|
||||
for key in temp_ap:
|
||||
self.addChild(apid, [str(key), str(temp_ap[key])], True)
|
||||
|
||||
elif obj.kind.lower() == 'excellon':
|
||||
for tool, value in obj.tools.items():
|
||||
self.addChild(tools, [str(tool), str(value['C'])], True)
|
||||
|
@ -1390,6 +1390,8 @@ class SolderPaste(FlatCAMTool):
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] No such file or directory"))
|
||||
return
|
||||
|
||||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("gcode", filename)
|
||||
self.app.file_saved.emit("gcode", filename)
|
||||
self.app.inform.emit(_("[success] Solder paste dispenser GCode file saved to: %s") % filename)
|
||||
|
||||
|
@ -151,7 +151,10 @@ class ToolSub(FlatCAMTool):
|
||||
self.new_tools = {}
|
||||
self.new_solid_geometry = []
|
||||
|
||||
self.sub_union = None
|
||||
self.sub_solid_union = None
|
||||
self.sub_follow_union = None
|
||||
self.sub_clear_union = None
|
||||
|
||||
|
||||
self.sub_grb_obj = None
|
||||
self.sub_grb_obj_name = None
|
||||
@ -251,12 +254,25 @@ class ToolSub(FlatCAMTool):
|
||||
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'] = []
|
||||
self.new_apertures[apid]['geometry'] = []
|
||||
|
||||
geo_solid_union_list = []
|
||||
geo_follow_union_list = []
|
||||
geo_clear_union_list = []
|
||||
|
||||
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)
|
||||
if 'geometry' in self.sub_grb_obj.apertures[apid1]:
|
||||
for elem in self.sub_grb_obj.apertures[apid1]['geometry']:
|
||||
if 'solid' in elem:
|
||||
geo_solid_union_list.append(elem['solid'])
|
||||
if 'follow' in elem:
|
||||
geo_follow_union_list.append(elem['follow'])
|
||||
if 'clear' in elem:
|
||||
geo_clear_union_list.append(elem['clear'])
|
||||
|
||||
self.sub_solid_union = cascaded_union(geo_solid_union_list)
|
||||
self.sub_follow_union = cascaded_union(geo_follow_union_list)
|
||||
self.sub_clear_union = cascaded_union(geo_clear_union_list)
|
||||
|
||||
# add the promises
|
||||
for apid in self.target_grb_obj.apertures:
|
||||
@ -266,32 +282,78 @@ class ToolSub(FlatCAMTool):
|
||||
self.periodic_check(500, reset=True)
|
||||
|
||||
for apid in self.target_grb_obj.apertures:
|
||||
geo = self.target_grb_obj.apertures[apid]['solid_geometry']
|
||||
geo = self.target_grb_obj.apertures[apid]['geometry']
|
||||
self.app.worker_task.emit({'fcn': self.aperture_intersection,
|
||||
'params': [apid, geo]})
|
||||
|
||||
def aperture_intersection(self, apid, geo):
|
||||
new_solid_geometry = []
|
||||
new_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)
|
||||
for geo_el in geo:
|
||||
new_el = dict()
|
||||
|
||||
if new_solid_geometry:
|
||||
while not self.new_apertures[apid]['solid_geometry']:
|
||||
self.new_apertures[apid]['solid_geometry'] = deepcopy(new_solid_geometry)
|
||||
if 'solid' in geo_el:
|
||||
work_geo = geo_el['solid']
|
||||
if self.sub_solid_union:
|
||||
if work_geo.intersects(self.sub_solid_union):
|
||||
new_geo = work_geo.difference(self.sub_solid_union)
|
||||
new_geo = new_geo.buffer(0)
|
||||
if new_geo:
|
||||
if not new_geo.is_empty:
|
||||
new_el['solid'] = new_geo
|
||||
else:
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_el['solid'] = work_geo
|
||||
|
||||
if 'follow' in geo_el:
|
||||
work_geo = geo_el['follow']
|
||||
if self.sub_follow_union:
|
||||
if work_geo.intersects(self.sub_follow_union):
|
||||
new_geo = work_geo.difference(self.sub_follow_union)
|
||||
new_geo = new_geo.buffer(0)
|
||||
if new_geo:
|
||||
if not new_geo.is_empty:
|
||||
new_el['follow'] = new_geo
|
||||
else:
|
||||
new_el['follow'] = work_geo
|
||||
else:
|
||||
new_el['follow'] = work_geo
|
||||
else:
|
||||
new_el['follow'] = work_geo
|
||||
else:
|
||||
new_el['follow'] = work_geo
|
||||
|
||||
if 'clear' in geo_el:
|
||||
work_geo = geo_el['clear']
|
||||
if self.sub_clear_union:
|
||||
if work_geo.intersects(self.sub_clear_union):
|
||||
new_geo = work_geo.difference(self.sub_clear_union)
|
||||
new_geo = new_geo.buffer(0)
|
||||
if new_geo:
|
||||
if not new_geo.is_empty:
|
||||
new_el['clear'] = new_geo
|
||||
else:
|
||||
new_el['clear'] = work_geo
|
||||
else:
|
||||
new_el['clear'] = work_geo
|
||||
else:
|
||||
new_el['clear'] = work_geo
|
||||
else:
|
||||
new_el['clear'] = work_geo
|
||||
|
||||
new_geometry.append(deepcopy(new_el))
|
||||
|
||||
if new_geometry:
|
||||
while not self.new_apertures[apid]['geometry']:
|
||||
self.new_apertures[apid]['geometry'] = deepcopy(new_geometry)
|
||||
time.sleep(0.5)
|
||||
|
||||
while True:
|
||||
@ -312,9 +374,11 @@ class ToolSub(FlatCAMTool):
|
||||
grb_obj.apertures = deepcopy(self.new_apertures)
|
||||
|
||||
poly_buff = []
|
||||
follow_buff = []
|
||||
for ap in self.new_apertures:
|
||||
for poly in self.new_apertures[ap]['solid_geometry']:
|
||||
poly_buff.append(poly)
|
||||
for elem in self.new_apertures[ap]['geometry']:
|
||||
poly_buff.append(elem['solid'])
|
||||
follow_buff.append(elem['follow'])
|
||||
|
||||
work_poly_buff = cascaded_union(poly_buff)
|
||||
try:
|
||||
@ -327,14 +391,14 @@ class ToolSub(FlatCAMTool):
|
||||
pass
|
||||
|
||||
grb_obj.solid_geometry = deepcopy(poly_buff)
|
||||
grb_obj.follow_geometry = deepcopy(follow_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)
|
||||
|
||||
|
@ -658,19 +658,18 @@ class ToolTransform(FlatCAMTool):
|
||||
|
||||
self.app.progress.emit(20)
|
||||
|
||||
px = 0.5 * (xminimal + xmaximal)
|
||||
py = 0.5 * (yminimal + ymaximal)
|
||||
for sel_obj in obj_list:
|
||||
px = 0.5 * (xminimal + xmaximal)
|
||||
py = 0.5 * (yminimal + ymaximal)
|
||||
if isinstance(sel_obj, FlatCAMCNCjob):
|
||||
self.app.inform.emit(_("CNCJob objects can't be rotated."))
|
||||
else:
|
||||
sel_obj.rotate(-num, point=(px, py))
|
||||
sel_obj.plot()
|
||||
self.app.object_changed.emit(sel_obj)
|
||||
|
||||
# add information to the object that it was changed and how much
|
||||
sel_obj.options['rotate'] = num
|
||||
|
||||
sel_obj.plot()
|
||||
self.app.inform.emit(_('[success] Rotate done ...'))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
@ -719,31 +718,30 @@ class ToolTransform(FlatCAMTool):
|
||||
self.app.progress.emit(20)
|
||||
|
||||
# execute mirroring
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, FlatCAMCNCjob):
|
||||
for sel_obj in obj_list:
|
||||
if isinstance(sel_obj, FlatCAMCNCjob):
|
||||
self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped."))
|
||||
else:
|
||||
if axis is 'X':
|
||||
obj.mirror('X', (px, py))
|
||||
sel_obj.mirror('X', (px, py))
|
||||
# add information to the object that it was changed and how much
|
||||
# the axis is reversed because of the reference
|
||||
if 'mirror_y' in obj.options:
|
||||
obj.options['mirror_y'] = not obj.options['mirror_y']
|
||||
if 'mirror_y' in sel_obj.options:
|
||||
sel_obj.options['mirror_y'] = not sel_obj.options['mirror_y']
|
||||
else:
|
||||
obj.options['mirror_y'] = True
|
||||
obj.plot()
|
||||
sel_obj.options['mirror_y'] = True
|
||||
self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
|
||||
elif axis is 'Y':
|
||||
obj.mirror('Y', (px, py))
|
||||
sel_obj.mirror('Y', (px, py))
|
||||
# add information to the object that it was changed and how much
|
||||
# the axis is reversed because of the reference
|
||||
if 'mirror_x' in obj.options:
|
||||
obj.options['mirror_x'] = not obj.options['mirror_x']
|
||||
if 'mirror_x' in sel_obj.options:
|
||||
sel_obj.options['mirror_x'] = not sel_obj.options['mirror_x']
|
||||
else:
|
||||
obj.options['mirror_x'] = True
|
||||
obj.plot()
|
||||
sel_obj.options['mirror_x'] = True
|
||||
self.app.inform.emit(_('[success] Flip on the X axis done ...'))
|
||||
self.app.object_changed.emit(obj)
|
||||
self.app.object_changed.emit(sel_obj)
|
||||
sel_obj.plot()
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except Exception as e:
|
||||
@ -776,20 +774,20 @@ class ToolTransform(FlatCAMTool):
|
||||
|
||||
self.app.progress.emit(20)
|
||||
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, FlatCAMCNCjob):
|
||||
for sel_obj in obj_list:
|
||||
if isinstance(sel_obj, FlatCAMCNCjob):
|
||||
self.app.inform.emit(_("CNCJob objects can't be skewed."))
|
||||
else:
|
||||
if axis is 'X':
|
||||
obj.skew(num, 0, point=(xminimal, yminimal))
|
||||
sel_obj.skew(num, 0, point=(xminimal, yminimal))
|
||||
# add information to the object that it was changed and how much
|
||||
obj.options['skew_x'] = num
|
||||
sel_obj.options['skew_x'] = num
|
||||
elif axis is 'Y':
|
||||
obj.skew(0, num, point=(xminimal, yminimal))
|
||||
sel_obj.skew(0, num, point=(xminimal, yminimal))
|
||||
# add information to the object that it was changed and how much
|
||||
obj.options['skew_y'] = num
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
sel_obj.options['skew_y'] = num
|
||||
self.app.object_changed.emit(sel_obj)
|
||||
sel_obj.plot()
|
||||
self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
@ -836,16 +834,17 @@ class ToolTransform(FlatCAMTool):
|
||||
px = 0
|
||||
py = 0
|
||||
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, FlatCAMCNCjob):
|
||||
for sel_obj in obj_list:
|
||||
if isinstance(sel_obj, FlatCAMCNCjob):
|
||||
self.app.inform.emit(_("CNCJob objects can't be scaled."))
|
||||
else:
|
||||
obj.scale(xfactor, yfactor, point=(px, py))
|
||||
sel_obj.scale(xfactor, yfactor, point=(px, py))
|
||||
# add information to the object that it was changed and how much
|
||||
obj.options['scale_x'] = xfactor
|
||||
obj.options['scale_y'] = yfactor
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
sel_obj.options['scale_x'] = xfactor
|
||||
sel_obj.options['scale_y'] = yfactor
|
||||
self.app.object_changed.emit(sel_obj)
|
||||
sel_obj.plot()
|
||||
|
||||
self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
except Exception as e:
|
||||
@ -854,8 +853,6 @@ class ToolTransform(FlatCAMTool):
|
||||
|
||||
def on_offset(self, axis, num):
|
||||
obj_list = self.app.collection.get_selected()
|
||||
xminlist = []
|
||||
yminlist = []
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] No object selected. Please Select an object to offset!"))
|
||||
@ -863,34 +860,23 @@ class ToolTransform(FlatCAMTool):
|
||||
else:
|
||||
with self.app.proc_container.new(_("Applying Offset")):
|
||||
try:
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, FlatCAMCNCjob):
|
||||
pass
|
||||
else:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
xminlist.append(xmin)
|
||||
yminlist.append(ymin)
|
||||
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
self.app.progress.emit(20)
|
||||
|
||||
for obj in obj_list:
|
||||
if isinstance(obj, FlatCAMCNCjob):
|
||||
for sel_obj in obj_list:
|
||||
if isinstance(sel_obj, FlatCAMCNCjob):
|
||||
self.app.inform.emit(_("CNCJob objects can't be offseted."))
|
||||
else:
|
||||
if axis is 'X':
|
||||
obj.offset((num, 0))
|
||||
sel_obj.offset((num, 0))
|
||||
# add information to the object that it was changed and how much
|
||||
obj.options['offset_x'] = num
|
||||
sel_obj.options['offset_x'] = num
|
||||
elif axis is 'Y':
|
||||
obj.offset((0, num))
|
||||
sel_obj.offset((0, num))
|
||||
# add information to the object that it was changed and how much
|
||||
obj.options['offset_y'] = num
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
sel_obj.options['offset_y'] = num
|
||||
self.app.object_changed.emit(sel_obj)
|
||||
sel_obj.plot()
|
||||
|
||||
self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
|
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
@ -157,10 +157,11 @@ M6
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(p.spindlespeed)
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -260,10 +260,11 @@ M0
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(p.spindlespeed)
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -220,11 +220,12 @@ M0
|
||||
def z_feedrate_code(self, p):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self,p):
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(p.spindlespeed)
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -192,10 +192,11 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(p.spindlespeed)
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -191,11 +191,12 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
|
||||
def z_feedrate_code(self, p):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self,p):
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S%d' % p.spindlespeed
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -90,7 +90,11 @@ class grbl_laser(FlatCAMPostProc):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self, p):
|
||||
return ''
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
return ''
|
||||
|
@ -193,10 +193,11 @@ M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchang
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
|
||||
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(p.spindlespeed)
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M03'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
@ -198,11 +198,12 @@ M0""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchang
|
||||
def feedrate_rapid_code(self, p):
|
||||
return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
|
||||
|
||||
def spindle_code(self,p):
|
||||
def spindle_code(self, p):
|
||||
sdir = {'CW': 'M3', 'CCW': 'M4'}[p.spindledir]
|
||||
if p.spindlespeed:
|
||||
return 'M3 S%d' % p.spindlespeed
|
||||
return '%s S%s' % (sdir, str(p.spindlespeed))
|
||||
else:
|
||||
return 'M3'
|
||||
return sdir
|
||||
|
||||
def dwell_code(self, p):
|
||||
if p.dwelltime:
|
||||
|
BIN
share/eraser26.png
Normal file
BIN
share/eraser26.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -2,6 +2,7 @@ from ObjectCollection import *
|
||||
from tclCommands.TclCommand import TclCommandSignaled
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class TclCommandGeoCutout(TclCommandSignaled):
|
||||
"""
|
||||
Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry.
|
||||
@ -65,7 +66,6 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
:return:
|
||||
"""
|
||||
|
||||
|
||||
def subtract_rectangle(obj_, x0, y0, x1, y1):
|
||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
obj_.subtract_polygon(pts)
|
||||
@ -73,7 +73,6 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
def substract_rectangle_geo(geo, x0, y0, x1, y1):
|
||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
||||
|
||||
|
||||
def flatten(geometry=None, reset=True, pathonly=False):
|
||||
"""
|
||||
Creates a list of non-iterable linear geometry objects.
|
||||
@ -89,15 +88,15 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
if reset:
|
||||
self.flat_geometry = []
|
||||
|
||||
## If iterable, expand recursively.
|
||||
# If iterable, expand recursively.
|
||||
try:
|
||||
for geo in geometry:
|
||||
if geo is not None:
|
||||
flatten(geometry=geo,
|
||||
for geo_el in geometry:
|
||||
if geo_el is not None:
|
||||
flatten(geometry=geo_el,
|
||||
reset=False,
|
||||
pathonly=pathonly)
|
||||
|
||||
## Not iterable, do the actual indexing and add.
|
||||
# Not iterable, do the actual indexing and add.
|
||||
except TypeError:
|
||||
if pathonly and type(geometry) == Polygon:
|
||||
self.flat_geometry.append(geometry.exterior)
|
||||
@ -151,14 +150,15 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
# Get source object.
|
||||
try:
|
||||
cutout_obj = self.app.collection.get_by_name(str(name))
|
||||
except:
|
||||
except Exception as e:
|
||||
log.debug("TclCommandGeoCutout --> %s" % str(e))
|
||||
return "Could not retrieve object: %s" % name
|
||||
|
||||
if 0 in {dia}:
|
||||
self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive real number.")
|
||||
return "Tool Diameter is zero value. Change it to a positive real number."
|
||||
|
||||
if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
|
||||
if gaps not in ['lr', 'tb', '2lr', '2tb', '4', '8']:
|
||||
self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
|
||||
"Fill in a correct value and retry. ")
|
||||
return
|
||||
@ -226,47 +226,47 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
def geo_init(geo_obj, app_obj):
|
||||
try:
|
||||
geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2, follow=None)
|
||||
except Exception as e:
|
||||
log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
|
||||
except Exception as exc:
|
||||
log.debug("TclCommandGeoCutout.execute() --> %s" % str(exc))
|
||||
return 'fail'
|
||||
|
||||
if gaps_u == 8 or gaps_u == '2lr':
|
||||
geo = substract_rectangle_geo(geo,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
geo = substract_rectangle_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
|
||||
if gaps_u == 8 or gaps_u == '2tb':
|
||||
geo = substract_rectangle_geo(geo,
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
px - gapsize + lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
geo = substract_rectangle_geo(geo,
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
px - gapsize - lenghtx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps_u == 4 or gaps_u == 'lr':
|
||||
geo = substract_rectangle_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps_u == 4 or gaps_u == 'tb':
|
||||
geo = substract_rectangle_geo(geo,
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
px - gapsize,
|
||||
ymin - gapsize,
|
||||
px + gapsize,
|
||||
ymax + gapsize)
|
||||
geo_obj.solid_geometry = geo
|
||||
|
||||
outname = cutout_obj.options["name"] + "_cutout"
|
||||
@ -276,7 +276,3 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
||||
else:
|
||||
self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user