- Panelize Tool - add a new option for the panels of type Geometry named Path Optimiztion. If the checkbox is checked then all the LineStrings that are overlapped in the resulting multigeo Geometry panel object will keep only one of the paths thus minimizing the tool cuts.

This commit is contained in:
Marius Stanciu 2020-06-18 18:50:02 +03:00 committed by Marius
parent 7be4d98172
commit 8dc4eecbf4
5 changed files with 92 additions and 26 deletions

View File

@ -15,6 +15,7 @@ CHANGELOG for FlatCAM beta
- Cutout Tool - in manual gap adding there is now an option to automatically turn on the big cursor which could help
- Cutout Tool - fixed errors when trying to add a manual gap without having a geometry object selected in the combobox
- Cutout Tool - made sure that all the paths generated by this tool are contiguous which means that two lines that meet at one end will become onle line therefore reducing unnecessary Z moves
- Panelize Tool - add a new option for the panels of type Geometry named Path Optimiztion. If the checkbox is checked then all the LineStrings that are overlapped in the resulting multigeo Geometry panel object will keep only one of the paths thus minimizing the tool cuts.
17.06.2020

View File

@ -437,6 +437,7 @@ class PreferencesUIManager:
"tools_panelize_spacing_rows": self.ui.tools_defaults_form.tools_panelize_group.pspacing_rows,
"tools_panelize_columns": self.ui.tools_defaults_form.tools_panelize_group.pcolumns,
"tools_panelize_rows": self.ui.tools_defaults_form.tools_panelize_group.prows,
"tools_panelize_optimization": self.ui.tools_defaults_form.tools_panelize_group.poptimization_cb,
"tools_panelize_constrain": self.ui.tools_defaults_form.tools_panelize_group.pconstrain_cb,
"tools_panelize_constrainx": self.ui.tools_defaults_form.tools_panelize_group.px_width_entry,
"tools_panelize_constrainy": self.ui.tools_defaults_form.tools_panelize_group.py_height_entry,

View File

@ -106,6 +106,16 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.panel_type_label, 4, 0)
grid0.addWidget(self.panel_type_radio, 4, 1)
# Path optimization
self.poptimization_cb = FCCheckBox('%s' % _("Path Optimization"))
self.poptimization_cb.setToolTip(
_("Active only for Geometry panel type.\n"
"When checked the application will find\n"
"any two overlapping Line elements in the panel\n"
"and remove the overlapping parts, keeping only one of them.")
)
grid0.addWidget(self.poptimization_cb, 5, 0, 1, 2)
# ## Constrains
self.pconstrain_cb = FCCheckBox('%s:' % _("Constrain within"))
self.pconstrain_cb.setToolTip(
@ -115,7 +125,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
"the final panel will have as many columns and rows as\n"
"they fit completely within selected area.")
)
grid0.addWidget(self.pconstrain_cb, 5, 0, 1, 2)
grid0.addWidget(self.pconstrain_cb, 10, 0, 1, 2)
self.px_width_entry = FCDoubleSpinner()
self.px_width_entry.set_range(0.000001, 9999.9999)
@ -127,8 +137,8 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
_("The width (DX) within which the panel must fit.\n"
"In current units.")
)
grid0.addWidget(self.x_width_lbl, 6, 0)
grid0.addWidget(self.px_width_entry, 6, 1)
grid0.addWidget(self.x_width_lbl, 12, 0)
grid0.addWidget(self.px_width_entry, 12, 1)
self.py_height_entry = FCDoubleSpinner()
self.py_height_entry.set_range(0.000001, 9999.9999)
@ -140,7 +150,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
_("The height (DY)within which the panel must fit.\n"
"In current units.")
)
grid0.addWidget(self.y_height_lbl, 7, 0)
grid0.addWidget(self.py_height_entry, 7, 1)
grid0.addWidget(self.y_height_lbl, 17, 0)
grid0.addWidget(self.py_height_entry, 17, 1)
self.layout.addStretch()

View File

@ -15,8 +15,8 @@ from copy import deepcopy
import numpy as np
import shapely.affinity as affinity
from shapely.ops import unary_union
from shapely.geometry import LineString
from shapely.ops import unary_union, linemerge, snap
from shapely.geometry import LineString, MultiLineString
import gettext
import appTranslation as fcTranslate
@ -112,6 +112,10 @@ class Panelize(AppTool):
self.app.defaults["tools_panelize_columns"] else 0.0
self.ui.columns.set_value(int(cc))
optimized_path_cb = self.app.defaults["tools_panelize_optimization"] if \
self.app.defaults["tools_panelize_optimization"] else True
self.ui.optimization_cb.set_value(optimized_path_cb)
c_cb = self.app.defaults["tools_panelize_constrain"] if \
self.app.defaults["tools_panelize_constrain"] else False
self.ui.constrain_cb.set_value(c_cb)
@ -128,6 +132,8 @@ class Panelize(AppTool):
self.app.defaults["tools_panelize_panel_type"] else 'gerber'
self.ui.panel_type_radio.set_value(panel_type)
self.ui.on_panel_type(val=panel_type)
# run once the following so the obj_type attribute is updated in the FCComboBoxes
# such that the last loaded object is populated in the combo boxes
self.on_type_obj_index_changed()
@ -145,10 +151,12 @@ class Panelize(AppTool):
if self.ui.type_obj_combo.currentText() != 'Excellon':
self.ui.panel_type_label.setDisabled(False)
self.ui.panel_type_radio.setDisabled(False)
self.ui.on_panel_type(val=self.ui.panel_type_radio.get_value())
else:
self.ui.panel_type_label.setDisabled(True)
self.ui.panel_type_radio.setDisabled(True)
self.ui.panel_type_radio.set_value('geometry')
self.ui.optimization_cb.setDisabled(True)
def on_type_box_index_changed(self):
obj_type = self.ui.type_box_combo.currentIndex()
@ -257,6 +265,8 @@ class Panelize(AppTool):
for tt, tt_val in list(panel_source_obj.apertures.items()):
copied_apertures[tt] = deepcopy(tt_val)
to_optimize = self.ui.optimization_cb.get_value()
def panelize_worker():
if panel_source_obj is not None:
self.app.inform.emit(_("Generating panel ... "))
@ -370,7 +380,7 @@ class Panelize(AppTool):
obj_fin.tools = copied_tools
if panel_source_obj.multigeo is True:
for tool in panel_source_obj.tools:
obj_fin.tools[tool]['solid_geometry'][:] = []
obj_fin.tools[tool]['solid_geometry'] = []
elif panel_source_obj.kind == 'gerber':
obj_fin.apertures = copied_apertures
for ap in obj_fin.apertures:
@ -385,11 +395,6 @@ class Panelize(AppTool):
geo_len += len(panel_source_obj.tools[tool]['solid_geometry'])
except TypeError:
geo_len += 1
# else:
# try:
# geo_len = len(panel_source_obj.solid_geometry)
# except TypeError:
# geo_len = 1
elif panel_source_obj.kind == 'gerber':
for ap in panel_source_obj.apertures:
if 'geometry' in panel_source_obj.apertures[ap]:
@ -410,17 +415,20 @@ class Panelize(AppTool):
if panel_source_obj.kind == 'geometry':
if panel_source_obj.multigeo is True:
for tool in panel_source_obj.tools:
# graceful abort requested by the user
if app_obj.abort_flag:
# graceful abort requested by the user
raise grace
# calculate the number of polygons
geo_len = len(panel_source_obj.tools[tool]['solid_geometry'])
# panelization
pol_nr = 0
for geo_el in panel_source_obj.tools[tool]['solid_geometry']:
trans_geo = translate_recursion(geo_el)
obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
# update progress
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
@ -428,15 +436,17 @@ class Panelize(AppTool):
' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
old_disp_number = disp_number
else:
# graceful abort requested by the user
if app_obj.abort_flag:
# graceful abort requested by the user
raise grace
# calculate the number of polygons
try:
# calculate the number of polygons
geo_len = len(panel_source_obj.solid_geometry)
except TypeError:
geo_len = 1
# panelization
pol_nr = 0
try:
for geo_el in panel_source_obj.solid_geometry:
@ -447,9 +457,9 @@ class Panelize(AppTool):
trans_geo = translate_recursion(geo_el)
obj_fin.solid_geometry.append(trans_geo)
# update progress
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(
' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
@ -460,14 +470,15 @@ class Panelize(AppTool):
obj_fin.solid_geometry.append(trans_geo)
# Will panelize a Gerber Object
else:
# graceful abort requested by the user
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# panelization solid_geometry
try:
for geo_el in panel_source_obj.solid_geometry:
# graceful abort requested by the user
if app_obj.abort_flag:
# graceful abort requested by the user
raise grace
trans_geo = translate_recursion(geo_el)
@ -477,15 +488,18 @@ class Panelize(AppTool):
obj_fin.solid_geometry.append(trans_geo)
for apid in panel_source_obj.apertures:
# graceful abort requested by the user
if app_obj.abort_flag:
# graceful abort requested by the user
raise grace
if 'geometry' in panel_source_obj.apertures[apid]:
# calculate the number of polygons
try:
# calculate the number of polygons
geo_len = len(panel_source_obj.apertures[apid]['geometry'])
except TypeError:
geo_len = 1
# panelization -> tools
pol_nr = 0
for el in panel_source_obj.apertures[apid]['geometry']:
if app_obj.abort_flag:
@ -496,20 +510,17 @@ class Panelize(AppTool):
if 'solid' in el:
geo_aper = translate_recursion(el['solid'])
new_el['solid'] = geo_aper
if 'clear' in el:
geo_aper = translate_recursion(el['clear'])
new_el['clear'] = geo_aper
if 'follow' in el:
geo_aper = translate_recursion(el['follow'])
new_el['follow'] = geo_aper
obj_fin.apertures[apid]['geometry'].append(deepcopy(new_el))
# update progress
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(
' %s: %d %d%%' % (_("Copy"), int(element), disp_number))
@ -522,17 +533,42 @@ class Panelize(AppTool):
# I'm going to do this only here as a fix for panelizing cutouts
# I'm going to separate linestrings out of the solid geometry from other
# possible type of elements and apply unary_union on them to fuse them
if to_optimize is True:
app_obj.inform.emit('%s' % _("Optimizing the overlapping paths."))
for tool in obj_fin.tools:
lines = []
other_geo = []
for geo in obj_fin.tools[tool]['solid_geometry']:
if isinstance(geo, LineString):
lines.append(geo)
elif isinstance(geo, MultiLineString):
for line in geo:
lines.append(line)
else:
other_geo.append(geo)
fused_lines = list(unary_union(lines))
if to_optimize is True:
for idx, line in enumerate(lines):
for idx_s in range(idx+1, len(lines)):
line_mod = lines[idx_s]
dist = line.distance(line_mod)
if dist < 1e-8:
print("Disjoint %d: %d -> %s" % (idx, idx_s, str(dist)))
print("Distance %f" % dist)
res = snap(line_mod, line, tolerance=1e-7)
if res and not res.is_empty:
lines[idx_s] = res
fused_lines = linemerge(lines)
fused_lines = [unary_union(fused_lines)]
obj_fin.tools[tool]['solid_geometry'] = fused_lines + other_geo
if to_optimize is True:
app_obj.inform.emit('%s' % _("Optimization complete."))
if panel_type == 'gerber':
app_obj.inform.emit('%s' % _("Generating panel ... Adding the Gerber code."))
obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
@ -766,6 +802,16 @@ class PanelizeUI:
form_layout.addRow(self.panel_type_label)
form_layout.addRow(self.panel_type_radio)
# Path optimization
self.optimization_cb = FCCheckBox('%s' % _("Path Optimization"))
self.optimization_cb.setToolTip(
_("Active only for Geometry panel type.\n"
"When checked the application will find\n"
"any two overlapping Line elements in the panel\n"
"and remove the overlapping parts, keeping only one of them.")
)
form_layout.addRow(self.optimization_cb)
# Constrains
self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
self.constrain_cb.setToolTip(
@ -839,6 +885,13 @@ class PanelizeUI:
# #################################### FINSIHED GUI ###########################
# #############################################################################
self.panel_type_radio.activated_custom.connect(self.on_panel_type)
def on_panel_type(self, val):
if val == 'geometry':
self.optimization_cb.setDisabled(False)
else:
self.optimization_cb.setDisabled(True)
def confirmation_message(self, accepted, minval, maxval):
if accepted is False:

View File

@ -506,6 +506,7 @@ class FlatCAMDefaults:
"tools_panelize_spacing_rows": 0.0,
"tools_panelize_columns": 1,
"tools_panelize_rows": 1,
"tools_panelize_optimization": True,
"tools_panelize_constrain": False,
"tools_panelize_constrainx": 200.0,
"tools_panelize_constrainy": 290.0,