From 804786c6c28c9d9f00b4b162f34c2797a5e3e7bc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 13 Apr 2022 18:33:05 +0300 Subject: [PATCH] - in Geometry Editor - fixed the Explode tool to work on the result of adding Text geometry - all the Geometry Editor plugins are moved inside another folder and the UI's are moved into their own class --- CHANGELOG.md | 2 + appEditors/AppGeoEditor.py | 9 +- appEditors/AppGerberEditor.py | 6 +- appEditors/plugins/GeoBufferPlugin.py | 174 ++-- appEditors/plugins/GeoPaintPlugin.py | 401 ++++---- appEditors/plugins/GeoTextPlugin.py | 282 +++--- appEditors/plugins/GeoTransformationPlugin.py | 885 +++++++++--------- appGUI/MainGUI.py | 2 +- appPlugins/ToolTransform.py | 6 +- 9 files changed, 912 insertions(+), 855 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23de14c1..6820a647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ CHANGELOG for FlatCAM Evo beta - fixed the display of lines in Distance Plugin when using 'snap to' together with 'multipoint' - in Geometry Editor - update (some reformatting and adding shape data) +- in Geometry Editor - fixed the Explode tool to work on the result of adding Text geometry +- all the Geometry Editor plugins are moved inside another folder and the UI's are moved into their own class 7.04.2022 diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 52acbda1..95b7894d 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -1141,7 +1141,11 @@ class FCExplode(FCShapeTool): if geo.geom_type == 'MultiLineString': lines = [line for line in geo.geoms] - + elif geo.geom_type == 'MultiPolygon': + lines = [] + for poly in geo.geoms: + lines.append(poly.exterior) + lines += list(poly.interiors) elif geo.is_ring: geo = Polygon(geo) ext_coords = list(geo.exterior.coords) @@ -3466,7 +3470,8 @@ class AppGeoEditor(QtCore.QObject): geometry = self.active_tool.geometry try: - for geo in geometry: + w_geo = geometry.geoms if isinstance(geometry, (MultiPolygon, MultiLineString)) else geometry + for geo in w_geo: plot_elements += self.plot_shape(geometry=geo, color=color, linewidth=linewidth) # Non-iterable except TypeError: diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index ff69093a..b34fb812 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -6767,11 +6767,11 @@ class TransformEditorTool(AppTool): Inputs to specify how to paint the selected polygons. """ - pluginName = _("Transform Tool") + pluginName = _("Transformation") rotateName = _("Rotate") skewName = _("Skew/Shear") scaleName = _("Scale") - flipName = _("Mirror (Flip)") + flipName = _("Mirror") offsetName = _("Offset") bufferName = _("Buffer") @@ -7217,7 +7217,7 @@ class TransformEditorTool(AppTool): AppTool.run(self) self.set_tool_ui() - self.app.ui.notebook.setTabText(2, _("Transform Tool")) + self.app.ui.notebook.setTabText(2, _("Transformation")) def install(self, icon=None, separator=None, **kwargs): AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs) diff --git a/appEditors/plugins/GeoBufferPlugin.py b/appEditors/plugins/GeoBufferPlugin.py index a550a35e..8d8f24a7 100644 --- a/appEditors/plugins/GeoBufferPlugin.py +++ b/appEditors/plugins/GeoBufferPlugin.py @@ -11,82 +11,22 @@ 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) + self.ui = BufferEditorUI(layout=self.layout, buffer_class=self) - # 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) + self.connect_signals_at_init() + self.set_tool_ui() + def connect_signals_at_init(self): # 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) + 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()") @@ -123,59 +63,63 @@ class BufferSelectionTool(AppTool): self.app.ui.notebook.setTabText(2, _("Buffer Tool")) + 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.buffer_distance_entry.get_value()) + 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.buffer_distance_entry.get_value().replace(',', '.')) - self.buffer_distance_entry.set_value(buffer_distance) + 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.buffer_corner_cb.currentIndex() + 1 + 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.buffer_distance_entry.get_value()) + 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.buffer_distance_entry.get_value().replace(',', '.')) - self.buffer_distance_entry.set_value(buffer_distance) + 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.buffer_corner_cb.currentIndex() + 1 + 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.buffer_distance_entry.get_value()) + 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.buffer_distance_entry.get_value().replace(',', '.')) - self.buffer_distance_entry.set_value(buffer_distance) + 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.buffer_corner_cb.currentIndex() + 1 + join_style = self.ui.buffer_corner_cb.currentIndex() + 1 self.buffer_ext(buffer_distance, join_style) def buffer(self, buf_distance, join_style): @@ -393,5 +337,75 @@ class BufferSelectionTool(AppTool): self.app.worker_task.emit({'fcn': work_task, 'params': [self]}) def hide_tool(self): - self.buffer_tool_frame.hide() + self.ui.buffer_tool_frame.hide() self.app.ui.notebook.setCurrentWidget(self.app.ui.project_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, 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) diff --git a/appEditors/plugins/GeoPaintPlugin.py b/appEditors/plugins/GeoPaintPlugin.py index aaddd334..a96c6f92 100644 --- a/appEditors/plugins/GeoPaintPlugin.py +++ b/appEditors/plugins/GeoPaintPlugin.py @@ -12,8 +12,6 @@ class PaintOptionsTool(AppTool): Inputs to specify how to paint the selected polygons. """ - pluginName = _("Paint Tool") - def __init__(self, app, fcdraw): AppTool.__init__(self, app) @@ -21,6 +19,211 @@ class PaintOptionsTool(AppTool): 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(""" @@ -129,196 +332,4 @@ class PaintOptionsTool(AppTool): 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]}) + self.layout.addStretch(1) diff --git a/appEditors/plugins/GeoTextPlugin.py b/appEditors/plugins/GeoTextPlugin.py index 5a75f207..4914e90d 100644 --- a/appEditors/plugins/GeoTextPlugin.py +++ b/appEditors/plugins/GeoTextPlugin.py @@ -12,8 +12,6 @@ class TextInputTool(AppTool): Simple input for buffer distance. """ - pluginName = _("Text Input Tool") - def __init__(self, app, draw_app): AppTool.__init__(self, app) @@ -24,30 +22,58 @@ class TextInputTool(AppTool): 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 - # 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) + self.ui = TextEditorUI(layout=self.layout, text_class=self) - # Title - title_label = FCLabel("%s" % self.pluginName) - title_label.setStyleSheet(""" - QLabel - { - font-size: 16px; - font-weight: bold; - } - """) - self.text_tools_box.addWidget(title_label) + self.connect_signals_at_init() + self.set_tool_ui() - # Grid Layout - self.grid_text = GLay(v_spacing=5, h_spacing=3) - self.text_tools_box.addLayout(self.grid_text) + 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") @@ -57,16 +83,15 @@ class TextInputTool(AppTool): 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) + 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 @@ -104,8 +129,105 @@ class TextInputTool(AppTool): # except WindowsError: # pass - # Font size + 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) @@ -144,8 +266,7 @@ class TextInputTool(AppTool): 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 @@ -153,104 +274,3 @@ class TextInputTool(AppTool): 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")) diff --git a/appEditors/plugins/GeoTransformationPlugin.py b/appEditors/plugins/GeoTransformationPlugin.py index 296bfd72..bebadce3 100644 --- a/appEditors/plugins/GeoTransformationPlugin.py +++ b/appEditors/plugins/GeoTransformationPlugin.py @@ -11,14 +11,6 @@ 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) @@ -26,390 +18,32 @@ class TransformEditorTool(AppTool): 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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.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()") @@ -453,7 +87,7 @@ class TransformEditorTool(AppTool): AppTool.run(self) self.set_tool_ui() - self.app.ui.notebook.setTabText(2, _("Transform Tool")) + self.app.ui.notebook.setTabText(2, _("Transformation")) def on_tab_close(self): self.draw_app.select_tool("select") @@ -467,29 +101,29 @@ class TransformEditorTool(AppTool): 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.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.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.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.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.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.offx_entry.set_value(self.app.options["tools_transform_offset_x"]) - self.offy_entry.set_value(self.app.options["tools_transform_offset_y"]) + 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.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"]) + 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.point_label.hide() - self.point_entry.hide() - self.point_button.hide() + self.ui.point_label.hide() + self.ui.point_entry.hide() + self.ui.point_button.hide() def template(self): if not self.draw_app.selected: @@ -502,22 +136,11 @@ class TransformEditorTool(AppTool): 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() + ref_val = self.ui.ref_combo.currentIndex() if ref_val == 0: # "Origin" reference return 0, 0 @@ -532,7 +155,7 @@ class TransformEditorTool(AppTool): 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() + point_val = self.ui.point_entry.get_value() try: px, py = eval('{}'.format(point_val)) return px, py @@ -557,10 +180,10 @@ class TransformEditorTool(AppTool): def on_add_coords(self): val = self.app.clipboard.text() - self.point_entry.set_value(val) + self.ui.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 + 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 @@ -584,15 +207,12 @@ class TransformEditorTool(AppTool): 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 + xvalue = float(self.ui.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 + 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) @@ -603,7 +223,7 @@ class TransformEditorTool(AppTool): def on_skewy(self, val=None, ref=None): xvalue = 0 - yvalue = float(self.skewy_entry.get_value()) if val is None else val + yvalue = float(self.ui.skewy_entry.get_value()) if val is None else val if yvalue == 0: return @@ -616,17 +236,14 @@ class TransformEditorTool(AppTool): 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 + 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 - if self.scale_link_cb.get_value(): - yvalue = xvalue - else: - yvalue = 1 + 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) @@ -637,7 +254,7 @@ class TransformEditorTool(AppTool): def on_scaley(self, val=None, ref=None): xvalue = 1 - yvalue = float(self.scaley_entry.get_value()) if val is None else val + 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' % @@ -652,7 +269,7 @@ class TransformEditorTool(AppTool): 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 + 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 @@ -661,7 +278,7 @@ class TransformEditorTool(AppTool): 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 + 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 @@ -670,14 +287,14 @@ class TransformEditorTool(AppTool): 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 + 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.buffer_factor_entry.get_value() / 100.0) - join = 1 if self.buffer_rounded_cb.get_value() else 2 + 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 @@ -975,3 +592,391 @@ class TransformEditorTool(AppTool): 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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("%s" % 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() diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 653ce1b9..a83e93a4 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -726,7 +726,7 @@ class MainGUI(QtWidgets.QMainWindow): ) self.geo_transform_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/transform.png'), - '%s\t%s' % (_("Transform Tool"), _('Alt+R')) + '%s\t%s' % (_("Transformation"), _('Alt+R')) ) self.geo_editor_menu.addSeparator() self.geo_cornersnap_menuitem = self.geo_editor_menu.addAction( diff --git a/appPlugins/ToolTransform.py b/appPlugins/ToolTransform.py index d057ceef..70402a63 100644 --- a/appPlugins/ToolTransform.py +++ b/appPlugins/ToolTransform.py @@ -78,7 +78,7 @@ class ToolTransform(AppTool): AppTool.run(self) self.set_tool_ui() - self.app.ui.notebook.setTabText(2, _("Object Transform")) + self.app.ui.notebook.setTabText(2, _("Transformation")) def install(self, icon=None, separator=None, **kwargs): AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs) @@ -560,11 +560,11 @@ class ToolTransform(AppTool): class TransformUI: - pluginName = _("Object Transform") + pluginName = _("Transformation") rotateName = _("Rotate") skewName = _("Skew/Shear") scaleName = _("Scale") - flipName = _("Mirror (Flip)") + flipName = _("Mirror") offsetName = _("Offset") bufferName = _("Buffer")