- in Geometry Editor - update (some reformatting and adding shape data)

This commit is contained in:
Marius Stanciu
2022-04-13 15:25:34 +03:00
committed by Marius
parent fcdb318c23
commit f60919f4c7
6 changed files with 2041 additions and 2031 deletions

View File

@@ -0,0 +1,397 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class BufferSelectionTool(AppTool):
"""
Simple input for buffer distance.
"""
pluginName = _("Buffer Selection")
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
# Title
title_label = FCLabel("%s" % ('Editor ' + self.pluginName))
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
# this way I can hide/show the frame
self.buffer_tool_frame = QtWidgets.QFrame()
self.buffer_tool_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.buffer_tool_frame)
self.buffer_tools_box = QtWidgets.QVBoxLayout()
self.buffer_tools_box.setContentsMargins(0, 0, 0, 0)
self.buffer_tool_frame.setLayout(self.buffer_tools_box)
# Grid Layout
grid_buffer = GLay(v_spacing=5, h_spacing=3)
self.buffer_tools_box.addLayout(grid_buffer)
# Buffer distance
self.buffer_distance_entry = FCDoubleSpinner()
self.buffer_distance_entry.set_precision(self.decimals)
self.buffer_distance_entry.set_range(0.0000, 9910000.0000)
grid_buffer.addWidget(FCLabel('%s:' % _("Buffer distance")), 0, 0)
grid_buffer.addWidget(self.buffer_distance_entry, 0, 1)
self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner"))
self.buffer_corner_lbl.setToolTip(
_("There are 3 types of corners:\n"
" - 'Round': the corner is rounded for exterior buffer.\n"
" - 'Square': the corner is met in a sharp angle for exterior buffer.\n"
" - 'Beveled': the corner is a line that directly connects the features meeting in the corner")
)
self.buffer_corner_cb = FCComboBox()
self.buffer_corner_cb.addItem(_("Round"))
self.buffer_corner_cb.addItem(_("Square"))
self.buffer_corner_cb.addItem(_("Beveled"))
grid_buffer.addWidget(self.buffer_corner_lbl, 2, 0)
grid_buffer.addWidget(self.buffer_corner_cb, 2, 1)
# Buttons
hlay = QtWidgets.QHBoxLayout()
grid_buffer.addLayout(hlay, 4, 0, 1, 2)
self.buffer_int_button = FCButton(_("Buffer Interior"))
hlay.addWidget(self.buffer_int_button)
self.buffer_ext_button = FCButton(_("Buffer Exterior"))
hlay.addWidget(self.buffer_ext_button)
hlay1 = QtWidgets.QHBoxLayout()
grid_buffer.addLayout(hlay1, 6, 0, 1, 2)
self.buffer_button = FCButton(_("Full Buffer"))
hlay1.addWidget(self.buffer_button)
self.layout.addStretch(1)
# Signals
self.buffer_button.clicked.connect(self.on_buffer)
self.buffer_int_button.clicked.connect(self.on_buffer_int)
self.buffer_ext_button.clicked.connect(self.on_buffer_ext)
# Init appGUI
self.buffer_distance_entry.set_value(0.01)
def run(self):
self.app.defaults.report_usage("Geo Editor ToolBuffer()")
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, _("Buffer Tool"))
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_buffer(self):
try:
buffer_distance = float(self.buffer_distance_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
buffer_distance = float(self.buffer_distance_entry.get_value().replace(',', '.'))
self.buffer_distance_entry.set_value(buffer_distance)
except ValueError:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry."))
return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.buffer(buffer_distance, join_style)
def on_buffer_int(self):
try:
buffer_distance = float(self.buffer_distance_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
buffer_distance = float(self.buffer_distance_entry.get_value().replace(',', '.'))
self.buffer_distance_entry.set_value(buffer_distance)
except ValueError:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry."))
return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.buffer_int(buffer_distance, join_style)
def on_buffer_ext(self):
try:
buffer_distance = float(self.buffer_distance_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
buffer_distance = float(self.buffer_distance_entry.get_value().replace(',', '.'))
self.buffer_distance_entry.set_value(buffer_distance)
except ValueError:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry."))
return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.buffer_ext(buffer_distance, join_style)
def buffer(self, buf_distance, join_style):
def work_task(geo_editor):
with geo_editor.app.proc_container.new(_("Working...")):
selected = geo_editor.get_selected()
if buf_distance < 0:
msg = '[ERROR_NOTCL] %s' % _("Negative buffer value is not accepted. "
"Use Buffer interior to generate an 'inside' shape")
geo_editor.app.inform.emit(msg)
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
if len(selected) == 0:
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
return 'fail'
if not isinstance(buf_distance, float):
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Invalid distance."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
results = []
for t in selected:
if not t.geo.is_empty and t.geo.is_valid:
if t.geo.geom_type == 'Polygon':
results.append(t.geo.exterior.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style)
)
elif t.geo.geom_type == 'MultiLineString':
for line in t.geo:
if line.is_ring:
b_geo = Polygon(line)
results.append(b_geo.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
results.append(b_geo.buffer(
-buf_distance + 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
elif t.geo.geom_type in ['LineString', 'LinearRing']:
if t.geo.is_ring:
b_geo = Polygon(t.geo)
results.append(b_geo.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
results.append(b_geo.buffer(
-buf_distance + 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
if not results:
geo_editor.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed, the result is empty."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
for sha in results:
geo_editor.add_shape(sha)
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]})
def buffer_int(self, buf_distance, join_style):
def work_task(geo_editor):
with geo_editor.app.proc_container.new(_("Working...")):
selected = geo_editor.get_selected()
if buf_distance < 0:
geo_editor.app.inform.emit('[ERROR_NOTCL] %s' % _("Negative buffer value is not accepted."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
if len(selected) == 0:
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
return 'fail'
if not isinstance(buf_distance, float):
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Invalid distance."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
results = []
for t in selected:
if not t.geo.is_empty and t.geo.is_valid:
if t.geo.geom_type == 'Polygon':
results.append(t.geo.exterior.buffer(
-buf_distance + 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
elif t.geo.geom_type == 'MultiLineString':
for line in t.geo:
if line.is_ring:
b_geo = Polygon(line)
results.append(b_geo.buffer(
-buf_distance + 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
elif t.geo.geom_type in ['LineString', 'LinearRing']:
if t.geo.is_ring:
b_geo = Polygon(t.geo)
results.append(b_geo.buffer(
-buf_distance + 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
if not results:
geo_editor.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed, the result is empty."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
for sha in results:
geo_editor.add_shape(sha)
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]})
def buffer_ext(self, buf_distance, join_style):
def work_task(geo_editor):
with geo_editor.app.proc_container.new(_("Working...")):
selected = geo_editor.get_selected()
if buf_distance < 0:
msg = '[ERROR_NOTCL] %s' % _("Negative buffer value is not accepted. "
"Use Buffer interior to generate an 'inside' shape")
geo_editor.app.inform.emit(msg)
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return
if len(selected) == 0:
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
return
if not isinstance(buf_distance, float):
geo_editor.app.inform.emit('[WARNING_NOTCL] %s' % _("Invalid distance."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return
results = []
for t in selected:
if not t.geo.is_empty and t.geo.is_valid:
if t.geo.geom_type == 'Polygon':
results.append(t.geo.exterior.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
elif t.geo.geom_type == 'MultiLineString':
for line in t.geo:
if line.is_ring:
b_geo = Polygon(line)
results.append(b_geo.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
elif t.geo.geom_type in ['LineString', 'LinearRing']:
if t.geo.is_ring:
b_geo = Polygon(t.geo)
results.append(b_geo.buffer(
buf_distance - 1e-10,
resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4),
join_style=join_style).exterior
)
if not results:
geo_editor.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed, the result is empty."))
# deselect everything
geo_editor.selected = []
geo_editor.plot_all()
return 'fail'
for sha in results:
geo_editor.add_shape(sha)
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]})
def hide_tool(self):
self.buffer_tool_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)

View File

@@ -0,0 +1,324 @@
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.
"""
pluginName = _("Paint Tool")
def __init__(self, app, fcdraw):
AppTool.__init__(self, app)
self.app = app
self.fcdraw = fcdraw
self.decimals = self.app.decimals
# Title
title_label = FCLabel("%s" % self.pluginName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
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()
# Signals
self.paint_button.clicked.connect(self.on_paint)
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 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.painttooldia_entry.set_value(self.app.options["tools_paint_tooldia"])
else:
self.painttooldia_entry.set_value(0.0)
if self.app.options["tools_paint_overlap"]:
self.paintoverlap_entry.set_value(self.app.options["tools_paint_overlap"])
else:
self.paintoverlap_entry.set_value(0.0)
if self.app.options["tools_paint_offset"]:
self.paintmargin_entry.set_value(self.app.options["tools_paint_offset"])
else:
self.paintmargin_entry.set_value(0.0)
if self.app.options["tools_paint_method"]:
self.paintmethod_combo.set_value(self.app.options["tools_paint_method"])
else:
self.paintmethod_combo.set_value(_("Seed"))
if self.app.options["tools_paint_connect"]:
self.pathconnect_cb.set_value(self.app.options["tools_paint_connect"])
else:
self.pathconnect_cb.set_value(False)
if self.app.options["tools_paint_contour"]:
self.paintcontour_cb.set_value(self.app.options["tools_paint_contour"])
else:
self.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.painttooldia_entry.get_value()
overlap = self.paintoverlap_entry.get_value() / 100.0
margin = self.paintmargin_entry.get_value()
method = self.paintmethod_combo.get_value()
contour = self.paintcontour_cb.get_value()
connect = self.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:
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]})

View File

@@ -0,0 +1,256 @@
from appTool import *
from appParsers.ParseFont import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class TextInputTool(AppTool):
"""
Simple input for buffer distance.
"""
pluginName = _("Text Input Tool")
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.app = app
self.draw_app = draw_app
self.text_path = []
self.decimals = self.app.decimals
self.f_parse = ParseFont(self.app)
self.f_parse.get_fonts_by_types()
# this way I can hide/show the frame
self.text_tool_frame = QtWidgets.QFrame()
self.text_tool_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.text_tool_frame)
self.text_tools_box = QtWidgets.QVBoxLayout()
self.text_tools_box.setContentsMargins(0, 0, 0, 0)
self.text_tool_frame.setLayout(self.text_tools_box)
# Title
title_label = FCLabel("%s" % self.pluginName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.text_tools_box.addWidget(title_label)
# Grid Layout
self.grid_text = GLay(v_spacing=5, h_spacing=3)
self.text_tools_box.addLayout(self.grid_text)
# Font type
if sys.platform == "win32":
f_current = QtGui.QFont("Arial")
elif sys.platform == "linux":
f_current = QtGui.QFont("FreeMono")
else:
f_current = QtGui.QFont("Helvetica Neue")
self.font_name = f_current.family()
self.font_type_cb = QtWidgets.QFontComboBox(self)
self.font_type_cb.setCurrentFont(f_current)
self.grid_text.addWidget(FCLabel('%s:' % _("Font")), 0, 0)
self.grid_text.addWidget(self.font_type_cb, 0, 1)
# Flag variables to show if font is bold, italic, both or none (regular)
self.font_bold = False
self.font_italic = False
# # Create dictionaries with the filenames of the fonts
# # Key: Fontname
# # Value: Font File Name.ttf
#
# # regular fonts
# self.ff_names_regular ={}
# # bold fonts
# self.ff_names_bold = {}
# # italic fonts
# self.ff_names_italic = {}
# # bold and italic fonts
# self.ff_names_bi = {}
#
# if sys.platform == 'win32':
# from winreg import ConnectRegistry, OpenKey, EnumValue, HKEY_LOCAL_MACHINE
# registry = ConnectRegistry(None, HKEY_LOCAL_MACHINE)
# font_key = OpenKey(registry, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts")
# try:
# i = 0
# while 1:
# name_font, value, type = EnumValue(font_key, i)
# k = name_font.replace(" (TrueType)", '')
# if 'Bold' in k and 'Italic' in k:
# k = k.replace(" Bold Italic", '')
# self.ff_names_bi.update({k: value})
# elif 'Bold' in k:
# k = k.replace(" Bold", '')
# self.ff_names_bold.update({k: value})
# elif 'Italic' in k:
# k = k.replace(" Italic", '')
# self.ff_names_italic.update({k: value})
# else:
# self.ff_names_regular.update({k: value})
# i += 1
# except WindowsError:
# pass
# Font size
hlay = QtWidgets.QHBoxLayout()
self.font_size_cb = FCComboBox(policy=False)
self.font_size_cb.setEditable(True)
self.font_size_cb.setMinimumContentsLength(3)
self.font_size_cb.setMaximumWidth(70)
font_sizes = ['6', '7', '8', '9', '10', '11', '12', '13', '14',
'15', '16', '18', '20', '22', '24', '26', '28',
'32', '36', '40', '44', '48', '54', '60', '66',
'72', '80', '88', '96']
self.font_size_cb.addItems(font_sizes)
self.font_size_cb.setCurrentIndex(4)
hlay.addWidget(self.font_size_cb)
hlay.addStretch()
self.font_bold_tb = QtWidgets.QToolButton()
self.font_bold_tb.setCheckable(True)
self.font_bold_tb.setIcon(QtGui.QIcon(self.app.resource_location + '/bold32.png'))
hlay.addWidget(self.font_bold_tb)
self.font_italic_tb = QtWidgets.QToolButton()
self.font_italic_tb.setCheckable(True)
self.font_italic_tb.setIcon(QtGui.QIcon(self.app.resource_location + '/italic32.png'))
hlay.addWidget(self.font_italic_tb)
self.grid_text.addWidget(FCLabel('%s:' % _("Size")), 2, 0)
self.grid_text.addLayout(hlay, 2, 1)
# Text input
self.grid_text.addWidget(FCLabel('%s:' % _("Text")), 4, 0, 1, 2)
self.text_input_entry = FCTextAreaRich()
self.text_input_entry.setTabStopDistance(12)
self.text_input_entry.setMinimumHeight(200)
# self.text_input_entry.setMaximumHeight(150)
self.text_input_entry.setCurrentFont(f_current)
self.text_input_entry.setFontPointSize(10)
self.grid_text.addWidget(self.text_input_entry, 6, 0, 1, 2)
# Buttons
self.apply_button = FCButton(_("Apply"))
self.grid_text.addWidget(self.apply_button, 8, 0, 1, 2)
# self.layout.addStretch()
# Signals
self.apply_button.clicked.connect(self.on_apply_button)
self.font_type_cb.currentFontChanged.connect(self.font_family)
self.font_size_cb.activated.connect(self.font_size)
self.font_bold_tb.clicked.connect(self.on_bold_button)
self.font_italic_tb.clicked.connect(self.on_italic_button)
def run(self):
self.app.defaults.report_usage("Geo Editor TextInputTool()")
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, _("Text Tool"))
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_apply_button(self):
font_to_geo_type = ""
if self.font_bold is True:
font_to_geo_type = 'bold'
elif self.font_italic is True:
font_to_geo_type = 'italic'
elif self.font_bold is True and self.font_italic is True:
font_to_geo_type = 'bi'
elif self.font_bold is False and self.font_italic is False:
font_to_geo_type = 'regular'
string_to_geo = self.text_input_entry.get_value()
font_to_geo_size = self.font_size_cb.get_value()
self.text_path = self.f_parse.font_to_geometry(char_string=string_to_geo, font_name=self.font_name,
font_size=font_to_geo_size,
font_type=font_to_geo_type,
units=self.app.app_units.upper())
def font_family(self, font):
self.text_input_entry.selectAll()
font.setPointSize(float(self.font_size_cb.get_value()))
self.text_input_entry.setCurrentFont(font)
self.font_name = self.font_type_cb.currentFont().family()
def font_size(self):
self.text_input_entry.selectAll()
self.text_input_entry.setFontPointSize(float(self.font_size_cb.get_value()))
def on_bold_button(self):
if self.font_bold_tb.isChecked():
self.text_input_entry.selectAll()
self.text_input_entry.setFontWeight(QtGui.QFont.Weight.Bold)
self.font_bold = True
else:
self.text_input_entry.selectAll()
self.text_input_entry.setFontWeight(QtGui.QFont.Weight.Normal)
self.font_bold = False
def on_italic_button(self):
if self.font_italic_tb.isChecked():
self.text_input_entry.selectAll()
self.text_input_entry.setFontItalic(True)
self.font_italic = True
else:
self.text_input_entry.selectAll()
self.text_input_entry.setFontItalic(False)
self.font_italic = False
def hide_tool(self):
self.text_tool_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
# self.app.ui.splitter.setSizes([0, 1])
self.app.ui.notebook.setTabText(2, _("Tool"))

View File

@@ -0,0 +1,977 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class TransformEditorTool(AppTool):
"""
Inputs to specify how to paint the selected polygons.
"""
pluginName = _("Transform Tool")
rotateName = _("Rotate")
skewName = _("Skew/Shear")
scaleName = _("Scale")
flipName = _("Mirror (Flip)")
offsetName = _("Offset")
bufferName = _("Buffer")
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.app = app
self.draw_app = draw_app
self.decimals = self.app.decimals
# ## Title
title_label = FCLabel("%s" % self.pluginName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(FCLabel(''))
# ## Layout
grid0 = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0])
self.layout.addLayout(grid0)
grid0.addWidget(FCLabel(''))
# Reference
ref_label = FCLabel('%s:' % _("Reference"))
ref_label.setToolTip(
_("The reference point for Rotate, Skew, Scale, Mirror.\n"
"Can be:\n"
"- Origin -> it is the 0, 0 point\n"
"- Selection -> the center of the bounding box of the selected objects\n"
"- Point -> a custom point defined by X,Y coordinates\n"
"- Min Selection -> the point (minx, miny) of the bounding box of the selection")
)
self.ref_combo = FCComboBox()
self.ref_items = [_("Origin"), _("Selection"), _("Point"), _("Minimum")]
self.ref_combo.addItems(self.ref_items)
grid0.addWidget(ref_label, 0, 0)
grid0.addWidget(self.ref_combo, 0, 1, 1, 2)
self.point_label = FCLabel('%s:' % _("Value"))
self.point_label.setToolTip(
_("A point of reference in format X,Y.")
)
self.point_entry = NumericalEvalTupleEntry()
grid0.addWidget(self.point_label, 1, 0)
grid0.addWidget(self.point_entry, 1, 1, 1, 2)
self.point_button = FCButton(_("Add"))
self.point_button.setToolTip(
_("Add point coordinates from clipboard.")
)
grid0.addWidget(self.point_button, 2, 0, 1, 3)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 5, 0, 1, 3)
# ## Rotate Title
rotate_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.rotateName)
grid0.addWidget(rotate_title_label, 6, 0, 1, 3)
self.rotate_label = FCLabel('%s:' % _("Angle"))
self.rotate_label.setToolTip(
_("Angle, in degrees.\n"
"Float number between -360 and 359.\n"
"Positive numbers for CW motion.\n"
"Negative numbers for CCW motion.")
)
self.rotate_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.rotate_entry.set_precision(self.decimals)
self.rotate_entry.setSingleStep(45)
self.rotate_entry.setWrapping(True)
self.rotate_entry.set_range(-360, 360)
# self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.rotate_button = FCButton(_("Rotate"))
self.rotate_button.setToolTip(
_("Rotate the selected object(s).\n"
"The point of reference is the middle of\n"
"the bounding box for all selected objects.")
)
self.rotate_button.setMinimumWidth(90)
grid0.addWidget(self.rotate_label, 7, 0)
grid0.addWidget(self.rotate_entry, 7, 1)
grid0.addWidget(self.rotate_button, 7, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 8, 0, 1, 3)
# ## Skew Title
skew_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.skewName)
grid0.addWidget(skew_title_label, 9, 0, 1, 2)
self.skew_link_cb = FCCheckBox()
self.skew_link_cb.setText(_("Link"))
self.skew_link_cb.setToolTip(
_("Link the Y entry to X entry and copy its content.")
)
grid0.addWidget(self.skew_link_cb, 9, 2)
self.skewx_label = FCLabel('%s:' % _("X angle"))
self.skewx_label.setToolTip(
_("Angle for Skew action, in degrees.\n"
"Float number between -360 and 360.")
)
self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.skewx_entry.set_precision(self.decimals)
self.skewx_entry.set_range(-360, 360)
self.skewx_button = FCButton(_("Skew X"))
self.skewx_button.setToolTip(
_("Skew/shear the selected object(s).\n"
"The point of reference is the middle of\n"
"the bounding box for all selected objects."))
self.skewx_button.setMinimumWidth(90)
grid0.addWidget(self.skewx_label, 10, 0)
grid0.addWidget(self.skewx_entry, 10, 1)
grid0.addWidget(self.skewx_button, 10, 2)
self.skewy_label = FCLabel('%s:' % _("Y angle"))
self.skewy_label.setToolTip(
_("Angle for Skew action, in degrees.\n"
"Float number between -360 and 360.")
)
self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.skewy_entry.set_precision(self.decimals)
self.skewy_entry.set_range(-360, 360)
self.skewy_button = FCButton(_("Skew Y"))
self.skewy_button.setToolTip(
_("Skew/shear the selected object(s).\n"
"The point of reference is the middle of\n"
"the bounding box for all selected objects."))
self.skewy_button.setMinimumWidth(90)
grid0.addWidget(self.skewy_label, 12, 0)
grid0.addWidget(self.skewy_entry, 12, 1)
grid0.addWidget(self.skewy_button, 12, 2)
self.ois_sk = OptionalInputSection(self.skew_link_cb, [self.skewy_label, self.skewy_entry, self.skewy_button],
logic=False)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 14, 0, 1, 3)
# ## Scale Title
scale_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.scaleName)
grid0.addWidget(scale_title_label, 15, 0, 1, 2)
self.scale_link_cb = FCCheckBox()
self.scale_link_cb.setText(_("Link"))
self.scale_link_cb.setToolTip(
_("Link the Y entry to X entry and copy its content.")
)
grid0.addWidget(self.scale_link_cb, 15, 2)
self.scalex_label = FCLabel('%s:' % _("X factor"))
self.scalex_label.setToolTip(
_("Factor for scaling on X axis.")
)
self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.scalex_entry.set_precision(self.decimals)
self.scalex_entry.setMinimum(-1e6)
self.scalex_button = FCButton(_("Scale X"))
self.scalex_button.setToolTip(
_("Scale the selected object(s).\n"
"The point of reference depends on \n"
"the Scale reference checkbox state."))
self.scalex_button.setMinimumWidth(90)
grid0.addWidget(self.scalex_label, 17, 0)
grid0.addWidget(self.scalex_entry, 17, 1)
grid0.addWidget(self.scalex_button, 17, 2)
self.scaley_label = FCLabel('%s:' % _("Y factor"))
self.scaley_label.setToolTip(
_("Factor for scaling on Y axis.")
)
self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.scaley_entry.set_precision(self.decimals)
self.scaley_entry.setMinimum(-1e6)
self.scaley_button = FCButton(_("Scale Y"))
self.scaley_button.setToolTip(
_("Scale the selected object(s).\n"
"The point of reference depends on \n"
"the Scale reference checkbox state."))
self.scaley_button.setMinimumWidth(90)
grid0.addWidget(self.scaley_label, 19, 0)
grid0.addWidget(self.scaley_entry, 19, 1)
grid0.addWidget(self.scaley_button, 19, 2)
self.ois_s = OptionalInputSection(self.scale_link_cb,
[
self.scaley_label,
self.scaley_entry,
self.scaley_button
], logic=False)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 21, 0, 1, 3)
# ## Flip Title
flip_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.flipName)
grid0.addWidget(flip_title_label, 23, 0, 1, 3)
self.flipx_button = FCButton(_("Flip on X"))
self.flipx_button.setToolTip(
_("Flip the selected object(s) over the X axis.")
)
self.flipy_button = FCButton(_("Flip on Y"))
self.flipy_button.setToolTip(
_("Flip the selected object(s) over the X axis.")
)
hlay0 = QtWidgets.QHBoxLayout()
grid0.addLayout(hlay0, 25, 0, 1, 3)
hlay0.addWidget(self.flipx_button)
hlay0.addWidget(self.flipy_button)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 27, 0, 1, 3)
# ## Offset Title
offset_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.offsetName)
grid0.addWidget(offset_title_label, 29, 0, 1, 3)
self.offx_label = FCLabel('%s:' % _("X val"))
self.offx_label.setToolTip(
_("Distance to offset on X axis. In current units.")
)
self.offx_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.offx_entry.set_precision(self.decimals)
self.offx_entry.setMinimum(-1e6)
self.offx_button = FCButton(_("Offset X"))
self.offx_button.setToolTip(
_("Offset the selected object(s).\n"
"The point of reference is the middle of\n"
"the bounding box for all selected objects.\n"))
self.offx_button.setMinimumWidth(90)
grid0.addWidget(self.offx_label, 31, 0)
grid0.addWidget(self.offx_entry, 31, 1)
grid0.addWidget(self.offx_button, 31, 2)
self.offy_label = FCLabel('%s:' % _("Y val"))
self.offy_label.setToolTip(
_("Distance to offset on Y axis. In current units.")
)
self.offy_entry = FCDoubleSpinner(callback=self.confirmation_message)
# self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.offy_entry.set_precision(self.decimals)
self.offy_entry.setMinimum(-1e6)
self.offy_button = FCButton(_("Offset Y"))
self.offy_button.setToolTip(
_("Offset the selected object(s).\n"
"The point of reference is the middle of\n"
"the bounding box for all selected objects.\n"))
self.offy_button.setMinimumWidth(90)
grid0.addWidget(self.offy_label, 32, 0)
grid0.addWidget(self.offy_entry, 32, 1)
grid0.addWidget(self.offy_button, 32, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 34, 0, 1, 3)
# ## Buffer Title
buffer_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.bufferName)
grid0.addWidget(buffer_title_label, 35, 0, 1, 2)
self.buffer_rounded_cb = FCCheckBox('%s' % _("Rounded"))
self.buffer_rounded_cb.setToolTip(
_("If checked then the buffer will surround the buffered shape,\n"
"every corner will be rounded.\n"
"If not checked then the buffer will follow the exact geometry\n"
"of the buffered shape.")
)
grid0.addWidget(self.buffer_rounded_cb, 35, 2)
self.buffer_label = FCLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased with the 'distance'.")
)
self.buffer_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.buffer_entry.set_precision(self.decimals)
self.buffer_entry.setSingleStep(0.1)
self.buffer_entry.setWrapping(True)
self.buffer_entry.set_range(-10000.0000, 10000.0000)
self.buffer_button = FCButton(_("Buffer D"))
self.buffer_button.setToolTip(
_("Create the buffer effect on each geometry,\n"
"element from the selected object, using the distance.")
)
self.buffer_button.setMinimumWidth(90)
grid0.addWidget(self.buffer_label, 37, 0)
grid0.addWidget(self.buffer_entry, 37, 1)
grid0.addWidget(self.buffer_button, 37, 2)
self.buffer_factor_label = FCLabel('%s:' % _("Value"))
self.buffer_factor_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
"Each geometry element of the object will be increased\n"
"or decreased to fit the 'Value'. Value is a percentage\n"
"of the initial dimension.")
)
self.buffer_factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%')
self.buffer_factor_entry.set_range(-100.0000, 1000.0000)
self.buffer_factor_entry.set_precision(self.decimals)
self.buffer_factor_entry.setWrapping(True)
self.buffer_factor_entry.setSingleStep(1)
self.buffer_factor_button = FCButton(_("Buffer F"))
self.buffer_factor_button.setToolTip(
_("Create the buffer effect on each geometry,\n"
"element from the selected object, using the factor.")
)
self.buffer_factor_button.setMinimumWidth(90)
grid0.addWidget(self.buffer_factor_label, 38, 0)
grid0.addWidget(self.buffer_factor_entry, 38, 1)
grid0.addWidget(self.buffer_factor_button, 38, 2)
grid0.addWidget(FCLabel(''), 42, 0, 1, 3)
self.layout.addStretch()
# Signals
self.ref_combo.currentIndexChanged.connect(self.on_reference_changed)
self.point_button.clicked.connect(lambda: self.on_add_coords)
self.rotate_button.clicked.connect(lambda: self.on_rotate)
self.skewx_button.clicked.connect(lambda: self.on_skewx)
self.skewy_button.clicked.connect(lambda: self.on_skewy)
self.scalex_button.clicked.connect(lambda: self.on_scalex)
self.scaley_button.clicked.connect(lambda: self.on_scaley)
self.offx_button.clicked.connect(lambda: self.on_offx)
self.offy_button.clicked.connect(lambda: self.on_offy)
self.flipx_button.clicked.connect(lambda: self.on_flipx)
self.flipy_button.clicked.connect(lambda: self.on_flipy)
self.buffer_button.clicked.connect(lambda: self.on_buffer_by_distance)
self.buffer_factor_button.clicked.connect(lambda: self.on_buffer_by_factor)
self.set_tool_ui()
def run(self, toggle=True):
self.app.defaults.report_usage("Geo Editor Transform Tool()")
# 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
if toggle:
try:
if self.app.ui.plugin_scroll_area.widget().objectName() == self.pluginName:
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
else:
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
except AttributeError:
pass
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
def set_tool_ui(self):
# Initialize form
ref_val = self.app.options["tools_transform_reference"]
if ref_val == _("Object"):
ref_val = _("Selection")
self.ref_combo.set_value(ref_val)
self.point_entry.set_value(self.app.options["tools_transform_ref_point"])
self.rotate_entry.set_value(self.app.options["tools_transform_rotate"])
self.skewx_entry.set_value(self.app.options["tools_transform_skew_x"])
self.skewy_entry.set_value(self.app.options["tools_transform_skew_y"])
self.skew_link_cb.set_value(self.app.options["tools_transform_skew_link"])
self.scalex_entry.set_value(self.app.options["tools_transform_scale_x"])
self.scaley_entry.set_value(self.app.options["tools_transform_scale_y"])
self.scale_link_cb.set_value(self.app.options["tools_transform_scale_link"])
self.offx_entry.set_value(self.app.options["tools_transform_offset_x"])
self.offy_entry.set_value(self.app.options["tools_transform_offset_y"])
self.buffer_entry.set_value(self.app.options["tools_transform_buffer_dis"])
self.buffer_factor_entry.set_value(self.app.options["tools_transform_buffer_factor"])
self.buffer_rounded_cb.set_value(self.app.options["tools_transform_buffer_corner"])
# initial state is hidden
self.point_label.hide()
self.point_entry.hide()
self.point_button.hide()
def template(self):
if not self.draw_app.selected:
self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
return
self.draw_app.select_tool("select")
self.app.ui.notebook.setTabText(2, "Plugins")
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
self.app.ui.splitter.setSizes([0, 1])
def on_reference_changed(self, index):
if index == 0 or index == 1: # "Origin" or "Selection" reference
self.point_label.hide()
self.point_entry.hide()
self.point_button.hide()
elif index == 2: # "Point" reference
self.point_label.show()
self.point_entry.show()
self.point_button.show()
def on_calculate_reference(self, ref_index=None):
if ref_index:
ref_val = ref_index
else:
ref_val = self.ref_combo.currentIndex()
if ref_val == 0: # "Origin" reference
return 0, 0
elif ref_val == 1: # "Selection" reference
sel_list = self.draw_app.selected
if sel_list:
xmin, ymin, xmax, ymax = self.alt_bounds(sel_list)
px = (xmax + xmin) * 0.5
py = (ymax + ymin) * 0.5
return px, py
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No shape selected."))
return "fail"
elif ref_val == 2: # "Point" reference
point_val = self.point_entry.get_value()
try:
px, py = eval('{}'.format(point_val))
return px, py
except Exception:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Incorrect format for Point value. Needs format X,Y"))
return "fail"
else:
sel_list = self.draw_app.selected
if sel_list:
xmin, ymin, xmax, ymax = self.alt_bounds(sel_list)
if ref_val == 3:
return xmin, ymin # lower left corner
elif ref_val == 4:
return xmax, ymin # lower right corner
elif ref_val == 5:
return xmax, ymax # upper right corner
else:
return xmin, ymax # upper left corner
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No shape selected."))
return "fail"
def on_add_coords(self):
val = self.app.clipboard.text()
self.point_entry.set_value(val)
def on_rotate(self, val=None, ref=None):
value = float(self.rotate_entry.get_value()) if val is None else val
if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate transformation can not be done for a value of 0."))
return
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value, point]})
def on_flipx(self, ref=None):
axis = 'Y'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]})
def on_flipy(self, ref=None):
axis = 'X'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]})
def on_skewx(self, val=None, ref=None):
xvalue = float(self.skewx_entry.get_value()) if val is None else val
if xvalue == 0:
return
if self.skew_link_cb.get_value():
yvalue = xvalue
else:
yvalue = 0
axis = 'X'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]})
def on_skewy(self, val=None, ref=None):
xvalue = 0
yvalue = float(self.skewy_entry.get_value()) if val is None else val
if yvalue == 0:
return
axis = 'Y'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]})
def on_scalex(self, val=None, ref=None):
xvalue = float(self.scalex_entry.get_value()) if val is None else val
if xvalue == 0 or xvalue == 1:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Scale transformation can not be done for a factor of 0 or 1."))
return
if self.scale_link_cb.get_value():
yvalue = xvalue
else:
yvalue = 1
axis = 'X'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
def on_scaley(self, val=None, ref=None):
xvalue = 1
yvalue = float(self.scaley_entry.get_value()) if val is None else val
if yvalue == 0 or yvalue == 1:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Scale transformation can not be done for a factor of 0 or 1."))
return
axis = 'Y'
point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref)
if point == 'fail':
return
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
def on_offx(self, val=None):
value = float(self.offx_entry.get_value()) if val is None else val
if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
return
axis = 'X'
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
def on_offy(self, val=None):
value = float(self.offy_entry.get_value()) if val is None else val
if value == 0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
return
axis = 'Y'
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
def on_buffer_by_distance(self):
value = self.buffer_entry.get_value()
join = 1 if self.buffer_rounded_cb.get_value() else 2
self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]})
def on_buffer_by_factor(self):
value = 1 + (self.buffer_factor_entry.get_value() / 100.0)
join = 1 if self.buffer_rounded_cb.get_value() else 2
# tell the buffer method to use the factor
factor = True
self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join, factor]})
def on_rotate_action(self, val, point):
"""
Rotate geometry
:param val: Rotate with a known angle value, val
:param point: Reference point for rotation: tuple
:return:
"""
with self.app.proc_container.new('%s...' % _("Rotating")):
shape_list = self.draw_app.selected
px, py = point
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
try:
for sel_sha in shape_list:
sel_sha.rotate(-val, point=(px, py))
self.draw_app.plot_all()
self.app.inform.emit('[success] %s' % _("Done."))
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_flip(self, axis, point):
"""
Mirror (flip) geometry
:param axis: Mirror on a known axis given by the axis parameter
:param point: Mirror reference point
:return:
"""
shape_list = self.draw_app.selected
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
with self.app.proc_container.new('%s...' % _("Flipping")):
try:
px, py = point
# execute mirroring
for sha in shape_list:
if axis == 'X':
sha.mirror('X', (px, py))
self.app.inform.emit('[success] %s...' % _('Flip on Y axis done'))
elif axis == 'Y':
sha.mirror('Y', (px, py))
self.app.inform.emit('[success] %s' % _('Flip on X axis done'))
self.draw_app.plot_all()
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_skew(self, axis, xval, yval, point):
"""
Skew geometry
:param point:
:param axis: Axis on which to deform, skew
:param xval: Skew value on X axis
:param yval: Skew value on Y axis
:return:
"""
shape_list = self.draw_app.selected
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
with self.app.proc_container.new('%s...' % _("Skewing")):
try:
px, py = point
for sha in shape_list:
sha.skew(xval, yval, point=(px, py))
self.draw_app.plot_all()
if axis == 'X':
self.app.inform.emit('[success] %s...' % _('Skew on the X axis done'))
else:
self.app.inform.emit('[success] %s...' % _('Skew on the Y axis done'))
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_scale(self, axis, xfactor, yfactor, point=None):
"""
Scale geometry
:param axis: Axis on which to scale
:param xfactor: Factor for scaling on X axis
:param yfactor: Factor for scaling on Y axis
:param point: Point of origin for scaling
:return:
"""
shape_list = self.draw_app.selected
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
with self.app.proc_container.new('%s...' % _("Scaling")):
try:
px, py = point
for sha in shape_list:
sha.scale(xfactor, yfactor, point=(px, py))
self.draw_app.plot_all()
if str(axis) == 'X':
self.app.inform.emit('[success] %s...' % _('Scale on the X axis done'))
else:
self.app.inform.emit('[success] %s...' % _('Scale on the Y axis done'))
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_offset(self, axis, num):
"""
Offset geometry
:param axis: Axis on which to apply offset
:param num: The translation factor
:return:
"""
shape_list = self.draw_app.selected
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
with self.app.proc_container.new('%s...' % _("Offsetting")):
try:
for sha in shape_list:
if axis == 'X':
sha.offset((num, 0))
elif axis == 'Y':
sha.offset((0, num))
self.draw_app.plot_all()
if axis == 'X':
self.app.inform.emit('[success] %s %s' % (_('Offset on the X axis.'), _("Done.")))
else:
self.app.inform.emit('[success] %s %s' % (_('Offset on the Y axis.'), _("Done.")))
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_buffer_action(self, value, join, factor=None):
shape_list = self.draw_app.selected
if not shape_list:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
return
else:
with self.app.proc_container.new('%s...' % _("Buffering")):
try:
for sel_obj in shape_list:
sel_obj.buffer(value, join, factor)
self.draw_app.plot_all()
self.app.inform.emit('[success] %s...' % _('Buffer done'))
except Exception as e:
self.app.log.error("TransformEditorTool.on_buffer_action() --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
return
def on_rotate_key(self):
val_box = FCInputDoubleSpinner(title=_("Rotate ..."),
text='%s:' % _('Enter an Angle Value (degrees)'),
min=-359.9999, max=360.0000, decimals=self.decimals,
init_val=float(self.app.options['tools_transform_rotate']),
parent=self.app.ui)
val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/rotate.png'))
val, ok = val_box.get_value()
if ok:
self.on_rotate(val=val, ref=1)
self.app.inform.emit('[success] %s...' % _("Rotate done"))
return
else:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate cancelled"))
def on_offx_key(self):
units = self.app.app_units.lower()
val_box = FCInputDoubleSpinner(title=_("Offset on X axis ..."),
text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
min=-10000.0000, max=10000.0000, decimals=self.decimals,
init_val=float(self.app.options['tools_transform_offset_x']),
parent=self.app.ui)
val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsetx32.png'))
val, ok = val_box.get_value()
if ok:
self.on_offx(val=val)
self.app.inform.emit('[success] %s %s' % (_('Offset on the X axis.'), _("Done.")))
return
else:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset X cancelled"))
def on_offy_key(self):
units = self.app.app_units.lower()
val_box = FCInputDoubleSpinner(title=_("Offset on Y axis ..."),
text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
min=-10000.0000, max=10000.0000, decimals=self.decimals,
init_val=float(self.app.options['tools_transform_offset_y']),
parent=self.app.ui)
val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsety32.png'))
val, ok = val_box.get_value()
if ok:
self.on_offx(val=val)
self.app.inform.emit('[success] %s...' % _("Offset on Y axis done"))
return
else:
self.app.inform.emit('[success] %s...' % _("Offset on the Y axis canceled"))
def on_skewx_key(self):
val_box = FCInputDoubleSpinner(title=_("Skew on X axis ..."),
text='%s:' % _('Enter an Angle Value (degrees)'),
min=-359.9999, max=360.0000, decimals=self.decimals,
init_val=float(self.app.options['tools_transform_skew_x']),
parent=self.app.ui)
val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewX.png'))
val, ok = val_box.get_value()
if ok:
self.on_skewx(val=val, ref=3)
self.app.inform.emit('[success] %s...' % _("Skew on X axis done"))
return
else:
self.app.inform.emit('[success] %s...' % _("Skew on X axis canceled"))
def on_skewy_key(self):
val_box = FCInputDoubleSpinner(title=_("Skew on Y axis ..."),
text='%s:' % _('Enter an Angle Value (degrees)'),
min=-359.9999, max=360.0000, decimals=self.decimals,
init_val=float(self.app.options['tools_transform_skew_y']),
parent=self.app.ui)
val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewY.png'))
val, ok = val_box.get_value()
if ok:
self.on_skewx(val=val, ref=3)
self.app.inform.emit('[success] %s...' % _("Skew on Y axis done"))
return
else:
self.app.inform.emit('[success] %s...' % _("Skew on Y axis canceled"))
@staticmethod
def alt_bounds(shapelist):
"""
Returns coordinates of rectangular bounds of a selection of shapes
"""
def bounds_rec(lst):
minx = np.Inf
miny = np.Inf
maxx = -np.Inf
maxy = -np.Inf
try:
for shp in lst:
minx_, miny_, maxx_, maxy_ = bounds_rec(shp)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
except TypeError:
# it's an object, return it's bounds
return lst.bounds()
return bounds_rec(shapelist)