- finished the new feature in the Object Distance Plugin (calculate the distance between the bounding box centers) and code refactoring

This commit is contained in:
Marius Stanciu
2021-10-13 21:46:26 +03:00
committed by Marius Stanciu
parent 3b5ceae806
commit f355fc2fb1
3 changed files with 313 additions and 112 deletions

View File

@@ -23,6 +23,7 @@ CHANGELOG for FlatCAM beta
- updated the strings in all translation files - updated the strings in all translation files
- if the user is not admin then the application will not restart from within but the changes will be applied at the next app run - if the user is not admin then the application will not restart from within but the changes will be applied at the next app run
- updated the strings in all translation files - updated the strings in all translation files
- finished the new feature in the Object Distance Plugin (calculate the distance between the bounding box centers) and code refactoring
12.10.2021 12.10.2021

View File

@@ -589,7 +589,7 @@ class Distance(AppTool):
if angle < 0: if angle < 0:
angle += 360 angle += 360
except Exception as e: except Exception as e:
self.app.log.error("Distance.on_mouse_move() -> update utility geometry -> %s" % str(e)) self.app.log.error("Distance.update_angle() -> %s" % str(e))
return None return None
return angle return angle

View File

@@ -5,7 +5,7 @@
# MIT Licence # # MIT Licence #
# ########################################################## # ##########################################################
from PyQt6 import QtWidgets, QtCore from PyQt6 import QtWidgets, QtCore, QtGui
from appTool import AppTool from appTool import AppTool
from appGUI.GUIElements import FCEntry, FCLabel, FCButton, VerticalScrollArea, FCGridLayout, FCFrame, FCComboBox2 from appGUI.GUIElements import FCEntry, FCLabel, FCButton, VerticalScrollArea, FCGridLayout, FCFrame, FCComboBox2
@@ -45,12 +45,10 @@ class ObjectDistance(AppTool):
# ############################################################################# # #############################################################################
self.ui = ObjectDistanceUI(layout=self.layout, app=self.app) self.ui = ObjectDistanceUI(layout=self.layout, app=self.app)
self.pluginName = self.ui.pluginName self.pluginName = self.ui.pluginName
self.connect_signals_at_init()
self.h_point = (0, 0) self.h_point = (0, 0)
self.ui.measure_btn.clicked.connect(self.activate_measure_tool)
self.ui.jump_hp_btn.clicked.connect(self.on_jump_to_half_point)
def run(self, toggle=False): def run(self, toggle=False):
# if the plugin was already launched do not do it again # if the plugin was already launched do not do it again
if self.active is True: if self.active is True:
@@ -66,15 +64,6 @@ class ObjectDistance(AppTool):
if toggle: if toggle:
pass pass
self.set_tool_ui()
# activate the plugin
self.activate_measure_tool()
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='Shift+M', **kwargs)
def set_tool_ui(self):
# if the Tool Tab is hidden display it, else hide it but only if the objectName is the same # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same
found_idx = None found_idx = None
for idx in range(self.app.ui.notebook.count()): for idx in range(self.app.ui.notebook.count()):
@@ -97,8 +86,6 @@ class ObjectDistance(AppTool):
# focus on Tool Tab # focus on Tool Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
self.app.ui.notebook.setTabText(2, _("Object Distance"))
# Remove anything else in the appGUI # Remove anything else in the appGUI
self.app.ui.plugin_scroll_area.takeWidget() self.app.ui.plugin_scroll_area.takeWidget()
@@ -108,6 +95,22 @@ class ObjectDistance(AppTool):
# Switch notebook to tool page # Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Object Distance"))
# activate the plugin
self.activate_measure_tool()
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='Shift+M', **kwargs)
def connect_signals_at_init(self):
self.ui.measure_btn.clicked.connect(self.activate_measure_tool)
self.ui.jump_hp_btn.clicked.connect(self.on_jump_to_half_point)
self.ui.reset_button.clicked.connect(self.set_tool_ui)
self.ui.distance_type_combo.currentIndexChanged.connect(self.on_didstance_type_changed)
def set_tool_ui(self):
self.units = self.app.app_units.lower() self.units = self.app.app_units.lower()
# initial view of the layout # initial view of the layout
@@ -127,52 +130,134 @@ class ObjectDistance(AppTool):
self.active = True self.active = True
def on_didstance_type_changed(self):
self.init_plugin()
def activate_measure_tool(self): def activate_measure_tool(self):
# ENABLE the Measuring TOOL # ENABLE the Measuring TOOL
self.ui.jump_hp_btn.setDisabled(False) self.ui.jump_hp_btn.setDisabled(False)
self.units = self.app.app_units.lower() self.units = self.app.app_units.lower()
self.original_call_source = deepcopy(self.app.call_source) self.original_call_source = deepcopy(self.app.call_source)
measuring_type = self.ui.distance_type_combo.get_value()
if measuring_type == 0: # 0 is "nearest points"
if self.app.call_source == 'app': if self.app.call_source == 'app':
first_pos, last_pos = self.measure_nearest_in_app()
elif self.app.call_source == 'geo_editor':
first_pos, last_pos = self.measure_nearest_in_geo_editor()
elif self.app.call_source == 'exc_editor':
first_pos, last_pos = self.measure_nearest_in_exc_editor()
elif self.app.call_source == 'grb_editor':
first_pos, last_pos = self.measure_nearest_in_grb_editor()
else:
first_pos, last_pos = Point((0, 0)), Point((0, 0))
else:
if self.app.call_source == 'app':
first_pos, last_pos = self.measure_center_in_app()
elif self.app.call_source == 'geo_editor':
first_pos, last_pos = self.measure_center_in_geo_editor()
elif self.app.call_source == 'exc_editor':
first_pos, last_pos = self.measure_center_in_exc_editor()
elif self.app.call_source == 'grb_editor':
first_pos, last_pos = self.measure_center_in_grb_editor()
else:
first_pos, last_pos = Point((0, 0)), Point((0, 0))
if first_pos == "fail":
return
# self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, first_pos.x, self.decimals, first_pos.y))
# self.ui.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, last_pos.x, self.decimals, last_pos.y))
# update start point
val_start = self.update_start(first_pos)
self.display_start(val_start)
# update end point
val_stop = self.update_end_point(last_pos)
self.display_end(val_stop)
# update deltas
dx, dy = self.update_deltas(first_pt=first_pos, second_pt=last_pos)
self.display_deltas(dx, dy)
# update angle
angle_val = self.update_angle(dx=dx, dy=dy)
self.display_angle(angle_val)
# update the total distance
d = self.update_distance(dx, dy)
self.display_distance(d)
self.h_point = self.update_half_distance(first_pos, last_pos, dx, dy)
if measuring_type == 0: # 0 is "nearest points"
if d != 0:
self.display_half_distance(self.h_point)
else:
self.display_half_distance((0.0, 0.0))
intersect_loc = "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])
msg = '[WARNING_NOTCL] %s: %s' % (_("Objects intersects or touch at"), intersect_loc)
self.app.inform.emit(msg)
else:
self.display_half_distance(self.h_point)
self.active = False
def measure_nearest_in_app(self):
selected_objs = self.app.collection.get_selected() selected_objs = self.app.collection.get_selected()
if len(selected_objs) != 2: if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' % self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "), (_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs)))) str(len(selected_objs))))
return return "fail", "fail"
else:
geo_first = selected_objs[0].solid_geometry
geo_second = selected_objs[1].solid_geometry
if isinstance(selected_objs[0].solid_geometry, list): if isinstance(selected_objs[0].solid_geometry, list):
try: try:
selected_objs[0].solid_geometry = MultiPolygon(selected_objs[0].solid_geometry) geo_first = MultiPolygon(geo_first)
except Exception: except Exception:
selected_objs[0].solid_geometry = unary_union(selected_objs[0].solid_geometry) geo_first = unary_union(geo_first)
if isinstance(selected_objs[1].solid_geometry, list):
try: try:
selected_objs[1].solid_geometry = MultiPolygon(selected_objs[1].solid_geometry) geo_second = MultiPolygon(geo_second)
except Exception: except Exception:
selected_objs[1].solid_geometry = unary_union(selected_objs[1].solid_geometry) geo_second = unary_union(geo_second)
first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry) first_pos, last_pos = nearest_points(geo_first, geo_second)
return first_pos, last_pos
elif self.app.call_source == 'geo_editor': def measure_nearest_in_geo_editor(self):
selected_objs = self.app.geo_editor.selected selected_objs = self.app.geo_editor.selected
if len(selected_objs) != 2: if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' % self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "), (_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs)))) str(len(selected_objs))))
return return "fail", "fail"
else:
first_pos, last_pos = nearest_points(selected_objs[0].geo, selected_objs[1].geo) first_pos, last_pos = nearest_points(selected_objs[0].geo, selected_objs[1].geo)
elif self.app.call_source == 'exc_editor': return first_pos, last_pos
def measure_nearest_in_grb_editor(self):
selected_objs = self.app.grb_editor.selected
if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs))))
return "fail", "fail"
first_pos, last_pos = nearest_points(selected_objs[0].geo['solid'], selected_objs[1].geo['solid'])
return first_pos, last_pos
def measure_nearest_in_exc_editor(self):
selected_objs = self.app.exc_editor.selected selected_objs = self.app.exc_editor.selected
if len(selected_objs) != 2: if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' % self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "), (_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs)))) str(len(selected_objs))))
return return "fail", "fail"
else:
# the objects are really MultiLinesStrings made out of 2 lines in cross shape # the objects are really MultiLinesStrings made out of 2 lines in cross shape
xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds
first_geo_radius = (xmax - xmin) / 2 first_geo_radius = (xmax - xmin) / 2
@@ -186,61 +271,101 @@ class ObjectDistance(AppTool):
last_geo = last_geo_center.buffer(last_geo_radius) last_geo = last_geo_center.buffer(last_geo_radius)
first_pos, last_pos = nearest_points(first_geo, last_geo) first_pos, last_pos = nearest_points(first_geo, last_geo)
elif self.app.call_source == 'grb_editor': return first_pos, last_pos
def measure_center_in_app(self):
selected_objs = self.app.collection.get_selected()
if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs))))
return "fail", "fail"
geo_first = selected_objs[0].solid_geometry
geo_second = selected_objs[1].solid_geometry
if isinstance(selected_objs[0].solid_geometry, list):
try:
geo_first = MultiPolygon(geo_first)
except Exception:
geo_first = unary_union(geo_first)
if isinstance(selected_objs[1].solid_geometry, list):
try:
geo_second = MultiPolygon(geo_second)
except Exception:
geo_second = unary_union(geo_second)
first_bounds = geo_first.bounds # xmin, ymin, xmax, ymax
first_center_x = first_bounds[0] + (first_bounds[2] - first_bounds[0]) / 2
first_center_y = first_bounds[1] + (first_bounds[3] - first_bounds[1]) / 2
second_bounds = geo_second.bounds # xmin, ymin, xmax, ymax
second_center_x = second_bounds[0] + (second_bounds[2] - second_bounds[0]) / 2
second_center_y = second_bounds[1] + (second_bounds[3] - second_bounds[1]) / 2
return Point((first_center_x, first_center_y)), Point((second_center_x, second_center_y))
def measure_center_in_geo_editor(self):
selected_objs = self.app.geo_editor.selected
if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs))))
return "fail", "fail"
geo_first = selected_objs[0].geo
geo_second = selected_objs[1].geo
first_bounds = geo_first.bounds # xmin, ymin, xmax, ymax
first_center_x = first_bounds[0] + (first_bounds[2] - first_bounds[0]) / 2
first_center_y = first_bounds[1] + (first_bounds[3] - first_bounds[1]) / 2
second_bounds = geo_second.bounds # xmin, ymin, xmax, ymax
second_center_x = second_bounds[0] + (second_bounds[2] - second_bounds[0]) / 2
second_center_y = second_bounds[1] + (second_bounds[3] - second_bounds[1]) / 2
return Point((first_center_x, first_center_y)), Point((second_center_x, second_center_y))
def measure_center_in_grb_editor(self):
selected_objs = self.app.grb_editor.selected selected_objs = self.app.grb_editor.selected
if len(selected_objs) != 2: if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' % self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "), (_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs)))) str(len(selected_objs))))
return return "fail", "fail"
else:
first_pos, last_pos = nearest_points(selected_objs[0].geo['solid'], selected_objs[1].geo['solid'])
else:
first_pos, last_pos = Point((0, 0)), Point((0, 0))
self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, first_pos.x, self.decimals, first_pos.y)) geo_first = selected_objs[0].geo['solid']
self.ui.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, last_pos.x, self.decimals, last_pos.y)) geo_second = selected_objs[1].geo['solid']
dx = first_pos.x - last_pos.x first_bounds = geo_first.bounds # xmin, ymin, xmax, ymax
dy = first_pos.y - last_pos.y first_center_x = first_bounds[0] + (first_bounds[2] - first_bounds[0]) / 2
first_center_y = first_bounds[1] + (first_bounds[3] - first_bounds[1]) / 2
second_bounds = geo_second.bounds # xmin, ymin, xmax, ymax
second_center_x = second_bounds[0] + (second_bounds[2] - second_bounds[0]) / 2
second_center_y = second_bounds[1] + (second_bounds[3] - second_bounds[1]) / 2
return Point((first_center_x, first_center_y)), Point((second_center_x, second_center_y))
self.ui.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx))) def measure_center_in_exc_editor(self):
self.ui.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy))) selected_objs = self.app.exc_editor.selected
if len(selected_objs) != 2:
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
(_("Select two objects and no more. Currently the selection has objects: "),
str(len(selected_objs))))
return "fail", "fail"
try: # the objects are really MultiLinesStrings made out of 2 lines in cross shape
angle = math.degrees(math.atan(dy / dx)) xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds
self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle)) first_geo_radius = (xmax - xmin) / 2
except Exception: first_geo_center = Point(xmin + first_geo_radius, ymin + first_geo_radius)
pass geo_first = first_geo_center.buffer(first_geo_radius)
d = math.sqrt(dx ** 2 + dy ** 2) # the objects are really MultiLinesStrings made out of 2 lines in cross shape
self.ui.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d))) xmin, ymin, xmax, ymax = selected_objs[1].geo.bounds
last_geo_radius = (xmax - xmin) / 2
last_geo_center = Point(xmin + last_geo_radius, ymin + last_geo_radius)
geo_second = last_geo_center.buffer(last_geo_radius)
self.h_point = (min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2)) first_bounds = geo_first.bounds # xmin, ymin, xmax, ymax
if d != 0: first_center_x = first_bounds[0] + (first_bounds[2] - first_bounds[0]) / 2
self.ui.half_point_entry.set_value( first_center_y = first_bounds[1] + (first_bounds[3] - first_bounds[1]) / 2
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]) second_bounds = geo_second.bounds # xmin, ymin, xmax, ymax
) second_center_x = second_bounds[0] + (second_bounds[2] - second_bounds[0]) / 2
else: second_center_y = second_bounds[1] + (second_bounds[3] - second_bounds[1]) / 2
self.ui.half_point_entry.set_value( return Point((first_center_x, first_center_y)), Point((second_center_x, second_center_y))
"(%.*f, %.*f)" % (self.decimals, 0.0, self.decimals, 0.0)
)
if d != 0:
self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
tx1=_("MEASURING"),
tx2=_("Result"),
tx3=_("Distance"),
d_x='%*f' % (self.decimals, abs(dx)),
d_y='%*f' % (self.decimals, abs(dy)),
d_z='%*f' % (self.decimals, abs(d)))
)
else:
self.app.inform.emit('[WARNING_NOTCL] %s: %s' %
(_("Objects intersects or touch at"),
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])))
self.active = False
def on_jump_to_half_point(self): def on_jump_to_half_point(self):
self.app.on_jump_to(custom_location=self.h_point) self.app.on_jump_to(custom_location=self.h_point)
@@ -248,6 +373,67 @@ class ObjectDistance(AppTool):
(_("Jumped to the half point between the two selected objects"), (_("Jumped to the half point between the two selected objects"),
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]))) "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])))
def update_angle(self, dx, dy):
try:
angle = math.degrees(math.atan2(dy, dx))
if angle < 0:
angle += 360
except Exception as e:
self.app.log.error("ObjectDistance.update_angle() -> %s" % str(e))
return None
return angle
def display_angle(self, val):
if val:
self.ui.angle_entry.set_value(str(self.app.dec_format(val, self.decimals)))
def update_start(self, pt):
return self.app.dec_format(pt.x, self.decimals), self.app.dec_format(pt.y, self.decimals)
def display_start(self, val):
if val:
self.ui.start_entry.set_value(str(val))
def update_end_point(self, pt):
# update the end point value
return self.app.dec_format(pt.x, self.decimals), self.app.dec_format(pt.y, self.decimals)
def display_end(self, val):
if val:
self.ui.stop_entry.set_value(str(val))
@staticmethod
def update_deltas(first_pt, second_pt):
dx = first_pt.x - second_pt.x
dy = first_pt.y - second_pt.y
return dx, dy
def display_deltas(self, dx, dy):
if dx:
self.ui.distance_x_entry.set_value(str(self.app.dec_format(abs(dx), self.decimals)))
if dy:
self.ui.distance_y_entry.set_value(str(self.app.dec_format(abs(dy), self.decimals)))
@staticmethod
def update_distance(dx, dy):
return math.sqrt(dx ** 2 + dy ** 2)
def display_distance(self, val):
if val:
self.ui.total_distance_entry.set_value('%.*f' % (self.decimals, abs(val)))
@staticmethod
def update_half_distance(first_pos, last_pos, dx, dy):
return min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2)
def display_half_distance(self, val):
if val:
new_val = (
self.app.dec_format(val[0], self.decimals),
self.app.dec_format(val[1], self.decimals)
)
self.ui.half_point_entry.set_value(str(new_val))
def on_plugin_cleanup(self): def on_plugin_cleanup(self):
self.active = False self.active = False
self.app.call_source = self.original_call_source self.app.call_source = self.original_call_source
@@ -434,6 +620,20 @@ class ObjectDistanceUI:
FCGridLayout.set_common_column_size([param_grid, coords_grid, res_grid], 0) FCGridLayout.set_common_column_size([param_grid, coords_grid, res_grid], 0)
self.layout.addStretch(1) self.layout.addStretch(1)
# ## Reset Tool
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
self.reset_button.setToolTip(
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.layout.addWidget(self.reset_button)
# #################################### FINSIHED GUI ########################### # #################################### FINSIHED GUI ###########################
# ############################################################################# # #############################################################################