- in Document object fixed the issue with not setting selection color when in a dark theme (essentially got rid of using QPalette) - in dark theme stylesheet changed the indent of the QCheckBox (and in Radio buttons too) - updated the FClabel widget with some more properties - updated the hack to make sure that the Editor sub-tools do not lose the stylesheet of the background - updated the disabled project item color default value for the dark theme
376 lines
12 KiB
Python
376 lines
12 KiB
Python
# ########################################################## ##
|
|
# FlatCAM: 2D Post-processing for Manufacturing #
|
|
# http://flatcam.org #
|
|
# Author: Juan Pablo Caram (c) #
|
|
# Date: 2/5/2014 #
|
|
# MIT Licence #
|
|
# ########################################################## ##
|
|
|
|
from shapely.geometry import Polygon, LineString, box, MultiPolygon, MultiPoint, MultiLineString, LinearRing, Point, \
|
|
shape, base
|
|
from shapely.strtree import STRtree
|
|
from shapely.ops import unary_union, nearest_points, linemerge, snap
|
|
from shapely.affinity import translate, scale, skew, rotate
|
|
|
|
from appGUI.GUIElements import *
|
|
|
|
from copy import copy, deepcopy
|
|
import math
|
|
|
|
import numpy as np
|
|
from numpy import Inf
|
|
|
|
import simplejson as json
|
|
import os
|
|
import sys
|
|
import re
|
|
import time
|
|
import platform
|
|
from collections.abc import Iterable
|
|
import traceback
|
|
from datetime import datetime
|
|
|
|
import gettext
|
|
import appTranslation as fcTranslate
|
|
import builtins
|
|
import logging
|
|
|
|
fcTranslate.apply_language('strings')
|
|
if '_' not in builtins.__dict__:
|
|
_ = gettext.gettext
|
|
|
|
|
|
class AppTool(QtWidgets.QWidget):
|
|
|
|
pluginName = "FlatCAM Generic Tool"
|
|
|
|
def __init__(self, app, parent=None):
|
|
"""
|
|
|
|
:param app: The application this tool will run in.
|
|
:type app: appMain.App
|
|
:param parent: Qt Parent
|
|
:return: AppTool
|
|
"""
|
|
QtWidgets.QWidget.__init__(self, parent)
|
|
|
|
self.app = app
|
|
self.decimals = self.app.decimals
|
|
|
|
# self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum)
|
|
|
|
self.layout = QtWidgets.QVBoxLayout()
|
|
self.setLayout(self.layout)
|
|
|
|
self.menuAction = None
|
|
|
|
def install(self, icon=None, separator=None, shortcut=None, **kwargs):
|
|
before = None
|
|
|
|
# 'pos' is the menu where the Action has to be installed
|
|
# if no 'pos' kwarg is provided then by default our Action will be installed in the menu_plugins
|
|
# as it previously was
|
|
if 'pos' in kwargs:
|
|
pos = kwargs['pos']
|
|
else:
|
|
pos = self.app.ui.menu_plugins
|
|
|
|
# 'before' is the Action in the menu stated by 'pos' kwarg, before which we want our Action to be installed
|
|
# if 'before' kwarg is not provided, by default our Action will be added in the last place.
|
|
if 'before' in kwargs:
|
|
before = (kwargs['before'])
|
|
|
|
# create the new Action
|
|
self.menuAction = QtGui.QAction(self)
|
|
# if provided, add an icon to this Action
|
|
if icon is not None:
|
|
self.menuAction.setIcon(icon)
|
|
|
|
# set the text name of the Action, which will be displayed in the menu
|
|
if shortcut is None:
|
|
self.menuAction.setText(self.pluginName)
|
|
else:
|
|
self.menuAction.setText(self.pluginName + '\t%s' % shortcut)
|
|
|
|
# add a ToolTip to the new Action
|
|
# self.menuAction.setToolTip(self.toolTip) # currently not available
|
|
|
|
# insert the action in the position specified by 'before' and 'pos' kwargs
|
|
pos.insertAction(before, self.menuAction)
|
|
|
|
# if separator parameter is True add a Separator after the newly created Action
|
|
if separator is True:
|
|
pos.addSeparator()
|
|
|
|
self.menuAction.triggered.connect(lambda: self.run(toggle=True))
|
|
|
|
def run(self):
|
|
|
|
if self.app.plugin_tab_locked is True:
|
|
return
|
|
# Remove anything else in the appGUI
|
|
self.app.ui.plugin_scroll_area.takeWidget()
|
|
|
|
# Put ourselves in the appGUI
|
|
self.app.ui.plugin_scroll_area.setWidget(self)
|
|
|
|
# Switch notebook to tool page
|
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
|
|
|
|
# Set the tool name as the widget object name
|
|
self.app.ui.plugin_scroll_area.widget().setObjectName(self.pluginName)
|
|
|
|
self.show()
|
|
|
|
def clear_ui(self, layout):
|
|
# for item in reversed(range(layout.count())):
|
|
# lay_item = layout.itemAt(item)
|
|
#
|
|
# # in case that the widget is None
|
|
# try:
|
|
# widget = lay_item.widget()
|
|
# widget.setParent(None)
|
|
# except AttributeError:
|
|
# pass
|
|
|
|
if layout is not None:
|
|
while layout.count():
|
|
item = layout.takeAt(0)
|
|
widget = item.widget()
|
|
if widget is not None:
|
|
widget.setParent(None)
|
|
else:
|
|
self.clear_ui(item.layout())
|
|
|
|
def draw_tool_selection_shape(self, old_coords, coords, **kwargs):
|
|
"""
|
|
|
|
:param old_coords: old coordinates
|
|
:param coords: new coordinates
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if 'shapes_storage' in kwargs:
|
|
s_storage = kwargs['shapes_storage']
|
|
else:
|
|
s_storage = self.app.tool_shapes
|
|
|
|
if 'color' in kwargs:
|
|
color = kwargs['color']
|
|
else:
|
|
color = self.app.options['global_sel_line']
|
|
|
|
if 'face_color' in kwargs:
|
|
face_color = kwargs['face_color']
|
|
else:
|
|
face_color = self.app.options['global_sel_fill']
|
|
|
|
if 'face_alpha' in kwargs:
|
|
face_alpha = kwargs['face_alpha']
|
|
else:
|
|
face_alpha = 0.3
|
|
|
|
x0, y0 = old_coords
|
|
x1, y1 = coords
|
|
|
|
pt1 = (x0, y0)
|
|
pt2 = (x1, y0)
|
|
pt3 = (x1, y1)
|
|
pt4 = (x0, y1)
|
|
sel_rect = Polygon([pt1, pt2, pt3, pt4])
|
|
|
|
# color_t = Color(face_color)
|
|
# color_t.alpha = face_alpha
|
|
|
|
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
|
|
|
|
s_storage.add(sel_rect, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
|
|
if self.app.use_3d_engine:
|
|
s_storage.redraw()
|
|
|
|
def draw_selection_shape_polygon(self, points, **kwargs):
|
|
"""
|
|
|
|
:param points: a list of points from which to create a Polygon
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if 'shapes_storage' in kwargs:
|
|
s_storage = kwargs['shapes_storage']
|
|
else:
|
|
s_storage = self.app.tool_shapes
|
|
|
|
if 'color' in kwargs:
|
|
color = kwargs['color']
|
|
else:
|
|
color = self.app.options['global_sel_line']
|
|
|
|
if 'face_color' in kwargs:
|
|
face_color = kwargs['face_color']
|
|
else:
|
|
face_color = self.app.options['global_sel_fill']
|
|
|
|
if 'face_alpha' in kwargs:
|
|
face_alpha = kwargs['face_alpha']
|
|
else:
|
|
face_alpha = 0.3
|
|
|
|
if len(points) < 3:
|
|
sel_rect = LineString(points)
|
|
else:
|
|
sel_rect = Polygon(points)
|
|
|
|
# color_t = Color(face_color)
|
|
# color_t.alpha = face_alpha
|
|
|
|
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
|
|
|
|
s_storage.add(sel_rect, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
|
|
if self.app.use_3d_engine:
|
|
s_storage.redraw()
|
|
|
|
def delete_tool_selection_shape(self, **kwargs):
|
|
"""
|
|
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if 'shapes_storage' in kwargs:
|
|
s_storage = kwargs['shapes_storage']
|
|
else:
|
|
s_storage = self.app.tool_shapes
|
|
|
|
s_storage.clear()
|
|
s_storage.redraw()
|
|
|
|
def draw_moving_selection_shape_poly(self, points, data, **kwargs):
|
|
"""
|
|
|
|
:param points:
|
|
:param data:
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if 'shapes_storage' in kwargs:
|
|
s_storage = kwargs['shapes_storage']
|
|
else:
|
|
s_storage = self.app.move_tool.sel_shapes
|
|
|
|
if 'color' in kwargs:
|
|
color = kwargs['color']
|
|
else:
|
|
color = self.app.options['global_sel_line']
|
|
|
|
if 'face_color' in kwargs:
|
|
face_color = kwargs['face_color']
|
|
else:
|
|
face_color = self.app.options['global_sel_fill']
|
|
|
|
if 'face_alpha' in kwargs:
|
|
face_alpha = kwargs['face_alpha']
|
|
else:
|
|
face_alpha = 0.3
|
|
|
|
temp_points = [x for x in points]
|
|
try:
|
|
if data != temp_points[-1]:
|
|
temp_points.append(data)
|
|
except IndexError:
|
|
return
|
|
|
|
l_points = len(temp_points)
|
|
if l_points == 2:
|
|
geo = LineString(temp_points)
|
|
elif l_points > 2:
|
|
geo = Polygon(temp_points)
|
|
else:
|
|
return
|
|
|
|
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
|
|
color_t_error = "#00000000"
|
|
|
|
if geo.is_valid and not geo.is_empty:
|
|
s_storage.add(geo, color=color, face_color=color_t, update=True, layer=0, tolerance=None)
|
|
elif not geo.is_valid:
|
|
s_storage.add(geo, color="red", face_color=color_t_error, update=True, layer=0, tolerance=None)
|
|
|
|
if self.app.use_3d_engine:
|
|
s_storage.redraw()
|
|
|
|
def delete_moving_selection_shape(self, **kwargs):
|
|
"""
|
|
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if 'shapes_storage' in kwargs:
|
|
s_storage = kwargs['shapes_storage']
|
|
else:
|
|
s_storage = self.app.move_tool.sel_shapes
|
|
|
|
s_storage.clear()
|
|
s_storage.redraw()
|
|
|
|
def on_plugin_mouse_click_release(self, pos):
|
|
# this should be implemented in the descendants, the Plugin classes
|
|
pass
|
|
|
|
def on_plugin_mouse_move(self, pos):
|
|
# this should be implemented in the descendants, the Plugin classes
|
|
pass
|
|
|
|
def on_plugin_cleanup(self):
|
|
# this should be implemented in the descendants, the Plugin classes
|
|
pass
|
|
|
|
def confirmation_message(self, accepted, minval, maxval):
|
|
if accepted is False:
|
|
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
|
|
self.decimals,
|
|
minval,
|
|
self.decimals,
|
|
maxval), False)
|
|
else:
|
|
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
|
|
|
def confirmation_message_int(self, accepted, minval, maxval):
|
|
if accepted is False:
|
|
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
|
|
(_("Edited value is out of range"), minval, maxval), False)
|
|
else:
|
|
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
|
|
|
def sizeHint(self):
|
|
"""
|
|
I've overloaded this just in case I will need to make changes in the future to enforce dimensions
|
|
:return:
|
|
"""
|
|
default_hint_size = super(AppTool, self).sizeHint()
|
|
return QtCore.QSize(default_hint_size.width(), default_hint_size.height())
|
|
|
|
|
|
class AppToolEditor(AppTool):
|
|
|
|
def run(self):
|
|
super(AppToolEditor, self).run()
|
|
|
|
# TODO Hack, should find the root cause and fix
|
|
# for whatever reason the stylesheet for dark mode is lost at some point here, so we should reapply it for the
|
|
# QWidget
|
|
if self.app.options['global_theme'] not in ['default', 'light']:
|
|
super(AppTool, self).setStyleSheet(
|
|
'''
|
|
QWidget {
|
|
background-color: rgba(32.000, 33.000, 36.000, 1.000);
|
|
color: rgba(170.000, 170.000, 170.000, 1.000);
|
|
selection-background-color: rgba(138.000, 180.000, 247.000, 1.000);
|
|
selection-color: rgba(32.000, 33.000, 36.000, 1.000);
|
|
}
|
|
'''
|
|
)
|