Merged marius_stanciu/flatcam_beta/Beta_8.994 into Beta

This commit is contained in:
Marius Stanciu 2020-11-03 17:40:34 +02:00
commit 7ca5d69ed4
24 changed files with 3469 additions and 3290 deletions

View File

@ -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

View File

@ -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

View File

@ -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 ...")):

View File

@ -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 = []

View File

@ -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