- 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:
parent
7be4d98172
commit
8dc4eecbf4
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue