Merged in marius_stanciu/flatcam_beta/beta_8.917 (pull request #147)
Beta 8.917
This commit is contained in:
commit
900631b92e
128
FlatCAMApp.py
128
FlatCAMApp.py
|
@ -94,8 +94,8 @@ class App(QtCore.QObject):
|
|||
log.addHandler(handler)
|
||||
|
||||
# Version
|
||||
version = 8.916
|
||||
version_date = "2019/05/10"
|
||||
version = 8.917
|
||||
version_date = "2019/05/22"
|
||||
beta = True
|
||||
|
||||
# current date now
|
||||
|
@ -324,6 +324,8 @@ class App(QtCore.QObject):
|
|||
"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,
|
||||
|
||||
|
@ -369,8 +371,8 @@ 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
|
||||
|
@ -379,6 +381,9 @@ class App(QtCore.QObject):
|
|||
"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,
|
||||
|
@ -396,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,
|
||||
|
@ -434,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,
|
||||
|
@ -454,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,
|
||||
|
@ -602,6 +612,7 @@ class App(QtCore.QObject):
|
|||
"global_toggle_tooltips": True,
|
||||
"global_worker_number": 2,
|
||||
"global_tolerance": 0.01,
|
||||
"global_open_style": True,
|
||||
"global_compression_level": 3,
|
||||
"global_save_compressed": True,
|
||||
|
||||
|
@ -661,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
|
||||
|
@ -679,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
|
||||
|
@ -694,6 +705,9 @@ class App(QtCore.QObject):
|
|||
"gerber_exp_decimals": 4,
|
||||
"gerber_exp_zeros": 'L',
|
||||
|
||||
# Gerber Editor
|
||||
"gerber_editor_sel_limit": 30,
|
||||
|
||||
# Excellon General
|
||||
"excellon_plot": True,
|
||||
"excellon_solid": True,
|
||||
|
@ -711,6 +725,7 @@ 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,
|
||||
|
@ -740,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
|
||||
|
@ -753,6 +768,7 @@ class App(QtCore.QObject):
|
|||
"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',
|
||||
|
@ -769,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',
|
||||
|
@ -942,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,
|
||||
|
@ -963,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,
|
||||
|
@ -1065,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,
|
||||
|
@ -1085,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,
|
||||
|
@ -1882,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']
|
||||
|
@ -2264,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 not edited_obj.solid_geometry:
|
||||
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:
|
||||
|
@ -2780,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)
|
||||
|
||||
|
@ -2984,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
|
||||
|
@ -4401,8 +4414,8 @@ class App(QtCore.QObject):
|
|||
return
|
||||
|
||||
# Just for adding it to the recent files list.
|
||||
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)
|
||||
|
||||
|
@ -4797,13 +4810,17 @@ class App(QtCore.QObject):
|
|||
def convert_any2gerber(self):
|
||||
self.report_usage("convert_any2gerber()")
|
||||
|
||||
def initialize(obj_init, app):
|
||||
def initialize_geometry(obj_init, app):
|
||||
apertures = {}
|
||||
apid = 0
|
||||
|
||||
apertures[str(apid)] = {}
|
||||
apertures[str(apid)]['solid_geometry'] = []
|
||||
apertures[str(apid)]['solid_geometry'] = deepcopy(obj.solid_geometry)
|
||||
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'
|
||||
|
||||
|
@ -4816,9 +4833,12 @@ class App(QtCore.QObject):
|
|||
apid = 10
|
||||
for tool in obj.tools:
|
||||
apertures[str(apid)] = {}
|
||||
apertures[str(apid)]['solid_geometry'] = []
|
||||
apertures[str(apid)]['geometry'] = []
|
||||
for geo in obj.tools[tool]['solid_geometry']:
|
||||
apertures[str(apid)]['solid_geometry'].append(geo)
|
||||
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'
|
||||
|
@ -4827,8 +4847,8 @@ class App(QtCore.QObject):
|
|||
# create solid_geometry
|
||||
solid_geometry = []
|
||||
for apid in apertures:
|
||||
for geo in apertures[apid]['solid_geometry']:
|
||||
solid_geometry.append(geo)
|
||||
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)
|
||||
|
@ -4850,8 +4870,10 @@ class App(QtCore.QObject):
|
|||
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:
|
||||
self.new_object("gerber", str(obj_name) + "_conv", initialize)
|
||||
log.warning("App.convert_any2gerber --> This is no vaild object for conversion.")
|
||||
|
||||
except Exception as e:
|
||||
return "Operation failed: %s" % str(e)
|
||||
|
@ -5923,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:
|
||||
|
@ -6073,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):
|
||||
|
@ -6102,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):
|
||||
|
@ -6141,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):
|
||||
|
@ -6180,6 +6208,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("Excellon", filename)
|
||||
self.file_saved.emit("Excellon", filename)
|
||||
|
||||
def on_file_exportexcellon(self):
|
||||
|
@ -6219,6 +6249,8 @@ 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):
|
||||
|
@ -6258,6 +6290,8 @@ class App(QtCore.QObject):
|
|||
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):
|
||||
|
@ -6309,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):
|
||||
|
@ -6563,7 +6599,7 @@ class App(QtCore.QObject):
|
|||
else:
|
||||
self.worker_task.emit({'fcn': self.save_project,
|
||||
'params': [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)
|
||||
|
||||
|
@ -6609,8 +6645,8 @@ class App(QtCore.QObject):
|
|||
self.save_project(filename, quit)
|
||||
|
||||
# self.save_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
|
||||
|
@ -6668,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)
|
||||
|
||||
|
@ -6773,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)
|
||||
|
||||
|
@ -6887,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)
|
||||
|
||||
|
@ -7037,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:
|
||||
|
@ -7153,7 +7193,8 @@ class App(QtCore.QObject):
|
|||
|
||||
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:
|
||||
|
@ -7211,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:
|
||||
|
|
193
FlatCAMObj.py
193
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):
|
||||
|
@ -122,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
|
||||
|
||||
|
@ -203,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)
|
||||
|
||||
|
@ -219,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)
|
||||
|
||||
|
@ -420,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:
|
||||
|
@ -440,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)
|
||||
|
@ -685,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)
|
||||
|
@ -1096,10 +1097,15 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
elif type(g) == Point:
|
||||
pass
|
||||
else:
|
||||
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:
|
||||
for g in geometry:
|
||||
if type(g) == Polygon or type(g) == LineString:
|
||||
|
@ -1155,10 +1161,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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']:
|
||||
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)
|
||||
|
@ -1305,8 +1310,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
try:
|
||||
length = whole + fract
|
||||
if '0' in self.apertures:
|
||||
if 'solid_geometry' in self.apertures['0']:
|
||||
for geo in self.apertures['0']['solid_geometry']:
|
||||
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
|
||||
|
@ -1339,14 +1347,19 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
|
||||
# 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)
|
||||
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)
|
||||
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,
|
||||
|
@ -1355,6 +1368,40 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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'
|
||||
|
@ -1364,30 +1411,37 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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)
|
||||
|
||||
if 'follow_geometry' in self.apertures[apid]:
|
||||
for geo in self.apertures[apid]['follow_geometry']:
|
||||
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,
|
||||
|
@ -1396,9 +1450,15 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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)
|
||||
if 'clear_follow_geometry' in self.apertures[apid]:
|
||||
prev_coord = coord
|
||||
|
||||
# gerber_code += "D02*\n"
|
||||
|
||||
if 'clear' in geo_elem:
|
||||
gerber_code += '%LPC*%\n'
|
||||
for geo in self.apertures[apid]['clear_follow_geometry']:
|
||||
|
||||
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)
|
||||
|
@ -1412,14 +1472,19 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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)
|
||||
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)
|
||||
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,
|
||||
|
@ -1428,6 +1493,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
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:
|
||||
|
@ -2449,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()
|
||||
|
@ -2502,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"
|
||||
|
@ -4093,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")
|
||||
|
@ -4350,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"]
|
||||
|
||||
|
@ -4369,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,
|
||||
|
@ -4593,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,
|
||||
|
@ -5468,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)
|
||||
|
||||
|
@ -5687,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:
|
||||
|
@ -5701,6 +5791,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
return
|
||||
elif to_file is False:
|
||||
# Just for adding it to the recent files list.
|
||||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("cncjob", filename)
|
||||
self.app.file_saved.emit("cncjob", filename)
|
||||
|
||||
|
|
84
README.md
84
README.md
|
@ -9,10 +9,92 @@ 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
|
||||
|
||||
- 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
|
||||
- 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
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
############################################################
|
||||
# ###########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
# ###########################################################
|
||||
|
||||
############################################################ #
|
||||
# ########################################################### #
|
||||
# File Modified: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
############################################################
|
||||
# ###########################################################
|
||||
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
@ -33,9 +33,9 @@ from flatcamParsers.ParseFont import *
|
|||
# from vispy.io import read_png
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
import builtins
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
@ -414,7 +414,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
self.app = app
|
||||
self.fcdraw = fcdraw
|
||||
|
||||
## Title
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("%s" % ('Editor ' + self.toolName))
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
|
@ -431,7 +431,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
# Tool dia
|
||||
ptdlabel = QtWidgets.QLabel(_('Tool dia:'))
|
||||
ptdlabel.setToolTip(
|
||||
_( "Diameter of the tool to\n"
|
||||
_("Diameter of the tool to\n"
|
||||
"be used in the operation.")
|
||||
)
|
||||
grid.addWidget(ptdlabel, 0, 0)
|
||||
|
@ -460,7 +460,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
# Margin
|
||||
marginlabel = QtWidgets.QLabel(_('Margin:'))
|
||||
marginlabel.setToolTip(
|
||||
_( "Distance by which to avoid\n"
|
||||
_("Distance by which to avoid\n"
|
||||
"the edges of the polygon to\n"
|
||||
"be painted.")
|
||||
)
|
||||
|
@ -486,7 +486,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
# Connect lines
|
||||
pathconnectlabel = QtWidgets.QLabel(_("Connect:"))
|
||||
pathconnectlabel.setToolTip(
|
||||
_( "Draw lines between resulting\n"
|
||||
_("Draw lines between resulting\n"
|
||||
"segments to minimize tool lifts.")
|
||||
)
|
||||
grid.addWidget(pathconnectlabel, 4, 0)
|
||||
|
@ -502,8 +502,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
self.paintcontour_cb = FCCheckBox()
|
||||
grid.addWidget(self.paintcontour_cb, 5, 1)
|
||||
|
||||
|
||||
## Buttons
|
||||
# Buttons
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
hlay.addStretch()
|
||||
|
@ -512,7 +511,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
|
||||
self.layout.addStretch()
|
||||
|
||||
## Signals
|
||||
# Signals
|
||||
self.paint_button.clicked.connect(self.on_paint)
|
||||
|
||||
self.set_tool_ui()
|
||||
|
@ -528,7 +527,7 @@ class PaintOptionsTool(FlatCAMTool):
|
|||
self.app.ui.notebook.setTabText(2, _("Paint Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
## Init GUI
|
||||
# Init GUI
|
||||
if self.app.defaults["tools_painttooldia"]:
|
||||
self.painttooldia_entry.set_value(self.app.defaults["tools_painttooldia"])
|
||||
else:
|
||||
|
@ -654,18 +653,18 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.empty_label4.setFixedWidth(70)
|
||||
self.transform_lay.addWidget(self.empty_label)
|
||||
|
||||
## Rotate Title
|
||||
# Rotate Title
|
||||
rotate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
|
||||
self.transform_lay.addWidget(rotate_title_label)
|
||||
|
||||
## Layout
|
||||
# Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form_layout)
|
||||
form_child = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.rotate_label = QtWidgets.QLabel(_("Angle:"))
|
||||
self.rotate_label.setToolTip(
|
||||
_( "Angle for Rotation action, in degrees.\n"
|
||||
_("Angle for Rotation action, in degrees.\n"
|
||||
"Float number between -360 and 359.\n"
|
||||
"Positive numbers for CW motion.\n"
|
||||
"Negative numbers for CCW motion.")
|
||||
|
@ -692,11 +691,11 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.transform_lay.addWidget(self.empty_label1)
|
||||
|
||||
## Skew Title
|
||||
# Skew Title
|
||||
skew_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
|
||||
self.transform_lay.addWidget(skew_title_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form1_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form1_layout)
|
||||
form1_child_1 = QtWidgets.QHBoxLayout()
|
||||
|
@ -704,7 +703,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.skewx_label = QtWidgets.QLabel(_("Angle X:"))
|
||||
self.skewx_label.setToolTip(
|
||||
_( "Angle for Skew action, in degrees.\n"
|
||||
_("Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359.")
|
||||
)
|
||||
self.skewx_label.setFixedWidth(50)
|
||||
|
@ -715,14 +714,14 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.skewx_button = FCButton()
|
||||
self.skewx_button.set_value(_("Skew X"))
|
||||
self.skewx_button.setToolTip(
|
||||
_( "Skew/shear the selected shape(s).\n"
|
||||
_("Skew/shear the selected shape(s).\n"
|
||||
"The point of reference is the middle of\n"
|
||||
"the bounding box for all selected shapes."))
|
||||
self.skewx_button.setFixedWidth(60)
|
||||
|
||||
self.skewy_label = QtWidgets.QLabel(_("Angle Y:"))
|
||||
self.skewy_label.setToolTip(
|
||||
_( "Angle for Skew action, in degrees.\n"
|
||||
_("Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359.")
|
||||
)
|
||||
self.skewy_label.setFixedWidth(50)
|
||||
|
@ -749,11 +748,11 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.transform_lay.addWidget(self.empty_label2)
|
||||
|
||||
## Scale Title
|
||||
# Scale Title
|
||||
scale_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.scaleName)
|
||||
self.transform_lay.addWidget(scale_title_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form2_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form2_layout)
|
||||
form2_child_1 = QtWidgets.QHBoxLayout()
|
||||
|
@ -771,7 +770,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.scalex_button = FCButton()
|
||||
self.scalex_button.set_value(_("Scale X"))
|
||||
self.scalex_button.setToolTip(
|
||||
_( "Scale the selected shape(s).\n"
|
||||
_("Scale the selected shape(s).\n"
|
||||
"The point of reference depends on \n"
|
||||
"the Scale reference checkbox state."))
|
||||
self.scalex_button.setFixedWidth(60)
|
||||
|
@ -788,7 +787,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.scaley_button = FCButton()
|
||||
self.scaley_button.set_value(_("Scale Y"))
|
||||
self.scaley_button.setToolTip(
|
||||
_( "Scale the selected shape(s).\n"
|
||||
_("Scale the selected shape(s).\n"
|
||||
"The point of reference depends on \n"
|
||||
"the Scale reference checkbox state."))
|
||||
self.scaley_button.setFixedWidth(60)
|
||||
|
@ -823,11 +822,11 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.transform_lay.addWidget(self.empty_label3)
|
||||
|
||||
## Offset Title
|
||||
# Offset Title
|
||||
offset_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.offsetName)
|
||||
self.transform_lay.addWidget(offset_title_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form3_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form3_layout)
|
||||
form3_child_1 = QtWidgets.QHBoxLayout()
|
||||
|
@ -845,7 +844,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.offx_button = FCButton()
|
||||
self.offx_button.set_value(_("Offset X"))
|
||||
self.offx_button.setToolTip(
|
||||
_( "Offset the selected shape(s).\n"
|
||||
_("Offset the selected shape(s).\n"
|
||||
"The point of reference is the middle of\n"
|
||||
"the bounding box for all selected shapes.\n")
|
||||
)
|
||||
|
@ -880,11 +879,11 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.transform_lay.addWidget(self.empty_label4)
|
||||
|
||||
## Flip Title
|
||||
# Flip Title
|
||||
flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
|
||||
self.transform_lay.addWidget(flip_title_label)
|
||||
|
||||
## Form Layout
|
||||
# Form Layout
|
||||
form4_layout = QtWidgets.QFormLayout()
|
||||
form4_child_hlay = QtWidgets.QHBoxLayout()
|
||||
self.transform_lay.addLayout(form4_child_hlay)
|
||||
|
@ -937,7 +936,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.flip_ref_button = FCButton()
|
||||
self.flip_ref_button.set_value(_("Add"))
|
||||
self.flip_ref_button.setToolTip(
|
||||
_( "The point coordinates can be captured by\n"
|
||||
_("The point coordinates can be captured by\n"
|
||||
"left click on canvas together with pressing\n"
|
||||
"SHIFT key. Then click Add button to insert.")
|
||||
)
|
||||
|
@ -957,7 +956,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
|
||||
self.transform_lay.addStretch()
|
||||
|
||||
## Signals
|
||||
# Signals
|
||||
self.rotate_button.clicked.connect(self.on_rotate)
|
||||
self.skewx_button.clicked.connect(self.on_skewx)
|
||||
self.skewy_button.clicked.connect(self.on_skewy)
|
||||
|
@ -994,7 +993,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+T', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
## Initialize form
|
||||
# Initialize form
|
||||
if self.app.defaults["tools_transform_rotate"]:
|
||||
self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"])
|
||||
else:
|
||||
|
@ -1458,15 +1457,6 @@ class TransformEditorTool(FlatCAMTool):
|
|||
else:
|
||||
with self.app.proc_container.new(_("Applying Offset")):
|
||||
try:
|
||||
# first get a bounding box to fit all
|
||||
for sha in shape_list:
|
||||
xmin, ymin, xmax, ymax = sha.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 sha in shape_list:
|
||||
|
@ -1476,10 +1466,6 @@ class TransformEditorTool(FlatCAMTool):
|
|||
sha.offset((0, num))
|
||||
self.draw_app.replot()
|
||||
|
||||
# self.draw_app.add_shape(DrawToolShape(sha.geo))
|
||||
#
|
||||
# self.draw_app.transform_complete.emit()
|
||||
|
||||
self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
|
@ -1559,7 +1545,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
return
|
||||
else:
|
||||
self.app.inform.emit(
|
||||
_( "[WARNING_NOTCL] Geometry shape skew X cancelled..."))
|
||||
_("[WARNING_NOTCL] Geometry shape skew X cancelled..."))
|
||||
|
||||
def on_skewy_key(self):
|
||||
val_box = FCInputDialog(title=_("Skew on Y axis ..."),
|
||||
|
@ -1572,7 +1558,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
if ok:
|
||||
self.on_skewx(val=val)
|
||||
self.app.inform.emit(
|
||||
_( "[success] Geometry shape skew on Y axis done..."))
|
||||
_("[success] Geometry shape skew on Y axis done..."))
|
||||
return
|
||||
else:
|
||||
self.app.inform.emit(
|
||||
|
@ -1599,19 +1585,19 @@ class DrawToolShape(object):
|
|||
"""
|
||||
pts = []
|
||||
|
||||
## Iterable: descend into each item.
|
||||
# Iterable: descend into each item.
|
||||
try:
|
||||
for subo in o:
|
||||
pts += DrawToolShape.get_pts(subo)
|
||||
|
||||
## Non-iterable
|
||||
# Non-iterable
|
||||
except TypeError:
|
||||
if o is not None:
|
||||
## DrawToolShape: descend into .geo.
|
||||
# DrawToolShape: descend into .geo.
|
||||
if isinstance(o, DrawToolShape):
|
||||
pts += DrawToolShape.get_pts(o.geo)
|
||||
|
||||
## Descend into .exerior and .interiors
|
||||
# Descend into .exerior and .interiors
|
||||
elif type(o) == Polygon:
|
||||
pts += DrawToolShape.get_pts(o.exterior)
|
||||
for i in o.interiors:
|
||||
|
@ -1619,7 +1605,7 @@ class DrawToolShape(object):
|
|||
elif type(o) == MultiLineString:
|
||||
for line in o:
|
||||
pts += DrawToolShape.get_pts(line)
|
||||
## Has .coords: list them.
|
||||
# Has .coords: list them.
|
||||
else:
|
||||
if DrawToolShape.tolerance is not None:
|
||||
pts += list(o.simplify(DrawToolShape.tolerance).coords)
|
||||
|
@ -1645,14 +1631,14 @@ class DrawToolShape(object):
|
|||
"""
|
||||
# fixed issue of getting bounds only for one level lists of objects
|
||||
# now it can get bounds for nested lists of objects
|
||||
def bounds_rec(shape):
|
||||
if type(shape) is list:
|
||||
def bounds_rec(shape_el):
|
||||
if type(shape_el) is list:
|
||||
minx = Inf
|
||||
miny = Inf
|
||||
maxx = -Inf
|
||||
maxy = -Inf
|
||||
|
||||
for k in shape:
|
||||
for k in shape_el:
|
||||
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
|
||||
minx = min(minx, minx_)
|
||||
miny = min(miny, miny_)
|
||||
|
@ -1661,7 +1647,7 @@ class DrawToolShape(object):
|
|||
return minx, miny, maxx, maxy
|
||||
else:
|
||||
# it's a Shapely object, return it's bounds
|
||||
return shape.bounds
|
||||
return shape_el.bounds
|
||||
|
||||
bounds_coords = bounds_rec(self.geo)
|
||||
return bounds_coords
|
||||
|
@ -1681,14 +1667,14 @@ class DrawToolShape(object):
|
|||
px, py = point
|
||||
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
|
||||
|
||||
def mirror_geom(shape):
|
||||
if type(shape) is list:
|
||||
def mirror_geom(shape_el):
|
||||
if type(shape_el) is list:
|
||||
new_obj = []
|
||||
for g in shape:
|
||||
for g in shape_el:
|
||||
new_obj.append(mirror_geom(g))
|
||||
return new_obj
|
||||
else:
|
||||
return affinity.scale(shape, xscale, yscale, origin=(px,py))
|
||||
return affinity.scale(shape_el, xscale, yscale, origin=(px, py))
|
||||
|
||||
try:
|
||||
self.geo = mirror_geom(self.geo)
|
||||
|
@ -1714,14 +1700,14 @@ class DrawToolShape(object):
|
|||
|
||||
px, py = point
|
||||
|
||||
def rotate_geom(shape):
|
||||
if type(shape) is list:
|
||||
def rotate_geom(shape_el):
|
||||
if type(shape_el) is list:
|
||||
new_obj = []
|
||||
for g in shape:
|
||||
for g in shape_el:
|
||||
new_obj.append(rotate_geom(g))
|
||||
return new_obj
|
||||
else:
|
||||
return affinity.rotate(shape, angle, origin=(px, py))
|
||||
return affinity.rotate(shape_el, angle, origin=(px, py))
|
||||
|
||||
try:
|
||||
self.geo = rotate_geom(self.geo)
|
||||
|
@ -1745,14 +1731,14 @@ class DrawToolShape(object):
|
|||
"""
|
||||
px, py = point
|
||||
|
||||
def skew_geom(shape):
|
||||
if type(shape) is list:
|
||||
def skew_geom(shape_el):
|
||||
if type(shape_el) is list:
|
||||
new_obj = []
|
||||
for g in shape:
|
||||
for g in shape_el:
|
||||
new_obj.append(skew_geom(g))
|
||||
return new_obj
|
||||
else:
|
||||
return affinity.skew(shape, angle_x, angle_y, origin=(px, py))
|
||||
return affinity.skew(shape_el, angle_x, angle_y, origin=(px, py))
|
||||
|
||||
try:
|
||||
self.geo = skew_geom(self.geo)
|
||||
|
@ -1778,7 +1764,7 @@ class DrawToolShape(object):
|
|||
|
||||
def translate_recursion(geom):
|
||||
if type(geom) == list:
|
||||
geoms=list()
|
||||
geoms = list()
|
||||
for local_geom in geom:
|
||||
geoms.append(translate_recursion(local_geom))
|
||||
return geoms
|
||||
|
@ -1825,7 +1811,7 @@ class DrawToolShape(object):
|
|||
|
||||
def scale_recursion(geom):
|
||||
if type(geom) == list:
|
||||
geoms=list()
|
||||
geoms = list()
|
||||
for local_geom in geom:
|
||||
geoms.append(scale_recursion(local_geom))
|
||||
return geoms
|
||||
|
@ -2454,6 +2440,8 @@ class FCMove(FCShapeTool):
|
|||
|
||||
self.origin = None
|
||||
self.destination = None
|
||||
self.sel_limit = self.draw_app.app.defaults["geometry_editor_sel_limit"]
|
||||
self.selection_shape = self.selection_bbox()
|
||||
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
self.draw_app.app.inform.emit(_("[WARNING_NOTCL] MOVE: No shape selected. Select a shape to move ..."))
|
||||
|
@ -2475,13 +2463,17 @@ class FCMove(FCShapeTool):
|
|||
|
||||
if self.origin is None:
|
||||
self.set_origin(point)
|
||||
self.selection_shape = self.selection_bbox()
|
||||
return "Click on final location."
|
||||
else:
|
||||
self.destination = point
|
||||
self.make()
|
||||
# self.draw_app.app.worker_task.emit(({'fcn': self.make,
|
||||
# 'params': []}))
|
||||
return "Done."
|
||||
|
||||
def make(self):
|
||||
with self.draw_app.app.proc_container.new("Moving Geometry ..."):
|
||||
# Create new geometry
|
||||
dx = self.destination[0] - self.origin[0]
|
||||
dy = self.destination[1] - self.origin[1]
|
||||
|
@ -2490,15 +2482,28 @@ class FCMove(FCShapeTool):
|
|||
|
||||
# Delete old
|
||||
self.draw_app.delete_selected()
|
||||
|
||||
# # Select the new
|
||||
# for g in self.geometry:
|
||||
# # Note that g is not in the app's buffer yet!
|
||||
# self.draw_app.set_selected(g)
|
||||
|
||||
self.complete = True
|
||||
self.draw_app.app.inform.emit(_("[success] Done. Geometry(s) Move completed."))
|
||||
|
||||
def selection_bbox(self):
|
||||
geo_list = []
|
||||
for select_shape in self.draw_app.get_selected():
|
||||
geometric_data = select_shape.geo
|
||||
try:
|
||||
for g in geometric_data:
|
||||
geo_list.append(g)
|
||||
except TypeError:
|
||||
geo_list.append(geometric_data)
|
||||
|
||||
xmin, ymin, xmax, ymax = get_shapely_list_bounds(geo_list)
|
||||
|
||||
pt1 = (xmin, ymin)
|
||||
pt2 = (xmax, ymin)
|
||||
pt3 = (xmax, ymax)
|
||||
pt4 = (xmin, ymax)
|
||||
|
||||
return Polygon([pt1, pt2, pt3, pt4])
|
||||
|
||||
def utility_geometry(self, data=None):
|
||||
"""
|
||||
Temporary geometry on screen while using this tool.
|
||||
|
@ -2517,6 +2522,7 @@ class FCMove(FCShapeTool):
|
|||
dx = data[0] - self.origin[0]
|
||||
dy = data[1] - self.origin[1]
|
||||
|
||||
if len(self.draw_app.get_selected()) <= self.sel_limit:
|
||||
try:
|
||||
for geom in self.draw_app.get_selected():
|
||||
geo_list.append(affinity.translate(geom.geo, xoff=dx, yoff=dy))
|
||||
|
@ -2524,10 +2530,13 @@ class FCMove(FCShapeTool):
|
|||
self.draw_app.select_tool('select')
|
||||
self.draw_app.selected = []
|
||||
return
|
||||
|
||||
return DrawToolUtilityShape(geo_list)
|
||||
# return DrawToolUtilityShape([affinity.translate(geom.geo, xoff=dx, yoff=dy)
|
||||
# for geom in self.draw_app.get_selected()])
|
||||
else:
|
||||
try:
|
||||
ss_el = affinity.translate(self.selection_shape, xoff=dx, yoff=dy)
|
||||
except ValueError:
|
||||
ss_el = None
|
||||
return DrawToolUtilityShape(ss_el)
|
||||
|
||||
def select_shapes(self, pos):
|
||||
# list where we store the overlapped shapes under our mouse left click position
|
||||
|
@ -2775,6 +2784,111 @@ class FCBuffer(FCShapeTool):
|
|||
self.buff_tool.hide_tool()
|
||||
|
||||
|
||||
class FCEraser(FCShapeTool):
|
||||
def __init__(self, draw_app):
|
||||
DrawTool.__init__(self, draw_app)
|
||||
self.name = 'eraser'
|
||||
|
||||
self.origin = None
|
||||
self.destination = None
|
||||
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
if self.draw_app.launched_from_shortcuts is True:
|
||||
self.draw_app.launched_from_shortcuts = False
|
||||
self.draw_app.app.inform.emit(_("Select a shape to act as deletion area ..."))
|
||||
else:
|
||||
self.draw_app.app.inform.emit(_("Click to pick-up the erase shape..."))
|
||||
|
||||
self.geometry = []
|
||||
self.storage = self.draw_app.storage
|
||||
|
||||
# Switch notebook to Selected page
|
||||
self.draw_app.app.ui.notebook.setCurrentWidget(self.draw_app.app.ui.selected_tab)
|
||||
|
||||
def set_origin(self, origin):
|
||||
self.origin = origin
|
||||
|
||||
def click(self, point):
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
for obj_shape in self.storage.get_objects():
|
||||
try:
|
||||
__, closest_shape = self.storage.nearest(point)
|
||||
self.draw_app.selected.append(closest_shape)
|
||||
except StopIteration:
|
||||
if len(self.draw_app.selected) > 0:
|
||||
self.draw_app.app.inform.emit(_("Click to pick-up the erase shape..."))
|
||||
return ""
|
||||
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
return "Nothing to ersase."
|
||||
else:
|
||||
self.draw_app.app.inform.emit(_("Click to pick-up the erase shape..."))
|
||||
|
||||
if self.origin is None:
|
||||
self.set_origin(point)
|
||||
self.draw_app.app.inform.emit(_("Click to erase ..."))
|
||||
return
|
||||
else:
|
||||
self.destination = point
|
||||
self.make()
|
||||
|
||||
# self.draw_app.select_tool("select")
|
||||
return
|
||||
|
||||
def make(self):
|
||||
eraser_sel_shapes = []
|
||||
|
||||
# create the eraser shape from selection
|
||||
for eraser_shape in self.utility_geometry(data=self.destination).geo:
|
||||
temp_shape = eraser_shape.buffer(0.0000001)
|
||||
temp_shape = Polygon(temp_shape.exterior)
|
||||
eraser_sel_shapes.append(temp_shape)
|
||||
eraser_sel_shapes = cascaded_union(eraser_sel_shapes)
|
||||
|
||||
for obj_shape in self.storage.get_objects():
|
||||
try:
|
||||
geometric_data = obj_shape.geo
|
||||
if eraser_sel_shapes.intersects(geometric_data):
|
||||
obj_shape.geo = geometric_data.difference(eraser_sel_shapes)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.draw_app.delete_utility_geometry()
|
||||
self.draw_app.plot_all()
|
||||
self.draw_app.app.inform.emit(_("[success] Done. Eraser tool action completed."))
|
||||
|
||||
def utility_geometry(self, data=None):
|
||||
"""
|
||||
Temporary geometry on screen while using this tool.
|
||||
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
geo_list = []
|
||||
|
||||
if self.origin is None:
|
||||
return None
|
||||
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
return None
|
||||
|
||||
dx = data[0] - self.origin[0]
|
||||
dy = data[1] - self.origin[1]
|
||||
|
||||
try:
|
||||
for geom in self.draw_app.get_selected():
|
||||
geo_list.append(affinity.translate(geom.geo, xoff=dx, yoff=dy))
|
||||
except AttributeError:
|
||||
self.draw_app.select_tool('select')
|
||||
self.draw_app.selected = []
|
||||
return
|
||||
return DrawToolUtilityShape(geo_list)
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.plot_all()
|
||||
|
||||
|
||||
class FCPaint(FCShapeTool):
|
||||
def __init__(self, draw_app):
|
||||
FCShapeTool.__init__(self, draw_app)
|
||||
|
@ -2803,9 +2917,9 @@ class FCTransform(FCShapeTool):
|
|||
self.draw_app.transform_tool.run()
|
||||
|
||||
|
||||
########################
|
||||
### Main Application ###
|
||||
########################
|
||||
# #######################
|
||||
# ## Main Application ###
|
||||
# #######################
|
||||
class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
transform_complete = QtCore.pyqtSignal()
|
||||
|
@ -2841,6 +2955,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
"constructor": FCBuffer},
|
||||
"paint": {"button": self.app.ui.geo_add_paint_btn,
|
||||
"constructor": FCPaint},
|
||||
"eraser": {"button": self.app.ui.geo_eraser_btn,
|
||||
"constructor": FCEraser},
|
||||
"move": {"button": self.app.ui.geo_move_btn,
|
||||
"constructor": FCMove},
|
||||
"transform": {"button": self.app.ui.geo_transform_btn,
|
||||
|
@ -2849,7 +2965,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
"constructor": FCCopy}
|
||||
}
|
||||
|
||||
### Data
|
||||
# ## Data
|
||||
self.active_tool = None
|
||||
|
||||
self.storage = FlatCAMGeoEditor.make_storage()
|
||||
|
@ -2865,7 +2981,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.shapes.enabled = False
|
||||
self.tool_shape.enabled = False
|
||||
|
||||
## List of selected shapes.
|
||||
# List of selected shapes.
|
||||
self.selected = []
|
||||
|
||||
self.flat_geo = []
|
||||
|
@ -3131,7 +3247,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.fcgeometry.visible = True
|
||||
|
||||
def connect_canvas_event_handlers(self):
|
||||
## Canvas events
|
||||
# Canvas events
|
||||
|
||||
# first connect to new, then disconnect the old handlers
|
||||
# don't ask why but if there is nothing connected I've seen issues
|
||||
|
@ -3161,7 +3277,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.app.ui.draw_cut.triggered.connect(self.cutpath)
|
||||
self.app.ui.draw_move.triggered.connect(self.on_move)
|
||||
|
||||
|
||||
def disconnect_canvas_event_handlers(self):
|
||||
# we restore the key and mouse control to FlatCAMApp method
|
||||
# first connect to new, then disconnect the old handlers
|
||||
|
@ -3239,8 +3354,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
"Shape object has empty geometry (None)"
|
||||
|
||||
assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \
|
||||
not isinstance(shape.geo, list), \
|
||||
"Shape objects has empty geometry ([])"
|
||||
not isinstance(shape.geo, list), "Shape objects has empty geometry ([])"
|
||||
|
||||
if isinstance(shape, DrawToolUtilityShape):
|
||||
self.utility.append(shape)
|
||||
|
@ -3280,6 +3394,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
into the editor.
|
||||
|
||||
:param fcgeometry: FlatCAMGeometry
|
||||
:param multigeo_tool: a tool for the case of multigeo
|
||||
:return: None
|
||||
"""
|
||||
assert isinstance(fcgeometry, Geometry), \
|
||||
|
@ -3317,9 +3432,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
|
||||
self.replot()
|
||||
|
||||
|
||||
# start with GRID toolbar activated
|
||||
if self.app.ui.grid_snap_btn.isChecked() == False:
|
||||
if self.app.ui.grid_snap_btn.isChecked() is False:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
|
||||
def on_buffer_tool(self):
|
||||
|
@ -3408,7 +3522,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
if self.active_tool is not None and event.button is 1:
|
||||
|
||||
# Dispatch event to active_tool
|
||||
msg = self.active_tool.click(self.snap(self.pos[0], self.pos[1]))
|
||||
self.active_tool.click(self.snap(self.pos[0], self.pos[1]))
|
||||
|
||||
# If it is a shape generating tool
|
||||
if isinstance(self.active_tool, FCShapeTool) and self.active_tool.complete:
|
||||
|
@ -3458,7 +3572,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
if self.active_tool is None:
|
||||
return
|
||||
|
||||
### Snap coordinates
|
||||
# ## Snap coordinates
|
||||
if self.app.grid_status():
|
||||
x, y = self.snap(x, y)
|
||||
self.app.app_cursor.enabled = True
|
||||
|
@ -3483,25 +3597,27 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (dx, dy))
|
||||
|
||||
### Utility geometry (animated)
|
||||
if event.button == 1 and event.is_dragging == 1 and isinstance(self.active_tool, FCEraser):
|
||||
pass
|
||||
else:
|
||||
# ## Utility geometry (animated)
|
||||
geo = self.active_tool.utility_geometry(data=(x, y))
|
||||
|
||||
if isinstance(geo, DrawToolShape) and geo.geo is not None:
|
||||
# Remove any previous utility shape
|
||||
self.tool_shape.clear(update=True)
|
||||
self.draw_utility_geometry(geo=geo)
|
||||
|
||||
### Selection area on canvas section ###
|
||||
# ## Selection area on canvas section ###
|
||||
dx = pos[0] - self.pos[0]
|
||||
if event.is_dragging == 1 and event.button == 1:
|
||||
self.app.delete_selection_shape()
|
||||
if dx < 0:
|
||||
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y),
|
||||
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y),
|
||||
color=self.app.defaults["global_alt_sel_line"],
|
||||
face_color=self.app.defaults['global_alt_sel_fill'])
|
||||
self.app.selection_type = False
|
||||
else:
|
||||
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y))
|
||||
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y))
|
||||
self.app.selection_type = True
|
||||
else:
|
||||
self.app.selection_type = None
|
||||
|
@ -3573,9 +3689,10 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
elif isinstance(self.active_tool, FCSelect):
|
||||
# Dispatch event to active_tool
|
||||
# msg = self.active_tool.click(self.snap(event.xdata, event.ydata))
|
||||
msg = self.active_tool.click_release((self.pos[0], self.pos[1]))
|
||||
self.active_tool.click_release((self.pos[0], self.pos[1]))
|
||||
# self.app.inform.emit(msg)
|
||||
self.replot()
|
||||
|
||||
except Exception as e:
|
||||
log.warning("Error: %s" % str(e))
|
||||
return
|
||||
|
@ -3586,15 +3703,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
:param start_pos: mouse position when the selection LMB click was done
|
||||
:param end_pos: mouse position when the left mouse button is released
|
||||
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
|
||||
:type Bool
|
||||
:return:
|
||||
"""
|
||||
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
|
||||
|
||||
self.app.delete_selection_shape()
|
||||
for obj in self.storage.get_objects():
|
||||
if (sel_type is True and poly_selection.contains(obj.geo)) or \
|
||||
(sel_type is False and poly_selection.intersects(obj.geo)):
|
||||
if (sel_type is True and poly_selection.contains(obj.geo)) or (sel_type is False and
|
||||
poly_selection.intersects(obj.geo)):
|
||||
if self.key == self.app.defaults["global_mselect_key"]:
|
||||
if obj in self.selected:
|
||||
self.selected.remove(obj)
|
||||
|
@ -3717,14 +3833,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
for geo in geometry:
|
||||
plot_elements += self.plot_shape(geometry=geo, color=color, linewidth=linewidth)
|
||||
|
||||
## Non-iterable
|
||||
# Non-iterable
|
||||
except TypeError:
|
||||
|
||||
## DrawToolShape
|
||||
# DrawToolShape
|
||||
if isinstance(geometry, DrawToolShape):
|
||||
plot_elements += self.plot_shape(geometry=geometry.geo, color=color, linewidth=linewidth)
|
||||
|
||||
## Polygon: Descend into exterior and each interior.
|
||||
# Polygon: Descend into exterior and each interior.
|
||||
if type(geometry) == Polygon:
|
||||
plot_elements += self.plot_shape(geometry=geometry.exterior, color=color, linewidth=linewidth)
|
||||
plot_elements += self.plot_shape(geometry=geometry.interiors, color=color, linewidth=linewidth)
|
||||
|
@ -3785,7 +3901,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
@staticmethod
|
||||
def make_storage():
|
||||
|
||||
## Shape storage.
|
||||
# Shape storage.
|
||||
storage = FlatCAMRTreeStorage()
|
||||
storage.get_points = DrawToolShape.get_pts
|
||||
|
||||
|
@ -3825,9 +3941,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
snap_x, snap_y = (x, y)
|
||||
snap_distance = Inf
|
||||
|
||||
### Object (corner?) snap
|
||||
### No need for the objects, just the coordinates
|
||||
### in the index.
|
||||
# ## Object (corner?) snap
|
||||
# ## No need for the objects, just the coordinates
|
||||
# ## in the index.
|
||||
if self.options["corner_snap"]:
|
||||
try:
|
||||
nearest_pt, shape = self.storage.nearest((x, y))
|
||||
|
@ -3839,7 +3955,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
except (StopIteration, AssertionError):
|
||||
pass
|
||||
|
||||
### Grid snap
|
||||
# ## Grid snap
|
||||
if self.options["grid_snap"]:
|
||||
if self.options["global_gridx"] != 0:
|
||||
snap_x_ = round(x / self.options["global_gridx"]) * self.options['global_gridx']
|
||||
|
@ -3921,23 +4037,24 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
:return: None
|
||||
"""
|
||||
|
||||
shapes = self.get_selected()
|
||||
geo_shapes = self.get_selected()
|
||||
|
||||
try:
|
||||
results = shapes[0].geo
|
||||
results = geo_shapes[0].geo
|
||||
except Exception as e:
|
||||
log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e))
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] A selection of at least 2 geo items is required to do Intersection."))
|
||||
self.app.inform.emit(
|
||||
_("[WARNING_NOTCL] A selection of at least 2 geo items is required to do Intersection."))
|
||||
self.select_tool('select')
|
||||
return
|
||||
|
||||
for shape in shapes[1:]:
|
||||
results = results.intersection(shape.geo)
|
||||
for shape_el in geo_shapes[1:]:
|
||||
results = results.intersection(shape_el.geo)
|
||||
|
||||
# Delete originals.
|
||||
for_deletion = [s for s in self.get_selected()]
|
||||
for shape in for_deletion:
|
||||
self.delete_shape(shape)
|
||||
for shape_el in for_deletion:
|
||||
self.delete_shape(shape_el)
|
||||
|
||||
# Selected geometry is now gone!
|
||||
self.selected = []
|
||||
|
@ -3953,30 +4070,31 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
:return: None
|
||||
"""
|
||||
|
||||
shapes = self.get_selected()
|
||||
geo_shapes = self.get_selected()
|
||||
results = []
|
||||
intact = []
|
||||
|
||||
try:
|
||||
intersector = shapes[0].geo
|
||||
intersector = geo_shapes[0].geo
|
||||
except Exception as e:
|
||||
log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e))
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] A selection of at least 2 geo items is required to do Intersection."))
|
||||
self.app.inform.emit(
|
||||
_("[WARNING_NOTCL] A selection of at least 2 geo items is required to do Intersection."))
|
||||
self.select_tool('select')
|
||||
return
|
||||
|
||||
for shape in shapes[1:]:
|
||||
if intersector.intersects(shape.geo):
|
||||
results.append(intersector.intersection(shape.geo))
|
||||
for shape_el in geo_shapes[1:]:
|
||||
if intersector.intersects(shape_el.geo):
|
||||
results.append(intersector.intersection(shape_el.geo))
|
||||
else:
|
||||
intact.append(shape)
|
||||
intact.append(shape_el)
|
||||
|
||||
if len(results) != 0:
|
||||
# Delete originals.
|
||||
for_deletion = [s for s in self.get_selected()]
|
||||
for shape in for_deletion:
|
||||
if shape not in intact:
|
||||
self.delete_shape(shape)
|
||||
for shape_el in for_deletion:
|
||||
if shape_el not in intact:
|
||||
self.delete_shape(shape_el)
|
||||
|
||||
for geo in results:
|
||||
self.add_shape(DrawToolShape(geo))
|
||||
|
@ -4031,8 +4149,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
try:
|
||||
for linestring in target.geo:
|
||||
self.add_shape(DrawToolShape(linestring.difference(toolgeo)))
|
||||
except:
|
||||
self.app.log.warning("Current LinearString does not intersect the target")
|
||||
except Exception as e:
|
||||
self.app.log.warning("Current LinearString does not intersect the target. %s" % str(e))
|
||||
else:
|
||||
self.app.log.warning("Not implemented. Object type: %s" % str(type(target.geo)))
|
||||
return
|
||||
|
@ -4045,7 +4163,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
|
||||
if buf_distance < 0:
|
||||
self.app.inform.emit(
|
||||
_( "[ERROR_NOTCL] Negative buffer value is not accepted. Use Buffer interior to generate an 'inside' shape"))
|
||||
_("[ERROR_NOTCL] Negative buffer value is not accepted. "
|
||||
"Use Buffer interior to generate an 'inside' shape"))
|
||||
|
||||
# deselect everything
|
||||
self.selected = []
|
||||
|
@ -4285,13 +4404,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
if reset:
|
||||
self.flat_geo = []
|
||||
|
||||
## If iterable, expand recursively.
|
||||
# If iterable, expand recursively.
|
||||
try:
|
||||
for geo in geometry:
|
||||
if geo is not None:
|
||||
recurse(geometry=geo, reset=False)
|
||||
for geo_el in geometry:
|
||||
if geo_el is not None:
|
||||
recurse(geometry=geo_el, reset=False)
|
||||
|
||||
## Not iterable, do the actual indexing and add.
|
||||
# Not iterable, do the actual indexing and add.
|
||||
except TypeError:
|
||||
self.flat_geo.append(geometry)
|
||||
|
||||
|
@ -4351,3 +4470,22 @@ def mag(vec):
|
|||
|
||||
def poly2rings(poly):
|
||||
return [poly.exterior] + [interior for interior in poly.interiors]
|
||||
|
||||
|
||||
def get_shapely_list_bounds(geometry_list):
|
||||
xmin = Inf
|
||||
ymin = Inf
|
||||
xmax = -Inf
|
||||
ymax = -Inf
|
||||
|
||||
for gs in geometry_list:
|
||||
try:
|
||||
gxmin, gymin, gxmax, gymax = gs.bounds
|
||||
xmin = min([xmin, gxmin])
|
||||
ymin = min([ymin, gymin])
|
||||
xmax = max([xmax, gxmax])
|
||||
ymax = max([ymax, gymax])
|
||||
except Exception as e:
|
||||
log.warning("DEVELOPMENT: Tried to get bounds of empty geometry. --> %s" % str(e))
|
||||
|
||||
return [xmin, ymin, xmax, ymax]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -686,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'))
|
||||
|
@ -720,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'),
|
||||
|
@ -922,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"
|
||||
|
@ -932,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."))
|
||||
|
@ -940,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)
|
||||
|
@ -951,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."))
|
||||
|
@ -1908,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'))
|
||||
|
@ -1942,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'),
|
||||
|
@ -3149,6 +3156,9 @@ class GerberPreferencesUI(QtWidgets.QWidget):
|
|||
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)
|
||||
|
@ -3157,6 +3167,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
|
|||
self.layout.addWidget(self.gerber_gen_group)
|
||||
self.layout.addLayout(self.vlay)
|
||||
self.layout.addWidget(self.gerber_adv_opt_group)
|
||||
self.layout.addWidget(self.gerber_editor_group)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
|
@ -3201,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()
|
||||
|
||||
|
@ -3928,9 +3942,16 @@ 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'))
|
||||
|
@ -4180,28 +4201,28 @@ 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_entry = FloatEntry2()
|
||||
grid0.addWidget(self.buffer_aperture_entry, 3, 1)
|
||||
# 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.layout.addStretch()
|
||||
|
||||
|
@ -4300,6 +4321,40 @@ class GerberExpPrefGroupUI(OptionsGroupUI):
|
|||
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()
|
||||
|
||||
|
||||
class ExcellonGenPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
@ -4525,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]
|
||||
|
@ -4627,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(
|
||||
|
@ -4639,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])
|
||||
|
||||
|
@ -4652,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
|
||||
|
@ -4669,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()
|
||||
|
@ -5120,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(
|
||||
|
@ -5131,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])
|
||||
|
||||
|
@ -5143,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()
|
||||
|
||||
|
@ -5280,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)
|
||||
|
|
|
@ -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")
|
||||
|
@ -60,7 +60,7 @@ class CutOut(FlatCAMTool):
|
|||
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,7 +76,7 @@ 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"
|
||||
_("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,7 +85,7 @@ class CutOut(FlatCAMTool):
|
|||
self.margin = FCEntry()
|
||||
self.margin_label = QtWidgets.QLabel(_("Margin:"))
|
||||
self.margin_label.setToolTip(
|
||||
_( "Margin over bounds. A positive value here\n"
|
||||
_("Margin over bounds. A positive value here\n"
|
||||
"will make the cutout of the PCB further from\n"
|
||||
"the actual PCB border")
|
||||
)
|
||||
|
@ -95,7 +95,7 @@ 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"
|
||||
_("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).")
|
||||
|
@ -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)
|
||||
|
||||
|
@ -151,7 +152,7 @@ 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)
|
||||
|
||||
|
@ -193,7 +194,7 @@ class CutOut(FlatCAMTool):
|
|||
)
|
||||
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"
|
||||
|
@ -202,11 +203,11 @@ class CutOut(FlatCAMTool):
|
|||
)
|
||||
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()))
|
||||
|
@ -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."
|
||||
|
@ -399,70 +401,84 @@ class CutOut(FlatCAMTool):
|
|||
|
||||
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):
|
||||
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
|
||||
|
||||
# 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 == '2LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
py - gapsize + leny / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
subtract_rectangle(cutout_obj,
|
||||
py + gapsize + leny / 4) # topright_y
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
py - gapsize - leny / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
py + gapsize - leny / 4)
|
||||
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize + lenghtx / 4,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize + lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
px + gapsize + lenx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize - lenghtx / 4,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize - lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
px + gapsize - lenx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
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."))
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
||||
|
@ -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
|
||||
|
||||
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 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':
|
||||
subtract_rectangle(cutout_obj,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize, # botleft_x
|
||||
py - gapsize + lenghty / 4, # botleft_y
|
||||
py - gapsize + leny / 4, # botleft_y
|
||||
xmax + gapsize, # topright_x
|
||||
py + gapsize + lenghty / 4) # topright_y
|
||||
subtract_rectangle(cutout_obj,
|
||||
py + gapsize + leny / 4) # topright_y
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize - lenghty / 4,
|
||||
py - gapsize - leny / 4,
|
||||
xmax + gapsize,
|
||||
py + gapsize - lenghty / 4)
|
||||
py + gapsize - leny / 4)
|
||||
|
||||
if gaps == '8' or gaps == '2TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize + lenghtx / 4,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize + lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize + lenghtx / 4,
|
||||
px + gapsize + lenx / 4,
|
||||
ymax + gapsize)
|
||||
subtract_rectangle(cutout_obj,
|
||||
px - gapsize - lenghtx / 4,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
px - gapsize - lenx / 4,
|
||||
ymin - gapsize,
|
||||
px + gapsize - lenghtx / 4,
|
||||
px + gapsize - lenx / 4,
|
||||
ymax + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'LR':
|
||||
subtract_rectangle(cutout_obj,
|
||||
geo = self.subtract_poly_from_geo(geo,
|
||||
xmin - gapsize,
|
||||
py - gapsize,
|
||||
xmax + gapsize,
|
||||
py + gapsize)
|
||||
|
||||
if gaps == '4' or gaps == 'TB':
|
||||
subtract_rectangle(cutout_obj,
|
||||
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)
|
||||
|
||||
cutout_obj.plot()
|
||||
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."))
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
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'])
|
||||
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)
|
||||
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])
|
||||
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)
|
||||
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'])
|
||||
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])
|
||||
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)
|
||||
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])
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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:
|
||||
apertures_dict['0']['solid_geometry'] += path_geo
|
||||
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:
|
||||
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])
|
||||
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)
|
||||
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])
|
||||
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)
|
||||
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])
|
||||
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)
|
||||
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
|
||||
# ###############################################
|
||||
# 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:
|
||||
apertures_dict['0']['solid_geometry'] += fill_geo
|
||||
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']['solid_geometry'] = []
|
||||
apertures_dict['0']['solid_geometry'] += fill_geo
|
||||
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:
|
||||
|
|
|
@ -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)
|
||||
for geo_el in geo:
|
||||
new_el = dict()
|
||||
|
||||
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_solid_geometry.append(new_geo)
|
||||
new_el['solid'] = new_geo
|
||||
else:
|
||||
new_solid_geometry.append(geo_silk)
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_solid_geometry.append(geo_silk)
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_solid_geometry.append(geo_silk)
|
||||
new_el['solid'] = work_geo
|
||||
else:
|
||||
new_el['solid'] = work_geo
|
||||
|
||||
if new_solid_geometry:
|
||||
while not self.new_apertures[apid]['solid_geometry']:
|
||||
self.new_apertures[apid]['solid_geometry'] = deepcopy(new_solid_geometry)
|
||||
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)
|
||||
|
||||
|
|
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:
|
||||
|
|
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,8 +226,8 @@ 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':
|
||||
|
@ -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