Merged marius_stanciu/flatcam_beta/Beta_8.994 into Beta
This commit is contained in:
commit
7ca5d69ed4
|
@ -15,6 +15,11 @@ CHANGELOG for FlatCAM beta
|
|||
- fix an older issue that made that only the Custom choice created an effect when changing the Offset in the Geometry Object Tool Table
|
||||
- trying to optimize Gerber Editor selection with the mouse
|
||||
- optimized some of the strings
|
||||
- fixed the project context save functionality to work in the new program configuration
|
||||
- updated Turkish translation (by Mehmet Kaya)
|
||||
- in NCC and Isolation Tools, the Validity Checking of the tools is now multithreaded when the Check Validity UI control is checked
|
||||
- translation strings updated
|
||||
- fixed an error in Gerber parser, when it encounter a pen-up followed by pen-down move while in a region
|
||||
|
||||
2.11.2020
|
||||
|
||||
|
|
|
@ -953,36 +953,41 @@ class Gerber(Geometry):
|
|||
# Reset path starting point
|
||||
path = [[current_x, current_y]]
|
||||
|
||||
# --- BUFFERED ---
|
||||
# Draw the flash
|
||||
# this treats the case when we are storing geometry as paths
|
||||
geo_dict = {}
|
||||
geo_flash = Point([current_x, current_y])
|
||||
follow_buffer.append(geo_flash)
|
||||
geo_dict['follow'] = geo_flash
|
||||
# treat the case when there is a flash inside a Gerber Region when the current_aperture
|
||||
# is None
|
||||
if current_aperture is None:
|
||||
pass
|
||||
else:
|
||||
# --- BUFFERED ---
|
||||
# Draw the flash
|
||||
# this treats the case when we are storing geometry as paths
|
||||
geo_dict = {}
|
||||
geo_flash = Point([current_x, current_y])
|
||||
follow_buffer.append(geo_flash)
|
||||
geo_dict['follow'] = geo_flash
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
flash = self.create_flash_geometry(
|
||||
Point([current_x, current_y]),
|
||||
self.apertures[current_aperture],
|
||||
self.steps_per_circle
|
||||
)
|
||||
if not flash.is_empty:
|
||||
if self.app.defaults['gerber_simplification']:
|
||||
poly_buffer.append(flash.simplify(s_tol))
|
||||
else:
|
||||
poly_buffer.append(flash)
|
||||
# this treats the case when we are storing geometry as solids
|
||||
flash = self.create_flash_geometry(
|
||||
Point([current_x, current_y]),
|
||||
self.apertures[current_aperture],
|
||||
self.steps_per_circle
|
||||
)
|
||||
if not flash.is_empty:
|
||||
if self.app.defaults['gerber_simplification']:
|
||||
poly_buffer.append(flash.simplify(s_tol))
|
||||
else:
|
||||
poly_buffer.append(flash)
|
||||
|
||||
if self.is_lpc is True:
|
||||
geo_dict['clear'] = flash
|
||||
else:
|
||||
geo_dict['solid'] = flash
|
||||
if self.is_lpc is True:
|
||||
geo_dict['clear'] = flash
|
||||
else:
|
||||
geo_dict['solid'] = flash
|
||||
|
||||
if current_aperture not in self.apertures:
|
||||
self.apertures[current_aperture] = {}
|
||||
if 'geometry' not in self.apertures[current_aperture]:
|
||||
self.apertures[current_aperture]['geometry'] = []
|
||||
self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
|
||||
if current_aperture not in self.apertures:
|
||||
self.apertures[current_aperture] = {}
|
||||
if 'geometry' not in self.apertures[current_aperture]:
|
||||
self.apertures[current_aperture]['geometry'] = []
|
||||
self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
|
||||
|
||||
if making_region is False:
|
||||
# if the aperture is rectangle then add a rectangular shape having as parameters the
|
||||
|
|
|
@ -908,9 +908,59 @@ class ToolIsolation(AppTool, Gerber):
|
|||
})
|
||||
|
||||
def on_find_optimal_tooldia(self):
|
||||
self.find_safe_tooldia_worker(is_displayed=True)
|
||||
self.find_safe_tooldia_worker()
|
||||
|
||||
def find_safe_tooldia_worker(self, is_displayed):
|
||||
@staticmethod
|
||||
def find_optim_mp(aperture_storage, decimals):
|
||||
msg = 'ok'
|
||||
total_geo = []
|
||||
|
||||
for ap in list(aperture_storage.keys()):
|
||||
if 'geometry' in aperture_storage[ap]:
|
||||
for geo_el in aperture_storage[ap]['geometry']:
|
||||
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
|
||||
total_geo.append(geo_el['solid'])
|
||||
|
||||
total_geo = MultiPolygon(total_geo)
|
||||
total_geo = total_geo.buffer(0)
|
||||
|
||||
try:
|
||||
__ = iter(total_geo)
|
||||
geo_len = len(total_geo)
|
||||
except TypeError:
|
||||
msg = ('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n"
|
||||
"There are no distances between geometry elements to be found."))
|
||||
|
||||
min_dict = {}
|
||||
idx = 1
|
||||
for geo in total_geo:
|
||||
for s_geo in total_geo[idx:]:
|
||||
# minimize the number of distances by not taking into considerations
|
||||
# those that are too small
|
||||
dist = geo.distance(s_geo)
|
||||
dist = float('%.*f' % (decimals, dist))
|
||||
loc_1, loc_2 = nearest_points(geo, s_geo)
|
||||
|
||||
proc_loc = (
|
||||
(float('%.*f' % (decimals, loc_1.x)), float('%.*f' % (decimals, loc_1.y))),
|
||||
(float('%.*f' % (decimals, loc_2.x)), float('%.*f' % (decimals, loc_2.y)))
|
||||
)
|
||||
|
||||
if dist in min_dict:
|
||||
min_dict[dist].append(proc_loc)
|
||||
else:
|
||||
min_dict[dist] = [proc_loc]
|
||||
|
||||
idx += 1
|
||||
|
||||
min_list = list(min_dict.keys())
|
||||
min_dist = min(min_list)
|
||||
|
||||
return msg, min_dist
|
||||
|
||||
# multiprocessing variant
|
||||
def find_safe_tooldia_multiprocessing(self):
|
||||
self.app.inform.emit(_("Checking tools for validity."))
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
|
||||
obj_name = self.ui.object_combo.currentText()
|
||||
|
@ -926,8 +976,73 @@ class ToolIsolation(AppTool, Gerber):
|
|||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
def job_thread(app_obj, is_display):
|
||||
with self.app.proc_container.new(_("Working ...")):
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new(_("Checking ...")):
|
||||
|
||||
ap_storage = fcobj.apertures
|
||||
|
||||
p = app_obj.pool.apply_async(self.find_optim_mp, args=(ap_storage, self.decimals))
|
||||
res = p.get()
|
||||
|
||||
if res[0] != 'ok':
|
||||
app_obj.inform.emit(res[0])
|
||||
return 'fail'
|
||||
else:
|
||||
min_dist = res[1]
|
||||
|
||||
try:
|
||||
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
|
||||
self.safe_tooldia = min_dist_truncated
|
||||
|
||||
if self.safe_tooldia:
|
||||
# find the selected tool ID's
|
||||
sorted_tools = []
|
||||
table_items = self.ui.tools_table.selectedItems()
|
||||
sel_rows = {t.row() for t in table_items}
|
||||
for row in sel_rows:
|
||||
tid = int(self.ui.tools_table.item(row, 3).text())
|
||||
sorted_tools.append(tid)
|
||||
if not sorted_tools:
|
||||
msg = _("There are no tools selected in the Tool Table.")
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
|
||||
return 'fail'
|
||||
|
||||
# check if the tools diameters are less then the safe tool diameter
|
||||
for tool in sorted_tools:
|
||||
tool_dia = float(self.iso_tools[tool]['tooldia'])
|
||||
if tool_dia > self.safe_tooldia:
|
||||
msg = _("Incomplete isolation. "
|
||||
"At least one tool could not do a complete isolation.")
|
||||
self.app.inform.emit('[WARNING] %s' % msg)
|
||||
break
|
||||
|
||||
# reset the value to prepare for another isolation
|
||||
self.safe_tooldia = None
|
||||
except Exception as ee:
|
||||
log.debug(str(ee))
|
||||
return
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def find_safe_tooldia_worker(self):
|
||||
self.app.inform.emit(_("Checking tools for validity."))
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
|
||||
obj_name = self.ui.object_combo.currentText()
|
||||
|
||||
# Get source object.
|
||||
try:
|
||||
fcobj = self.app.collection.get_by_name(obj_name)
|
||||
except Exception:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
|
||||
return
|
||||
|
||||
if fcobj is None:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new(_("Checking ...")):
|
||||
try:
|
||||
old_disp_number = 0
|
||||
pol_nr = 0
|
||||
|
@ -995,42 +1110,16 @@ class ToolIsolation(AppTool, Gerber):
|
|||
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
|
||||
self.safe_tooldia = min_dist_truncated
|
||||
|
||||
if is_display:
|
||||
self.optimal_found_sig.emit(min_dist_truncated)
|
||||
self.optimal_found_sig.emit(min_dist_truncated)
|
||||
|
||||
app_obj.inform.emit('[success] %s: %s %s' %
|
||||
(_("Optimal tool diameter found"), str(min_dist_truncated),
|
||||
self.units.lower()))
|
||||
else:
|
||||
if self.safe_tooldia:
|
||||
# find the selected tool ID's
|
||||
sorted_tools = []
|
||||
table_items = self.ui.tools_table.selectedItems()
|
||||
sel_rows = {t.row() for t in table_items}
|
||||
for row in sel_rows:
|
||||
tid = int(self.ui.tools_table.item(row, 3).text())
|
||||
sorted_tools.append(tid)
|
||||
if not sorted_tools:
|
||||
msg = _("There are no tools selected in the Tool Table.")
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
|
||||
return 'fail'
|
||||
|
||||
# check if the tools diameters are less then the safe tool diameter
|
||||
for tool in sorted_tools:
|
||||
tool_dia = float(self.iso_tools[tool]['tooldia'])
|
||||
if tool_dia > self.safe_tooldia:
|
||||
msg = _("Incomplete isolation. "
|
||||
"At least one tool could not do a complete isolation.")
|
||||
self.app.inform.emit('[WARNING] %s' % msg)
|
||||
break
|
||||
|
||||
# reset the value to prepare for another isolation
|
||||
self.safe_tooldia = None
|
||||
app_obj.inform.emit('[success] %s: %s %s' %
|
||||
(_("Optimal tool diameter found"), str(min_dist_truncated),
|
||||
self.units.lower()))
|
||||
except Exception as ee:
|
||||
log.debug(str(ee))
|
||||
return
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, is_displayed]})
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def on_tool_add(self, custom_dia=None):
|
||||
self.blockSignals(True)
|
||||
|
@ -1384,7 +1473,7 @@ class ToolIsolation(AppTool, Gerber):
|
|||
return
|
||||
|
||||
if self.ui.valid_cb.get_value() is True:
|
||||
self.find_safe_tooldia_worker(is_displayed=False)
|
||||
self.find_safe_tooldia_multiprocessing()
|
||||
|
||||
def worker_task(iso_obj):
|
||||
with self.app.proc_container.new(_("Isolating ...")):
|
||||
|
|
|
@ -845,10 +845,59 @@ class NonCopperClear(AppTool, Gerber):
|
|||
})
|
||||
|
||||
def on_find_optimal_tooldia(self):
|
||||
self.find_safe_tooldia_worker(is_displayed=True)
|
||||
self.find_safe_tooldia_worker()
|
||||
|
||||
def find_safe_tooldia_worker(self, is_displayed):
|
||||
self.app.inform.emit(_("NCC Tool. Checking tools for validity."))
|
||||
@staticmethod
|
||||
def find_optim_mp(aperture_storage, decimals):
|
||||
msg = 'ok'
|
||||
total_geo = []
|
||||
|
||||
for ap in list(aperture_storage.keys()):
|
||||
if 'geometry' in aperture_storage[ap]:
|
||||
for geo_el in aperture_storage[ap]['geometry']:
|
||||
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
|
||||
total_geo.append(geo_el['solid'])
|
||||
|
||||
total_geo = MultiPolygon(total_geo)
|
||||
total_geo = total_geo.buffer(0)
|
||||
|
||||
try:
|
||||
__ = iter(total_geo)
|
||||
geo_len = len(total_geo)
|
||||
except TypeError:
|
||||
msg = ('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n"
|
||||
"There are no distances between geometry elements to be found."))
|
||||
|
||||
min_dict = {}
|
||||
idx = 1
|
||||
for geo in total_geo:
|
||||
for s_geo in total_geo[idx:]:
|
||||
# minimize the number of distances by not taking into considerations
|
||||
# those that are too small
|
||||
dist = geo.distance(s_geo)
|
||||
dist = float('%.*f' % (decimals, dist))
|
||||
loc_1, loc_2 = nearest_points(geo, s_geo)
|
||||
|
||||
proc_loc = (
|
||||
(float('%.*f' % (decimals, loc_1.x)), float('%.*f' % (decimals, loc_1.y))),
|
||||
(float('%.*f' % (decimals, loc_2.x)), float('%.*f' % (decimals, loc_2.y)))
|
||||
)
|
||||
|
||||
if dist in min_dict:
|
||||
min_dict[dist].append(proc_loc)
|
||||
else:
|
||||
min_dict[dist] = [proc_loc]
|
||||
|
||||
idx += 1
|
||||
|
||||
min_list = list(min_dict.keys())
|
||||
min_dist = min(min_list)
|
||||
|
||||
return msg, min_dist
|
||||
|
||||
# multiprocessing variant
|
||||
def find_safe_tooldia_multiprocessing(self):
|
||||
self.app.inform.emit(_("Checking tools for validity."))
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
|
||||
obj_name = self.ui.object_combo.currentText()
|
||||
|
@ -864,8 +913,77 @@ class NonCopperClear(AppTool, Gerber):
|
|||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
def job_thread(app_obj, is_display):
|
||||
with self.app.proc_container.new(_("Working...")):
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new(_("Checking ...")):
|
||||
|
||||
ap_storage = fcobj.apertures
|
||||
|
||||
p = app_obj.pool.apply_async(self.find_optim_mp, args=(ap_storage, self.decimals))
|
||||
res = p.get()
|
||||
|
||||
if res[0] != 'ok':
|
||||
app_obj.inform.emit(res[0])
|
||||
return 'fail'
|
||||
else:
|
||||
min_dist = res[1]
|
||||
|
||||
try:
|
||||
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
|
||||
self.safe_tooldia = min_dist_truncated
|
||||
|
||||
# find the selected tool ID's
|
||||
sorted_tools = []
|
||||
table_items = self.ui.tools_table.selectedItems()
|
||||
sel_rows = {t.row() for t in table_items}
|
||||
for row in sel_rows:
|
||||
tid = int(self.ui.tools_table.item(row, 3).text())
|
||||
sorted_tools.append(tid)
|
||||
if not sorted_tools:
|
||||
msg = _("There are no tools selected in the Tool Table.")
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
|
||||
return 'fail'
|
||||
|
||||
# check if the tools diameters are less then the safe tool diameter
|
||||
suitable_tools = []
|
||||
for tool in sorted_tools:
|
||||
tool_dia = float(self.ncc_tools[tool]['tooldia'])
|
||||
if tool_dia <= self.safe_tooldia:
|
||||
suitable_tools.append(tool_dia)
|
||||
|
||||
if not suitable_tools:
|
||||
msg = _("Incomplete isolation. None of the selected tools could do a complete isolation.")
|
||||
self.app.inform.emit('[WARNING] %s' % msg)
|
||||
else:
|
||||
msg = _("At least one of the selected tools can do a complete isolation.")
|
||||
self.app.inform.emit('[success] %s' % msg)
|
||||
|
||||
# reset the value to prepare for another isolation
|
||||
self.safe_tooldia = None
|
||||
except Exception as ee:
|
||||
log.debug(str(ee))
|
||||
return
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def find_safe_tooldia_worker(self):
|
||||
self.app.inform.emit(_("Checking tools for validity."))
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
|
||||
obj_name = self.ui.object_combo.currentText()
|
||||
|
||||
# Get source object.
|
||||
try:
|
||||
fcobj = self.app.collection.get_by_name(obj_name)
|
||||
except Exception:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
|
||||
return
|
||||
|
||||
if fcobj is None:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new(_("Checking ...")):
|
||||
try:
|
||||
old_disp_number = 0
|
||||
pol_nr = 0
|
||||
|
@ -933,46 +1051,16 @@ class NonCopperClear(AppTool, Gerber):
|
|||
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
|
||||
self.safe_tooldia = min_dist_truncated
|
||||
|
||||
if is_display:
|
||||
self.optimal_found_sig.emit(min_dist_truncated)
|
||||
self.optimal_found_sig.emit(min_dist_truncated)
|
||||
|
||||
app_obj.inform.emit('[success] %s: %s %s' %
|
||||
(_("Optimal tool diameter found"), str(min_dist_truncated),
|
||||
self.units.lower()))
|
||||
else:
|
||||
# find the selected tool ID's
|
||||
sorted_tools = []
|
||||
table_items = self.ui.tools_table.selectedItems()
|
||||
sel_rows = {t.row() for t in table_items}
|
||||
for row in sel_rows:
|
||||
tid = int(self.ui.tools_table.item(row, 3).text())
|
||||
sorted_tools.append(tid)
|
||||
if not sorted_tools:
|
||||
msg = _("There are no tools selected in the Tool Table.")
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
|
||||
return 'fail'
|
||||
|
||||
# check if the tools diameters are less then the safe tool diameter
|
||||
suitable_tools = []
|
||||
for tool in sorted_tools:
|
||||
tool_dia = float(self.ncc_tools[tool]['tooldia'])
|
||||
if tool_dia <= self.safe_tooldia:
|
||||
suitable_tools.append(tool_dia)
|
||||
|
||||
if not suitable_tools:
|
||||
msg = _("Incomplete isolation. None of the selected tools could do a complete isolation.")
|
||||
self.app.inform.emit('[WARNING] %s' % msg)
|
||||
else:
|
||||
msg = _("At least one of the selected tools can do a complete isolation.")
|
||||
self.app.inform.emit('[success] %s' % msg)
|
||||
|
||||
# reset the value to prepare for another isolation
|
||||
self.safe_tooldia = None
|
||||
app_obj.inform.emit('[success] %s: %s %s' %
|
||||
(_("Optimal tool diameter found"), str(min_dist_truncated),
|
||||
self.units.lower()))
|
||||
except Exception as ee:
|
||||
log.debug(str(ee))
|
||||
return
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, is_displayed]})
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def on_tool_add(self, custom_dia=None):
|
||||
self.blockSignals(True)
|
||||
|
@ -1327,7 +1415,8 @@ class NonCopperClear(AppTool, Gerber):
|
|||
return
|
||||
|
||||
if self.ui.valid_cb.get_value() is True:
|
||||
self.find_safe_tooldia_worker(is_displayed=False)
|
||||
# this is done in another Process
|
||||
self.find_safe_tooldia_multiprocessing()
|
||||
|
||||
# use the selected tools in the tool table; get diameters for isolation
|
||||
self.iso_dia_list = []
|
||||
|
|
10
app_Main.py
10
app_Main.py
|
@ -7107,17 +7107,17 @@ class App(QtCore.QObject):
|
|||
|
||||
obj = self.collection.get_active()
|
||||
if type(obj) == GeometryObject:
|
||||
self.on_file_exportdxf()
|
||||
self.f_handlers.on_file_exportdxf()
|
||||
elif type(obj) == ExcellonObject:
|
||||
self.on_file_saveexcellon()
|
||||
self.f_handlers.on_file_saveexcellon()
|
||||
elif type(obj) == CNCJobObject:
|
||||
obj.on_exportgcode_button_click()
|
||||
elif type(obj) == GerberObject:
|
||||
self.on_file_savegerber()
|
||||
self.f_handlers.on_file_savegerber()
|
||||
elif type(obj) == ScriptObject:
|
||||
self.on_file_savescript()
|
||||
self.f_handlers.on_file_savescript()
|
||||
elif type(obj) == DocumentObject:
|
||||
self.on_file_savedocument()
|
||||
self.f_handlers.on_file_savedocument()
|
||||
|
||||
def obj_move(self):
|
||||
"""
|
||||
|
|
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
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
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
Loading…
Reference in New Issue