- upgraded the FCButton widget (and made it used everywhere instead of the QPushButton) so it can have the color and font weight properties settable
329 lines
13 KiB
Python
329 lines
13 KiB
Python
|
|
from appTool import *
|
|
from camlib import Geometry
|
|
|
|
fcTranslate.apply_language('strings')
|
|
if '_' not in builtins.__dict__:
|
|
_ = gettext.gettext
|
|
|
|
|
|
class PaintOptionsTool(AppTool):
|
|
"""
|
|
Inputs to specify how to paint the selected polygons.
|
|
"""
|
|
|
|
def __init__(self, app, fcdraw):
|
|
AppTool.__init__(self, app)
|
|
|
|
self.app = app
|
|
self.fcdraw = fcdraw
|
|
self.decimals = self.app.decimals
|
|
|
|
self.ui = PaintEditorUI(layout=self.layout, paint_class=self)
|
|
|
|
self.connect_signals_at_init()
|
|
self.set_tool_ui()
|
|
|
|
def run(self):
|
|
self.app.defaults.report_usage("Geo Editor ToolPaint()")
|
|
AppTool.run(self)
|
|
|
|
# if the splitter us hidden, display it
|
|
if self.app.ui.splitter.sizes()[0] == 0:
|
|
self.app.ui.splitter.setSizes([1, 1])
|
|
|
|
# if the Tool Tab is hidden display it, else hide it but only if the objectName is the same
|
|
found_idx = None
|
|
for idx in range(self.app.ui.notebook.count()):
|
|
if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab":
|
|
found_idx = idx
|
|
break
|
|
# show the Tab
|
|
if not found_idx:
|
|
try:
|
|
self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin"))
|
|
except RuntimeError:
|
|
self.app.ui.plugin_tab = QtWidgets.QWidget()
|
|
self.app.ui.plugin_tab.setObjectName("plugin_tab")
|
|
self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab)
|
|
self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2)
|
|
|
|
self.app.ui.plugin_scroll_area = VerticalScrollArea()
|
|
self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area)
|
|
self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin"))
|
|
# focus on Tool Tab
|
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
|
|
|
|
# self.app.ui.notebook.callback_on_close = self.on_tab_close
|
|
|
|
self.app.ui.notebook.setTabText(2, _("Paint Tool"))
|
|
|
|
def connect_signals_at_init(self):
|
|
# Signals
|
|
self.ui.paint_button.clicked.connect(self.on_paint)
|
|
|
|
def on_tab_close(self):
|
|
self.fcdraw.select_tool("select")
|
|
self.app.ui.notebook.callback_on_close = lambda: None
|
|
|
|
def set_tool_ui(self):
|
|
# Init appGUI
|
|
if self.app.options["tools_paint_tooldia"]:
|
|
self.ui.painttooldia_entry.set_value(self.app.options["tools_paint_tooldia"])
|
|
else:
|
|
self.ui.painttooldia_entry.set_value(0.0)
|
|
|
|
if self.app.options["tools_paint_overlap"]:
|
|
self.ui.paintoverlap_entry.set_value(self.app.options["tools_paint_overlap"])
|
|
else:
|
|
self.ui.paintoverlap_entry.set_value(0.0)
|
|
|
|
if self.app.options["tools_paint_offset"]:
|
|
self.ui.paintmargin_entry.set_value(self.app.options["tools_paint_offset"])
|
|
else:
|
|
self.ui.paintmargin_entry.set_value(0.0)
|
|
|
|
if self.app.options["tools_paint_method"]:
|
|
self.ui.paintmethod_combo.set_value(self.app.options["tools_paint_method"])
|
|
else:
|
|
self.ui.paintmethod_combo.set_value(_("Seed"))
|
|
|
|
if self.app.options["tools_paint_connect"]:
|
|
self.ui.pathconnect_cb.set_value(self.app.options["tools_paint_connect"])
|
|
else:
|
|
self.ui.pathconnect_cb.set_value(False)
|
|
|
|
if self.app.options["tools_paint_contour"]:
|
|
self.ui.paintcontour_cb.set_value(self.app.options["tools_paint_contour"])
|
|
else:
|
|
self.ui.paintcontour_cb.set_value(False)
|
|
|
|
def on_paint(self):
|
|
if not self.fcdraw.selected:
|
|
self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
|
|
return
|
|
|
|
tooldia = self.ui.painttooldia_entry.get_value()
|
|
overlap = self.ui.paintoverlap_entry.get_value() / 100.0
|
|
margin = self.ui.paintmargin_entry.get_value()
|
|
|
|
method = self.ui.paintmethod_combo.get_value()
|
|
contour = self.ui.paintcontour_cb.get_value()
|
|
connect = self.ui.pathconnect_cb.get_value()
|
|
|
|
self.paint(tooldia, overlap, margin, connect=connect, contour=contour, method=method)
|
|
self.fcdraw.select_tool("select")
|
|
# self.app.ui.notebook.setTabText(2, _("Tools"))
|
|
# self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
|
#
|
|
# self.app.ui.splitter.setSizes([0, 1])
|
|
|
|
def paint(self, tooldia, overlap, margin, connect, contour, method):
|
|
def work_task(geo_editor):
|
|
with geo_editor.app.proc_container.new(_("Working...")):
|
|
if overlap >= 100:
|
|
geo_editor.app.inform.emit('[ERROR_NOTCL] %s' %
|
|
_("Could not do Paint. Overlap value has to be less than 100%%."))
|
|
return
|
|
|
|
geo_editor.paint_tooldia = tooldia
|
|
selected = geo_editor.get_selected()
|
|
|
|
if len(selected) == 0:
|
|
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
|
|
return
|
|
|
|
for param in [tooldia, overlap, margin]:
|
|
if not isinstance(param, float):
|
|
param_name = [k for k, v in locals().items() if v is param][0]
|
|
geo_editor.app.inform.emit('[WARNING] %s: %s' % (_("Invalid value for"), str(param)))
|
|
|
|
results = []
|
|
|
|
def recurse(geometry, reset=True):
|
|
"""
|
|
Creates a list of non-iterable linear geometry objects.
|
|
Results are placed in self.flat_geometry
|
|
|
|
:param geometry: Shapely type or list or list of list of such.
|
|
:param reset: Clears the contents of self.flat_geometry.
|
|
"""
|
|
|
|
if geometry is None:
|
|
return
|
|
|
|
if reset:
|
|
self.flat_geo = []
|
|
|
|
# If iterable, expand recursively.
|
|
try:
|
|
for geo_el in geometry:
|
|
if geo_el is not None and not geo_el.is_emoty:
|
|
recurse(geometry=geo_el, reset=False)
|
|
|
|
# Not iterable, do the actual indexing and add.
|
|
except TypeError:
|
|
self.flat_geo.append(geometry)
|
|
|
|
return self.flat_geo
|
|
|
|
for geo in selected:
|
|
|
|
local_results = []
|
|
for geo_obj in recurse(geo.geo):
|
|
try:
|
|
if type(geo_obj) == Polygon:
|
|
poly_buf = geo_obj.buffer(-margin)
|
|
else:
|
|
poly_buf = Polygon(geo_obj).buffer(-margin)
|
|
|
|
if method == _("Seed"):
|
|
cp = Geometry.clear_polygon2(
|
|
geo_editor, polygon_to_clear=poly_buf, tooldia=tooldia,
|
|
steps_per_circle=geo_editor.app.options["geometry_circle_steps"],
|
|
overlap=overlap, contour=contour, connect=connect)
|
|
elif method == _("Lines"):
|
|
cp = Geometry.clear_polygon3(
|
|
geo_editor, polygon=poly_buf, tooldia=tooldia,
|
|
steps_per_circle=geo_editor.app.options["geometry_circle_steps"],
|
|
overlap=overlap, contour=contour, connect=connect)
|
|
else:
|
|
cp = Geometry.clear_polygon(
|
|
geo_editor, polygon=poly_buf, tooldia=tooldia,
|
|
steps_per_circle=geo_editor.app.options["geometry_circle_steps"],
|
|
overlap=overlap, contour=contour, connect=connect)
|
|
|
|
if cp is not None:
|
|
local_results += list(cp.get_objects())
|
|
except Exception as e:
|
|
geo_editor.app.log.error("Could not Paint the polygons. %s" % str(e))
|
|
geo_editor.app.inform.emit(
|
|
'[ERROR] %s\n%s' % (_("Could not do Paint. Try a different combination of parameters. "
|
|
"Or a different method of Paint"), str(e))
|
|
)
|
|
return
|
|
|
|
# add the result to the results list
|
|
results.append(unary_union(local_results))
|
|
|
|
# This is a dirty patch:
|
|
for r in results:
|
|
geo_editor.add_shape(r)
|
|
geo_editor.plot_all()
|
|
geo_editor.build_ui_sig.emit()
|
|
geo_editor.app.inform.emit('[success] %s' % _("Done."))
|
|
|
|
self.app.worker_task.emit({'fcn': work_task, 'params': [self.fcdraw]})
|
|
|
|
|
|
class PaintEditorUI:
|
|
pluginName = _("Paint")
|
|
|
|
def __init__(self, layout, paint_class):
|
|
self.paint_class = paint_class
|
|
self.decimals = self.paint_class.app.decimals
|
|
self.layout = layout
|
|
|
|
# Title
|
|
title_label = FCLabel("%s" % self.pluginName, size=16, bold=True)
|
|
self.layout.addWidget(title_label)
|
|
|
|
grid = GLay(v_spacing=5, h_spacing=3)
|
|
self.layout.addLayout(grid)
|
|
|
|
# Tool dia
|
|
ptdlabel = FCLabel('%s:' % _('Tool Dia'))
|
|
ptdlabel.setToolTip(
|
|
_("Diameter of the tool to be used in the operation.")
|
|
)
|
|
grid.addWidget(ptdlabel, 0, 0)
|
|
|
|
self.painttooldia_entry = FCDoubleSpinner()
|
|
self.painttooldia_entry.set_range(-10000.0000, 10000.0000)
|
|
self.painttooldia_entry.set_precision(self.decimals)
|
|
grid.addWidget(self.painttooldia_entry, 0, 1)
|
|
|
|
# Overlap
|
|
ovlabel = FCLabel('%s:' % _('Overlap'))
|
|
ovlabel.setToolTip(
|
|
_("How much (percentage) of the tool width to overlap each tool pass.\n"
|
|
"Adjust the value starting with lower values\n"
|
|
"and increasing it if areas that should be processed are still \n"
|
|
"not processed.\n"
|
|
"Lower values = faster processing, faster execution on CNC.\n"
|
|
"Higher values = slow processing and slow execution on CNC\n"
|
|
"due of too many paths.")
|
|
)
|
|
self.paintoverlap_entry = FCDoubleSpinner(suffix='%')
|
|
self.paintoverlap_entry.set_range(0.0000, 99.9999)
|
|
self.paintoverlap_entry.set_precision(self.decimals)
|
|
self.paintoverlap_entry.setWrapping(True)
|
|
self.paintoverlap_entry.setSingleStep(1)
|
|
|
|
grid.addWidget(ovlabel, 1, 0)
|
|
grid.addWidget(self.paintoverlap_entry, 1, 1)
|
|
|
|
# Margin
|
|
marginlabel = FCLabel('%s:' % _('Margin'))
|
|
marginlabel.setToolTip(
|
|
_("Distance by which to avoid\n"
|
|
"the edges of the polygon to\n"
|
|
"be painted.")
|
|
)
|
|
self.paintmargin_entry = FCDoubleSpinner()
|
|
self.paintmargin_entry.set_range(-10000.0000, 10000.0000)
|
|
self.paintmargin_entry.set_precision(self.decimals)
|
|
|
|
grid.addWidget(marginlabel, 2, 0)
|
|
grid.addWidget(self.paintmargin_entry, 2, 1)
|
|
|
|
# Method
|
|
methodlabel = FCLabel('%s:' % _('Method'))
|
|
methodlabel.setToolTip(
|
|
_("Algorithm to paint the polygons:\n"
|
|
"- Standard: Fixed step inwards.\n"
|
|
"- Seed-based: Outwards from seed.\n"
|
|
"- Line-based: Parallel lines.")
|
|
)
|
|
# self.paintmethod_combo = RadioSet([
|
|
# {"label": _("Standard"), "value": "standard"},
|
|
# {"label": _("Seed-based"), "value": "seed"},
|
|
# {"label": _("Straight lines"), "value": "lines"}
|
|
# ], orientation='vertical', compact=True)
|
|
self.paintmethod_combo = FCComboBox()
|
|
self.paintmethod_combo.addItems(
|
|
[_("Standard"), _("Seed"), _("Lines")]
|
|
)
|
|
|
|
grid.addWidget(methodlabel, 3, 0)
|
|
grid.addWidget(self.paintmethod_combo, 3, 1)
|
|
|
|
# Connect lines
|
|
pathconnectlabel = FCLabel('%s:' % _("Connect"))
|
|
pathconnectlabel.setToolTip(
|
|
_("Draw lines between resulting\n"
|
|
"segments to minimize tool lifts.")
|
|
)
|
|
self.pathconnect_cb = FCCheckBox()
|
|
|
|
grid.addWidget(pathconnectlabel, 4, 0)
|
|
grid.addWidget(self.pathconnect_cb, 4, 1)
|
|
|
|
contourlabel = FCLabel('%s:' % _("Contour"))
|
|
contourlabel.setToolTip(
|
|
_("Cut around the perimeter of the polygon\n"
|
|
"to trim rough edges.")
|
|
)
|
|
self.paintcontour_cb = FCCheckBox()
|
|
|
|
grid.addWidget(contourlabel, 5, 0)
|
|
grid.addWidget(self.paintcontour_cb, 5, 1)
|
|
|
|
# Buttons
|
|
hlay = QtWidgets.QHBoxLayout()
|
|
self.layout.addLayout(hlay)
|
|
self.paint_button = FCButton(_("Paint"))
|
|
hlay.addWidget(self.paint_button)
|
|
|
|
self.layout.addStretch(1)
|