- Follow Tool: added more parameters: simplification and union
- Follow Tool: the resulting LineString geometries are now merged together to minimize the number of lines
This commit is contained in:
@@ -7,13 +7,14 @@
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore, QtGui
|
||||
from appTool import AppTool
|
||||
from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet
|
||||
from appGUI.GUIElements import (VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet,
|
||||
FCDoubleSpinner, FCCheckBox, OptionalInputSection)
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
import numpy as np
|
||||
|
||||
from shapely import Polygon
|
||||
from shapely import Polygon, line_merge, MultiLineString, Point, simplify
|
||||
from shapely.ops import unary_union
|
||||
|
||||
import gettext
|
||||
@@ -129,7 +130,7 @@ class ToolFollow(AppTool, Gerber):
|
||||
|
||||
def connect_signals_at_init(self):
|
||||
self.ui.level.toggled.connect(self.on_level_changed)
|
||||
self.ui.selectmethod_radio.activated_custom.connect(self.ui.on_selection)
|
||||
self.ui.select_method_radio.activated_custom.connect(self.ui.on_selection)
|
||||
self.ui.generate_geometry_button.clicked.connect(self.on_generate_geometry_click)
|
||||
|
||||
def set_tool_ui(self):
|
||||
@@ -140,7 +141,7 @@ class ToolFollow(AppTool, Gerber):
|
||||
self.pluginName = self.ui.pluginName
|
||||
self.connect_signals_at_init()
|
||||
|
||||
self.ui.selectmethod_radio.set_value('all') # _("All")
|
||||
self.ui.select_method_radio.set_value('all') # _("All")
|
||||
self.ui.area_shape_radio.set_value('square')
|
||||
|
||||
self.sel_rect[:] = []
|
||||
@@ -154,10 +155,29 @@ class ToolFollow(AppTool, Gerber):
|
||||
obj_name = obj.obj_options['name']
|
||||
self.ui.object_combo.set_value(obj_name)
|
||||
|
||||
# Set UI
|
||||
self.ui.simplify_cb.set_value(self.app.options["tools_follow_simplification"])
|
||||
self.ui.tol_entry.set_value(self.app.options["tools_follow_tolerance"])
|
||||
self.ui.union_cb.set_value(self.app.options["tools_follow_union"])
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
app_mode = self.app.options["global_app_level"]
|
||||
self.change_level(app_mode)
|
||||
|
||||
# SIGNALS
|
||||
self.ui.simplify_cb.stateChanged.connect(self.on_simplify_changed)
|
||||
self.ui.tol_entry.valueChanged.connect(self.on_tolerance_changed)
|
||||
self.ui.union_cb.stateChanged.connect(self.on_union_changed)
|
||||
|
||||
def on_simplify_changed(self, checked):
|
||||
self.app.options["tools_follow_simplification"] = checked
|
||||
|
||||
def on_tolerance_changed(self, value):
|
||||
self.app.options["tools_follow_tolerance"] = value
|
||||
|
||||
def on_union_changed(self, checked):
|
||||
self.app.options["tools_follow_union"] = checked
|
||||
|
||||
def change_level(self, level):
|
||||
"""
|
||||
|
||||
@@ -217,7 +237,7 @@ class ToolFollow(AppTool, Gerber):
|
||||
formatted_name = obj_name
|
||||
outname = '%s_follow' % formatted_name
|
||||
|
||||
select_method = self.ui.selectmethod_radio.get_value()
|
||||
select_method = self.ui.select_method_radio.get_value()
|
||||
if select_method == 'all': # _("All")
|
||||
self.follow_all(obj, outname)
|
||||
else:
|
||||
@@ -259,7 +279,7 @@ class ToolFollow(AppTool, Gerber):
|
||||
return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
|
||||
|
||||
if obj is None:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
formatted_name = obj_name.rpartition('.')[0]
|
||||
@@ -283,6 +303,10 @@ class ToolFollow(AppTool, Gerber):
|
||||
:return: None
|
||||
"""
|
||||
|
||||
should_union = self.ui.union_cb.get_value()
|
||||
should_simplify = self.ui.simplify_cb.get_value()
|
||||
simplify_tol = self.ui.tol_entry.get_value()
|
||||
|
||||
def follow_init(new_obj, app_obj):
|
||||
if type(app_obj.defaults["tools_mill_tooldia"]) == float:
|
||||
tools_list = [app_obj.defaults["tools_mill_tooldia"]]
|
||||
@@ -306,12 +330,28 @@ class ToolFollow(AppTool, Gerber):
|
||||
if opt_key.find('tools_') == 0:
|
||||
new_data[opt_key] = app_obj.options[opt_key]
|
||||
|
||||
followed_obj.follow_geometry = flatten_shapely_geometry(followed_obj.follow_geometry)
|
||||
flattened_follow_geometry = flatten_shapely_geometry(followed_obj.follow_geometry)
|
||||
cleaned_flat_follow_geometry = [
|
||||
f for f in flattened_follow_geometry if not isinstance(f, Point) and not f.is_empty
|
||||
]
|
||||
|
||||
merged_geo = line_merge(MultiLineString(cleaned_flat_follow_geometry))
|
||||
if merged_geo and not merged_geo.is_empty:
|
||||
flattened_follow_geometry = flatten_shapely_geometry(merged_geo)
|
||||
|
||||
followed_obj.follow_geometry = flattened_follow_geometry
|
||||
|
||||
# Filter out empty geometries
|
||||
follow_geo = [
|
||||
g for g in followed_obj.follow_geometry
|
||||
if g and not g.is_empty and g.is_valid and g.geom_type != 'Point'
|
||||
]
|
||||
|
||||
if should_simplify and simplify_tol > 0.0:
|
||||
follow_geo = [simplify(f, tolerance=simplify_tol) for f in follow_geo]
|
||||
if should_union:
|
||||
follow_geo = unary_union(follow_geo)
|
||||
|
||||
if not follow_geo:
|
||||
self.app.log.warning("ToolFollow.follow_geo() -> Empty Follow Geometry")
|
||||
return 'fail'
|
||||
@@ -345,6 +385,9 @@ class ToolFollow(AppTool, Gerber):
|
||||
:type outname: str
|
||||
:return: None
|
||||
"""
|
||||
should_union = self.ui.union_cb.get_value()
|
||||
should_simplify = self.ui.simplify_cb.get_value()
|
||||
simplify_tol = self.ui.tol_entry.get_value()
|
||||
|
||||
def follow_init(new_obj, app_obj):
|
||||
new_obj.multigeo = True
|
||||
@@ -381,8 +424,21 @@ class ToolFollow(AppTool, Gerber):
|
||||
self.points = []
|
||||
|
||||
area_follow = flatten_shapely_geometry(area_follow)
|
||||
cleaned_flat_follow_area = [
|
||||
f for f in area_follow if not isinstance(f, Point) and not f.is_empty
|
||||
]
|
||||
|
||||
merged_geo = line_merge(MultiLineString(cleaned_flat_follow_area))
|
||||
if merged_geo and not merged_geo.is_empty:
|
||||
area_follow = flatten_shapely_geometry(merged_geo)
|
||||
|
||||
cleaned_area_follow = [g for g in area_follow if not g.is_empty and g.is_valid and g.geom_type != 'Point']
|
||||
|
||||
if should_simplify and simplify_tol > 0.0:
|
||||
cleaned_area_follow = [simplify(f, tolerance=simplify_tol) for f in cleaned_area_follow]
|
||||
if should_union:
|
||||
cleaned_area_follow = unary_union(cleaned_area_follow)
|
||||
|
||||
new_obj.multigeo = True
|
||||
new_obj.solid_geometry = deepcopy(cleaned_area_follow)
|
||||
new_obj.tools = {
|
||||
@@ -730,6 +786,34 @@ class FollowUI:
|
||||
grid0 = GLay(v_spacing=5, h_spacing=3)
|
||||
self.gp_frame.setLayout(grid0)
|
||||
|
||||
# Simplification
|
||||
self.simplify_cb = FCCheckBox(_("Simplify"))
|
||||
self.simplify_cb.setToolTip(
|
||||
_("If checked, the toolpaths will be simplified with the given tolerance.")
|
||||
)
|
||||
self.tol_label = FCLabel('%s:' % _('Tolerance'))
|
||||
self.tol_label.setToolTip(
|
||||
_("The tolerance of the simplification.")
|
||||
)
|
||||
self.tol_entry = FCDoubleSpinner()
|
||||
self.tol_entry.set_range(0.0, 10000.0)
|
||||
self.tol_entry.set_precision(self.decimals)
|
||||
self.tol_entry.setSingleStep(0.01)
|
||||
|
||||
self.simp_optional = OptionalInputSection(self.simplify_cb, [self.tol_label, self.tol_entry])
|
||||
|
||||
grid0.addWidget(self.simplify_cb, 0, 0, 1, 2)
|
||||
grid0.addWidget(self.tol_label, 2, 0)
|
||||
grid0.addWidget(self.tol_entry, 2, 1)
|
||||
|
||||
# UNION
|
||||
self.union_cb = FCCheckBox(_("Union"))
|
||||
self.union_cb.setToolTip(
|
||||
_("If checked, the toolpaths will be joined into a Union.")
|
||||
)
|
||||
|
||||
grid0.addWidget(self.union_cb, 4, 0, 1, 2)
|
||||
|
||||
# Polygon selection
|
||||
self.select_label = FCLabel('%s:' % _('Selection'))
|
||||
self.select_label.setToolTip(
|
||||
@@ -738,11 +822,11 @@ class FollowUI:
|
||||
"- 'Area Selection' - left mouse click to start selection of the area to be processed.")
|
||||
)
|
||||
|
||||
self.selectmethod_radio = RadioSet([{'label': _("All"), 'value': 'all'},
|
||||
{'label': _("Area Selection"), 'value': 'area'}])
|
||||
self.select_method_radio = RadioSet([{'label': _("All"), 'value': 'all'},
|
||||
{'label': _("Area Selection"), 'value': 'area'}])
|
||||
|
||||
grid0.addWidget(self.select_label, 0, 0)
|
||||
grid0.addWidget(self.selectmethod_radio, 0, 1)
|
||||
grid0.addWidget(self.select_label, 6, 0)
|
||||
grid0.addWidget(self.select_method_radio, 6, 1)
|
||||
|
||||
# Area Selection shape
|
||||
self.area_shape_label = FCLabel('%s:' % _("Shape"))
|
||||
@@ -753,8 +837,8 @@ class FollowUI:
|
||||
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
|
||||
{'label': _("Polygon"), 'value': 'polygon'}])
|
||||
|
||||
grid0.addWidget(self.area_shape_label, 2, 0)
|
||||
grid0.addWidget(self.area_shape_radio, 2, 1)
|
||||
grid0.addWidget(self.area_shape_label, 8, 0)
|
||||
grid0.addWidget(self.area_shape_radio, 8, 1)
|
||||
|
||||
self.area_shape_label.hide()
|
||||
self.area_shape_radio.hide()
|
||||
|
||||
Reference in New Issue
Block a user