- in Geometry Editor, in Circle Tool added UI, cursor data, radius projection and ability to add ellipses

This commit is contained in:
Marius Stanciu
2022-04-16 16:01:39 +03:00
committed by Marius
parent e399c6cddc
commit 4d496e4539
11 changed files with 449 additions and 13 deletions

View File

@@ -0,0 +1,411 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class BufferSelectionTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self.ui = BufferEditorUI(layout=self.layout, buffer_class=self)
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.buffer_button.clicked.connect(self.on_buffer)
self.ui.buffer_int_button.clicked.connect(self.on_buffer_int)
self.ui.buffer_ext_button.clicked.connect(self.on_buffer_ext)
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"))
def set_tool_ui(self):
# Init appGUI
self.ui.buffer_distance_entry.set_value(0.01)
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.ui.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.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.ui.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.ui.buffer_corner_cb.currentIndex() + 1
self.buffer(buffer_distance, join_style)
def on_buffer_int(self):
try:
buffer_distance = float(self.ui.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.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.ui.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.ui.buffer_corner_cb.currentIndex() + 1
self.buffer_int(buffer_distance, join_style)
def on_buffer_ext(self):
try:
buffer_distance = float(self.ui.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.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.ui.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.ui.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.ui.buffer_tool_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
class BufferEditorUI:
pluginName = _("Buffer")
def __init__(self, layout, buffer_class):
self.buffer_class = buffer_class
self.decimals = self.buffer_class.app.decimals
self.layout = layout
# 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, 10000.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)

View File

@@ -0,0 +1,270 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class CircleEditorTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app, plugin_name):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self._mode = 'add'
self.ui = CircleEditorUI(layout=self.layout, circle_class=self)
self.ui.pluginName = plugin_name
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.add_button.clicked.connect(self.on_execute)
def run(self):
self.app.defaults.report_usage("Geo Editor CircleTool()")
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, self.ui.pluginName)
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, self.ui.pluginName)
def set_tool_ui(self):
# Init appGUI
self.ui.x_entry.set_value(0)
self.ui.y_entry.set_value(0)
self.ui.radius_x_entry.set_value(0)
self.ui.radius_y_entry.set_value(0)
self.ui.angle_entry.set_value(0)
self.ui.radius_link_btn.setChecked(True)
self.ui.on_link_checked(True)
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_execute(self):
if self.mode == 'add':
self.app.log.info("CircleEditorTool.on_add() -> adding a Circle shape")
self.on_add()
else:
self.app.log.info("RectangleEditorTool.on_add() -> modifying a Circle shape")
self.draw_app.delete_selected()
self.on_add()
self.draw_app.app.inform.emit(_("Click on Center point ..."))
def on_add(self):
origin_x = self.ui.x_entry.get_value()
origin_y = self.ui.y_entry.get_value()
radius_x = self.ui.radius_x_entry.get_value()
radius_y = self.ui.radius_y_entry.get_value()
angle = self.ui.angle_entry.get_value()
is_circle = True if self.ui.radius_link_btn.isChecked() else False
if radius_x == 0.0 or radius_y == 0.0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed."))
return
if is_circle:
geo = Point((origin_x, origin_y)).buffer(radius_x)
else: # 'ellipse'
circle_geo = Point((origin_x, origin_y)).buffer(1)
geo = scale(circle_geo, radius_x, radius_y)
if angle != 0:
geo = rotate(geo, -angle)
added_shapes = self.draw_app.add_shape(geo.exterior)
for added_shape in added_shapes:
added_shape.data['type'] = _("Circle")
self.draw_app.plot_all()
def on_clear(self):
self.set_tool_ui()
@property
def mode(self):
return self._mode
@mode.setter
def mode(self, val):
self._mode = val
if self._mode == 'add':
# remove selections when adding a new rectangle
self.draw_app.selected = []
self.ui.add_button.set_value(_("Add"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
else:
self.ui.add_button.set_value(_("Apply"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/apply32.png'))
@property
def radius_x(self):
return self.ui.radius_x_entry.get_value()
@radius_x.setter
def radius_x(self, val):
self.ui.radius_x_entry.set_value(val)
@property
def radius_y(self):
return self.ui.radius_y_entry.get_value()
@radius_y.setter
def radius_y(self, val):
self.ui.radius_y_entry.set_value(val)
def hide_tool(self):
self.ui.circle_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
if self.draw_app.active_tool.name != 'select':
self.draw_app.select_tool("select")
class CircleEditorUI:
pluginName = _("Circle")
def __init__(self, layout, circle_class):
self.circle_class = circle_class
self.decimals = self.circle_class.app.decimals
self.app = self.circle_class.app
self.layout = layout
# 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.circle_frame = QtWidgets.QFrame()
self.circle_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.circle_frame)
self.circle_tool_box = QtWidgets.QVBoxLayout()
self.circle_tool_box.setContentsMargins(0, 0, 0, 0)
self.circle_frame.setLayout(self.circle_tool_box)
# Grid Layout
grid0 = GLay(v_spacing=5, h_spacing=3)
self.circle_tool_box.addLayout(grid0)
# Position
self.pos_lbl = FCLabel('<b>%s</b>' % _("Position"))
grid0.addWidget(self.pos_lbl, 0, 0, 1, 3)
# X Pos
self.x_lbl = FCLabel('%s:' % _("X"))
self.x_entry = FCDoubleSpinner()
self.x_entry.set_precision(self.decimals)
self.x_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.x_lbl, 2, 0)
grid0.addWidget(self.x_entry, 2, 1, 1, 2)
# Y Pos
self.y_lbl = FCLabel('%s:' % _("Y"))
self.y_entry = FCDoubleSpinner()
self.y_entry.set_precision(self.decimals)
self.y_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.y_lbl, 4, 0)
grid0.addWidget(self.y_entry, 4, 1, 1, 2)
# Radius X
self.radius_x_lbl = FCLabel('%s X:' % _("Radius"))
self.radius_x_entry = FCDoubleSpinner()
self.radius_x_entry.set_precision(self.decimals)
self.radius_x_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(self.radius_x_lbl, 6, 0)
grid0.addWidget(self.radius_x_entry, 6, 1)
# Radius Y
self.radius_y_lbl = FCLabel('%s Y:' % _("Radius"))
self.radius_y_entry = FCDoubleSpinner()
self.radius_y_entry.set_precision(self.decimals)
self.radius_y_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(self.radius_y_lbl, 7, 0)
grid0.addWidget(self.radius_y_entry, 7, 1)
# Angle
self.angle_lbl = FCLabel('%s:' % _("Angle"))
self.angle_entry = FCDoubleSpinner()
self.angle_entry.set_precision(self.decimals)
self.angle_entry.set_range(0.0000, 360.0000)
grid0.addWidget(self.angle_lbl, 8, 0)
grid0.addWidget(self.angle_entry, 8, 1)
# Radius link
self.radius_link_btn = QtWidgets.QToolButton()
self.radius_link_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/link32.png'))
self.radius_link_btn.setSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.radius_link_btn.setCheckable(True)
grid0.addWidget(self.radius_link_btn, 6, 2, 3, 1)
# Buttons
self.add_button = FCButton(_("Add"))
self.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
grid0.addWidget(self.add_button, 18, 0, 1, 3)
self.layout.addStretch(1)
# Note
self.note_lbl = FCLabel('<b>%s</b>' % _("Note"))
self.layout.addWidget(self.note_lbl)
self.note_description_lbl = FCLabel('%s' % _("Shift + click to select a shape for modification."))
self.layout.addWidget(self.note_description_lbl)
# Signals
self.radius_link_btn.clicked.connect(self.on_link_checked)
def on_link_checked(self, checked):
if checked:
self.radius_x_lbl.set_value('%s:' % _("Radius"))
self.radius_y_lbl.setDisabled(True)
self.radius_y_entry.setDisabled(True)
self.radius_y_entry.set_value(self.radius_x_entry.get_value())
self.angle_lbl.setDisabled(True)
self.angle_entry.setDisabled(True)
else:
self.radius_x_lbl.set_value('%s X:' % _("Radius"))
self.radius_y_lbl.setDisabled(False)
self.radius_y_entry.setDisabled(False)
self.angle_lbl.setDisabled(False)
self.angle_entry.setDisabled(False)

View File

@@ -0,0 +1,335 @@
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:
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)
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(1)

View File

@@ -0,0 +1,156 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class PathEditorTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app, plugin_name):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self.plugin_name = plugin_name
self.ui = PathEditorUI(layout=self.layout, path_class=self, plugin_name=plugin_name)
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.clear_btn.clicked.connect(self.on_clear)
def disconnect_signals(self):
# Signals
try:
self.ui.clear_btn.clicked.disconnect()
except (TypeError, AttributeError):
pass
def run(self):
self.app.defaults.report_usage("Geo Editor ToolPath()")
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, self.plugin_name)
def set_tool_ui(self):
# Init appGUI
self.length = 0.0
def on_tab_close(self):
self.disconnect_signals()
self.hide_tool()
# self.app.ui.notebook.callback_on_close = lambda: None
def on_clear(self):
self.set_tool_ui()
@property
def length(self):
return self.ui.project_line_entry.get_value()
@length.setter
def length(self, val):
self.ui.project_line_entry.set_value(val)
def hide_tool(self):
self.ui.path_tool_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
if self.draw_app.active_tool.name != 'select':
self.draw_app.select_tool("select")
class PathEditorUI:
def __init__(self, layout, path_class, plugin_name):
self.pluginName = plugin_name
self.path_class = path_class
self.decimals = self.path_class.app.decimals
self.layout = layout
# 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.path_tool_frame = QtWidgets.QFrame()
self.path_tool_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.path_tool_frame)
self.path_tool_box = QtWidgets.QVBoxLayout()
self.path_tool_box.setContentsMargins(0, 0, 0, 0)
self.path_tool_frame.setLayout(self.path_tool_box)
# Grid Layout
grid_path = GLay(v_spacing=5, h_spacing=3)
self.path_tool_box.addLayout(grid_path)
# Project distance
self.project_line_lbl = FCLabel('%s:' % _("Length"))
self.project_line_lbl.setToolTip(
_("Length of the current segment/move.")
)
self.project_line_entry = NumericalEvalEntry(border_color='#0069A9')
grid_path.addWidget(self.project_line_lbl, 0, 0)
grid_path.addWidget(self.project_line_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_path.addWidget(self.buffer_corner_lbl, 2, 0)
# grid_path.addWidget(self.buffer_corner_cb, 2, 1)
self.clear_btn = FCButton(_("Clear"))
grid_path.addWidget(self.clear_btn, 4, 0, 1, 2)
self.layout.addStretch(1)

View File

@@ -0,0 +1,317 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class RectangleEditorTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app, plugin_name):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self._mode = 'add'
self.ui = RectangleEditorUI(layout=self.layout, rect_class=self)
self.ui.pluginName = plugin_name
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.add_button.clicked.connect(self.on_execute)
def run(self):
self.app.defaults.report_usage("Geo Editor RectangleTool()")
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, self.ui.pluginName)
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, self.ui.pluginName)
def set_tool_ui(self):
# Init appGUI
self.ui.anchor_radio.set_value('c')
self.ui.x_entry.set_value(self.draw_app.snap_x)
self.ui.y_entry.set_value(self.draw_app.snap_y)
self.ui.corner_radio.set_value('r')
self.ui.radius_entry.set_value(1)
self.ui.length_entry.set_value(0.0)
self.ui.width_entry.set_value(0.0)
self.ui.on_corner_changed(val=self.ui.corner_radio.get_value())
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_execute(self):
if self.mode == 'add':
self.app.log.info("RectangleEditorTool.on_add() -> adding a Rectangle shape")
self.on_add()
else:
self.app.log.info("RectangleEditorTool.on_add() -> modifying a Rectangle shape")
self.draw_app.delete_selected()
self.on_add()
self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
def on_add(self):
origin = self.ui.anchor_radio.get_value()
origin_x = self.ui.x_entry.get_value()
origin_y = self.ui.y_entry.get_value()
corner_type = self.ui.corner_radio.get_value()
corner_radius = self.ui.radius_entry.get_value()
length = self.ui.length_entry.get_value()
width = self.ui.width_entry.get_value()
if length == 0.0 or width == 0.0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed."))
return
if origin == 'tl':
cx = origin_x + (length / 2)
cy = origin_y - (width / 2)
elif origin == 'tr':
cx = origin_x - (length / 2)
cy = origin_y - (width / 2)
elif origin == 'bl':
cx = origin_x + (length / 2)
cy = origin_y + (width / 2)
elif origin == 'br':
cx = origin_x - (length / 2)
cy = origin_y + (width / 2)
else: # 'c' - center
cx = origin_x
cy = origin_y
if corner_radius == 0.0:
corner_type = 's'
if corner_type in ['r', 'b']:
length -= 2 * corner_radius
width -= 2 * corner_radius
minx = cx - (length / 2)
miny = cy - (width / 2)
maxx = cx + (length / 2)
maxy = cy + (width / 2)
if corner_type == 'r':
geo = box(minx, miny, maxx, maxy).buffer(
corner_radius, join_style=base.JOIN_STYLE.round,
resolution=self.draw_app.app.options["geometry_circle_steps"]).exterior
elif corner_type == 'b':
geo = box(minx, miny, maxx, maxy).buffer(
corner_radius, join_style=base.JOIN_STYLE.bevel,
resolution=self.draw_app.app.options["geometry_circle_steps"]).exterior
else: # 's' - square
geo = box(minx, miny, maxx, maxy).exterior
added_shapes = self.draw_app.add_shape(geo)
for added_shape in added_shapes:
added_shape.data['type'] = _("Rectangle")
self.draw_app.plot_all()
def on_clear(self):
self.set_tool_ui()
@property
def mode(self):
return self._mode
@mode.setter
def mode(self, val):
self._mode = val
if self._mode == 'add':
# remove selections when adding a new rectangle
self.draw_app.selected = []
self.ui.add_button.set_value(_("Add"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
else:
self.ui.add_button.set_value(_("Apply"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/apply32.png'))
@property
def length(self):
return self.ui.length_entry.get_value()
@length.setter
def length(self, val):
self.ui.length_entry.set_value(val)
@property
def width(self):
return self.ui.width_entry.get_value()
@width.setter
def width(self, val):
self.ui.width_entry.set_value(val)
def hide_tool(self):
self.ui.rect_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
if self.draw_app.active_tool.name != 'select':
self.draw_app.select_tool("select")
class RectangleEditorUI:
pluginName = _("Rectangle")
def __init__(self, layout, rect_class):
self.rect_class = rect_class
self.decimals = self.rect_class.app.decimals
self.app = self.rect_class.app
self.layout = layout
# 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.rect_frame = QtWidgets.QFrame()
self.rect_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.rect_frame)
self.rect_tool_box = QtWidgets.QVBoxLayout()
self.rect_tool_box.setContentsMargins(0, 0, 0, 0)
self.rect_frame.setLayout(self.rect_tool_box)
# Grid Layout
grid0 = GLay(v_spacing=5, h_spacing=3)
self.rect_tool_box.addLayout(grid0)
# Anchor
self.anchor_lbl = FCLabel('<b>%s:</b>' % _("Anchor"))
choices = [
{"label": _("T Left"), "value": "tl"},
{"label": _("T Right"), "value": "tr"},
{"label": _("B Left"), "value": "bl"},
{"label": _("B Right"), "value": "br"},
{"label": _("Center"), "value": "c"}
]
self.anchor_radio = RadioSetCross(choices, compact=True)
grid0.addWidget(self.anchor_lbl, 0, 0)
grid0.addWidget(self.anchor_radio, 0, 1)
# Position
self.pos_lbl = FCLabel('<b>%s</b>' % _("Position"))
grid0.addWidget(self.pos_lbl, 2, 0, 1, 2)
# X Pos
self.x_lbl = FCLabel('%s:' % _("X"))
self.x_entry = FCDoubleSpinner()
self.x_entry.set_precision(self.decimals)
self.x_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.x_lbl, 4, 0)
grid0.addWidget(self.x_entry, 4, 1)
# Y Pos
self.y_lbl = FCLabel('%s:' % _("Y"))
self.y_entry = FCDoubleSpinner()
self.y_entry.set_precision(self.decimals)
self.y_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.y_lbl, 6, 0)
grid0.addWidget(self.y_entry, 6, 1)
# Corner Type
self.corner_lbl = FCLabel('%s:' % _("Corner"))
self.corner_lbl.setToolTip(
_("There are 3 types of corners:\n"
" - 'Round': the corners are rounded\n"
" - 'Square': the corners meet in a sharp angle\n"
" - 'Beveled': the corners are a line that directly connects the features meeting in the corner")
)
self.corner_radio = RadioSet([
{'label': _('Round'), 'value': 'r'},
{'label': _('Square'), 'value': 's'},
{'label': _('Beveled'), 'value': 'b'},
], orientation='vertical', compact=True)
grid0.addWidget(self.corner_lbl, 8, 0)
grid0.addWidget(self.corner_radio, 8, 1)
# Radius
self.radius_lbl = FCLabel('%s:' % _("Radius"))
self.radius_entry = FCDoubleSpinner()
self.radius_entry.set_precision(self.decimals)
self.radius_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(self.radius_lbl, 10, 0)
grid0.addWidget(self.radius_entry, 10, 1)
# Size
self.size_lbl = FCLabel('<b>%s</b>' % _("Size"))
grid0.addWidget(self.size_lbl, 12, 0, 1, 2)
# Length
self.length_lbl = FCLabel('%s:' % _("Length"))
self.length_entry = NumericalEvalEntry()
grid0.addWidget(self.length_lbl, 14, 0)
grid0.addWidget(self.length_entry, 14, 1)
# Width
self.width_lbl = FCLabel('%s:' % _("Width"))
self.width_entry = NumericalEvalEntry()
grid0.addWidget(self.width_lbl, 16, 0)
grid0.addWidget(self.width_entry, 16, 1)
# Buttons
self.add_button = FCButton(_("Add"))
self.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
grid0.addWidget(self.add_button, 18, 0, 1, 2)
self.layout.addStretch(1)
# Note
self.note_lbl = FCLabel('<b>%s</b>' % _("Note"))
self.layout.addWidget(self.note_lbl)
self.note_description_lbl = FCLabel('%s' % _("Shift + click to select a shape for modification."))
self.layout.addWidget(self.note_description_lbl)
# Signals
self.corner_radio.activated_custom.connect(self.on_corner_changed)
def on_corner_changed(self, val):
if val in ['r', 'b']:
self.radius_lbl.show()
self.radius_entry.show()
else:
self.radius_lbl.hide()
self.radius_entry.hide()

View File

@@ -0,0 +1,263 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class SimplificationTool(AppTool):
"""
Do a shape simplification for the selected geometry.
"""
update_ui = pyqtSignal(list, int)
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self.app = self.draw_app.app
self.ui = SimplificationEditorUI(layout=self.layout, simp_class=self)
self.plugin_name = self.ui.pluginName
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.simplification_btn.clicked.connect(self.on_simplification_click)
self.update_ui.connect(self.on_update_ui)
def run(self):
self.app.defaults.report_usage("Geo Editor SimplificationTool()")
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, self.plugin_name)
def set_tool_ui(self):
# Init appGUI
self.ui.geo_tol_entry.set_value(0.01 if self.draw_app.units == 'MM' else 0.0004)
selected_shapes_geos = []
selected_tree_items = self.draw_app.tw.selectedItems()
for sel in selected_tree_items:
for obj_shape in self.draw_app.storage.get_objects():
try:
if id(obj_shape) == int(sel.text(0)):
selected_shapes_geos.append(obj_shape.geo)
except ValueError:
pass
if selected_shapes_geos:
# those are displayed by triggering the signal self.update_ui
self.calculate_coords_vertex(selected_shapes_geos[-1])
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_simplification_click(self):
self.app.log.debug("FCSimplification.on_simplification_click()")
selected_shapes_geos = []
tol = self.ui.geo_tol_entry.get_value()
def task_job(self):
with self.app.proc_container.new('%s...' % _("Simplify")):
selected_shapes = self.draw_app.get_selected()
self.draw_app.interdict_selection = True
for obj_shape in selected_shapes:
selected_shapes_geos.append(obj_shape.geo.simplify(tolerance=tol))
if not selected_shapes:
self.app.inform.emit('%s' % _("Failed."))
return
for shape in selected_shapes:
self.draw_app.delete_shape(shape=shape)
for geo in selected_shapes_geos:
self.draw_app.add_shape(geo, build_ui=False)
self.draw_app.selected = []
last_sel_geo = selected_shapes_geos[-1]
self.calculate_coords_vertex(last_sel_geo)
self.app.inform.emit('%s' % _("Done."))
self.draw_app.plot_all()
self.draw_app.interdict_selection = False
self.draw_app.build_ui_sig.emit()
self.app.worker_task.emit({'fcn': task_job, 'params': [self]})
def calculate_coords_vertex(self, last_sel_geo):
if last_sel_geo:
if last_sel_geo.geom_type == 'MultiLineString':
coords = ''
vertex_nr = 0
for idx, line in enumerate(last_sel_geo.geoms):
line_coords = list(line.coords)
vertex_nr += len(line_coords)
coords += 'Line %s\n' % str(idx)
coords += str(line_coords) + '\n'
elif last_sel_geo.geom_type == 'MultiPolygon':
coords = ''
vertex_nr = 0
for idx, poly in enumerate(last_sel_geo.geoms):
poly_coords = list(poly.exterior.coords) + [list(i.coords) for i in poly.interiors]
vertex_nr += len(poly_coords)
coords += 'Polygon %s\n' % str(idx)
coords += str(poly_coords) + '\n'
elif last_sel_geo.geom_type in ['LinearRing', 'LineString']:
coords = list(last_sel_geo.coords)
vertex_nr = len(coords)
elif last_sel_geo.geom_type == 'Polygon':
coords = list(last_sel_geo.exterior.coords)
vertex_nr = len(coords)
else:
coords = 'None'
vertex_nr = 0
self.update_ui.emit(coords, vertex_nr)
def on_update_ui(self, coords, vertex_nr):
self.ui.geo_coords_entry.set_value(str(coords))
self.ui.geo_vertex_entry.set_value(vertex_nr)
def hide_tool(self):
self.ui.simp_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
class SimplificationEditorUI:
pluginName = _("Simplification")
def __init__(self, layout, simp_class):
self.simp_class = simp_class
self.app = self.simp_class.app
self.decimals = self.app.decimals
self.layout = layout
# 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.simp_frame = QtWidgets.QFrame()
self.simp_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.simp_frame)
self.simp_tools_box = QtWidgets.QVBoxLayout()
self.simp_tools_box.setContentsMargins(0, 0, 0, 0)
self.simp_frame.setLayout(self.simp_tools_box)
# Grid Layout
grid0 = GLay(v_spacing=5, h_spacing=3)
self.simp_tools_box.addLayout(grid0)
# Coordinates
coords_lbl = FCLabel('<b>%s</b>:' % _("Coordinates"))
coords_lbl.setToolTip(
_("The coordinates of the selected geometry element.")
)
grid0.addWidget(coords_lbl, 22, 0, 1, 3)
self.geo_coords_entry = FCTextEdit()
self.geo_coords_entry.setPlaceholderText(
_("The coordinates of the selected geometry element.")
)
grid0.addWidget(self.geo_coords_entry, 24, 0, 1, 3)
# Vertex Points Number
vertex_lbl = FCLabel('<b>%s</b>:' % _("Vertex Points"))
vertex_lbl.setToolTip(
_("The number of vertex points in the selected geometry element.")
)
self.geo_vertex_entry = FCEntry(decimals=self.decimals)
self.geo_vertex_entry.setReadOnly(True)
grid0.addWidget(vertex_lbl, 26, 0)
grid0.addWidget(self.geo_vertex_entry, 26, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
grid0.addWidget(separator_line, 28, 0, 1, 3)
# Simplification Title
simplif_lbl = FCLabel('<b>%s</b>:' % _("Simplification"))
simplif_lbl.setToolTip(
_("Simplify a geometry by reducing its vertex points number.")
)
grid0.addWidget(simplif_lbl, 30, 0, 1, 3)
# Simplification Tolerance
simplification_tol_lbl = FCLabel('<b>%s</b>:' % _("Tolerance"))
simplification_tol_lbl.setToolTip(
_("All points in the simplified object will be\n"
"within the tolerance distance of the original geometry.")
)
self.geo_tol_entry = FCDoubleSpinner()
self.geo_tol_entry.set_precision(self.decimals)
self.geo_tol_entry.setSingleStep(10 ** -self.decimals)
self.geo_tol_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(simplification_tol_lbl, 32, 0)
grid0.addWidget(self.geo_tol_entry, 32, 1, 1, 2)
# Simplification button
self.simplification_btn = FCButton(_("Simplify"))
self.simplification_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/simplify32.png'))
self.simplification_btn.setToolTip(
_("Simplify a geometry element by reducing its vertex points number.")
)
self.simplification_btn.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.simplification_btn, 34, 0, 1, 3)
self.layout.addStretch(1)

View File

@@ -0,0 +1,276 @@
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.
"""
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()
self.font_name = None
self.font_bold = False
self.font_italic = False
self.ui = TextEditorUI(layout=self.layout, text_class=self)
self.connect_signals_at_init()
self.set_tool_ui()
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 connect_signals_at_init(self):
# Signals
self.ui.apply_button.clicked.connect(self.on_apply_button)
self.ui.font_type_cb.currentFontChanged.connect(self.font_family)
self.ui.font_size_cb.activated.connect(self.font_size)
self.ui.font_bold_tb.clicked.connect(self.on_bold_button)
self.ui.font_italic_tb.clicked.connect(self.on_italic_button)
def set_tool_ui(self):
# 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.ui.font_type_cb.setCurrentFont(f_current)
# Flag variables to show if font is bold, italic, both or none (regular)
self.font_bold = False
self.font_italic = False
self.ui.text_input_entry.setCurrentFont(f_current)
self.ui.text_input_entry.setFontPointSize(10)
# # 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
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.ui.text_input_entry.get_value()
font_to_geo_size = self.ui.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.ui.text_input_entry.selectAll()
font.setPointSize(int(self.ui.font_size_cb.get_value()))
self.ui.text_input_entry.setCurrentFont(font)
self.font_name = self.ui.font_type_cb.currentFont().family()
def font_size(self):
self.ui.text_input_entry.selectAll()
self.ui.text_input_entry.setFontPointSize(float(self.ui.font_size_cb.get_value()))
def on_bold_button(self):
if self.ui.font_bold_tb.isChecked():
self.ui.text_input_entry.selectAll()
self.ui.text_input_entry.setFontWeight(QtGui.QFont.Weight.Bold)
self.font_bold = True
else:
self.ui.text_input_entry.selectAll()
self.ui.text_input_entry.setFontWeight(QtGui.QFont.Weight.Normal)
self.font_bold = False
def on_italic_button(self):
if self.ui.font_italic_tb.isChecked():
self.ui.text_input_entry.selectAll()
self.ui.text_input_entry.setFontItalic(True)
self.font_italic = True
else:
self.ui.text_input_entry.selectAll()
self.ui.text_input_entry.setFontItalic(False)
self.font_italic = False
def hide_tool(self):
self.ui.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"))
self.app.ui.notebook.removeTab(2)
class TextEditorUI:
pluginName = _("Text")
def __init__(self, layout, text_class):
self.text_class = text_class
self.decimals = self.text_class.app.decimals
self.layout = layout
self.app = self.text_class.app
# 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)
self.font_type_cb = QtWidgets.QFontComboBox()
self.grid_text.addWidget(FCLabel('%s:' % _("Font")), 0, 0)
self.grid_text.addWidget(self.font_type_cb, 0, 1)
# 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.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()

View File

@@ -0,0 +1,982 @@
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.
"""
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.app = app
self.draw_app = draw_app
self.decimals = self.app.decimals
self.ui = TransformationEditorUI(layout=self.layout, transform_class=self)
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.point_button.clicked.connect(lambda: self.on_add_coords())
self.ui.rotate_button.clicked.connect(lambda: self.on_rotate())
self.ui.skewx_button.clicked.connect(lambda: self.on_skewx())
self.ui.skewy_button.clicked.connect(lambda: self.on_skewy())
self.ui.scalex_button.clicked.connect(lambda: self.on_scalex())
self.ui.scaley_button.clicked.connect(lambda: self.on_scaley())
self.ui.offx_button.clicked.connect(lambda: self.on_offx())
self.ui.offy_button.clicked.connect(lambda: self.on_offy())
self.ui.flipx_button.clicked.connect(lambda: self.on_flipx())
self.ui.flipy_button.clicked.connect(lambda: self.on_flipy())
self.ui.buffer_button.clicked.connect(lambda: self.on_buffer_by_distance())
self.ui.buffer_factor_button.clicked.connect(lambda: self.on_buffer_by_factor())
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, _("Transformation"))
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.ui.ref_combo.set_value(ref_val)
self.ui.point_entry.set_value(self.app.options["tools_transform_ref_point"])
self.ui.rotate_entry.set_value(self.app.options["tools_transform_rotate"])
self.ui.skewx_entry.set_value(self.app.options["tools_transform_skew_x"])
self.ui.skewy_entry.set_value(self.app.options["tools_transform_skew_y"])
self.ui.skew_link_cb.set_value(self.app.options["tools_transform_skew_link"])
self.ui.scalex_entry.set_value(self.app.options["tools_transform_scale_x"])
self.ui.scaley_entry.set_value(self.app.options["tools_transform_scale_y"])
self.ui.scale_link_cb.set_value(self.app.options["tools_transform_scale_link"])
self.ui.offx_entry.set_value(self.app.options["tools_transform_offset_x"])
self.ui.offy_entry.set_value(self.app.options["tools_transform_offset_y"])
self.ui.buffer_entry.set_value(self.app.options["tools_transform_buffer_dis"])
self.ui.buffer_factor_entry.set_value(self.app.options["tools_transform_buffer_factor"])
self.ui.buffer_rounded_cb.set_value(self.app.options["tools_transform_buffer_corner"])
# initial state is hidden
self.ui.point_label.hide()
self.ui.point_entry.hide()
self.ui.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_calculate_reference(self, ref_index=None):
if ref_index:
ref_val = ref_index
else:
ref_val = self.ui.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.ui.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.ui.point_entry.set_value(val)
def on_rotate(self, val=None, ref=None):
value = float(self.ui.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.ui.skewx_entry.get_value()) if val is None else val
if xvalue == 0:
return
yvalue = xvalue if self.ui.skew_link_cb.get_value() else 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.ui.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.ui.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
yvalue = xvalue if self.ui.scale_link_cb.get_value() else 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.ui.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.ui.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.ui.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.ui.buffer_entry.get_value()
join = 1 if self.ui.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.ui.buffer_factor_entry.get_value() / 100.0)
join = 1 if self.ui.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)
class TransformationEditorUI:
pluginName = _("Transformation")
rotateName = _("Rotate")
skewName = _("Skew/Shear")
scaleName = _("Scale")
flipName = _("Mirror")
offsetName = _("Offset")
bufferName = _("Buffer")
def __init__(self, layout, transform_class):
self.transform_class = transform_class
self.decimals = self.transform_class.app.decimals
self.layout = layout
# ## 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.transform_class.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.transform_class.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.transform_class.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.transform_class.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.transform_class.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.transform_class.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.transform_class.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.transform_class.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.transform_class.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()
self.ref_combo.currentIndexChanged.connect(self.on_reference_changed)
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()