From c1c819276f595b83ed0f5e8e24c851607c0334cd Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 25 Aug 2020 03:06:09 +0300 Subject: [PATCH] - in CNCJob UI Autolevelling - made the Voronoi calculations work even in the scenarios that previously did not work; it need a newer version of Shapely, currently I installed the GIT version - in CNCJob UI Autolevelling - Voronoi polygons are now plotted - in CNCJob UI Autolevelling - adding manual probe points now show some geometry (circles) for the added points until the adding is finished --- CHANGELOG.md | 6 ++ Common.py => appCommon/Common.py | 49 ++++++++++- appObjects/FlatCAMCNCJob.py | 140 +++++++++++++++++++++++++++---- appObjects/FlatCAMObj.py | 2 +- appParsers/ParsePDF.py | 2 +- appTools/ToolSolderPaste.py | 2 +- app_Main.py | 7 +- camlib.py | 2 +- defaults.py | 2 +- 9 files changed, 188 insertions(+), 24 deletions(-) rename Common.py => appCommon/Common.py (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2165747..e823e184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta ================================================= +25.08.2020 + +- in CNCJob UI Autolevelling - made the Voronoi calculations work even in the scenarios that previously did not work; it need a newer version of Shapely, currently I installed the GIT version +- in CNCJob UI Autolevelling - Voronoi polygons are now plotted +- in CNCJob UI Autolevelling - adding manual probe points now show some geometry (circles) for the added points until the adding is finished + 24.08.2020 - fixed issues in units conversion diff --git a/Common.py b/appCommon/Common.py similarity index 96% rename from Common.py rename to appCommon/Common.py index b850fe6f..d1ed1e43 100644 --- a/Common.py +++ b/appCommon/Common.py @@ -12,7 +12,7 @@ # ########################################################## from PyQt5 import QtCore -from shapely.geometry import Polygon, Point, LineString +from shapely.geometry import Polygon, Point, LineString, MultiPoint from shapely.ops import unary_union from appGUI.VisPyVisuals import ShapeCollection @@ -20,8 +20,11 @@ from appTool import AppTool from copy import deepcopy import collections +import traceback import numpy as np +from voronoi import Voronoi +from voronoi import Polygon as voronoi_polygon import gettext import appTranslation as fcTranslate @@ -910,6 +913,50 @@ def farthest_point(origin, points_list): return fartherst_pt +def voronoi_diagram(geom, envelope, edges=False): + """ + + :param geom: a collection of Shapely Points from which to build the Voronoi diagram + :type geom: MultiPoint + :param envelope: a bounding box to constrain the diagram (Shapely Polygon) + :type envelope: Polygon + :param edges: If False, return regions as polygons. Else, return only + edges e.g. LineStrings. + :type edges: bool, False + :return: + :rtype: + """ + + if not isinstance(geom, MultiPoint): + return False + + coords = list(envelope.exterior.coords) + v_poly = voronoi_polygon(coords) + + vp = Voronoi(v_poly) + + points = [] + for pt in geom: + points.append((pt.x, pt.y)) + vp.create_diagram(points=points, vis_steps=False, verbose=False, vis_result=False, vis_tree=False) + + if edges is True: + return vp.edges + else: + voronoi_polygons = [] + for pt in vp.points: + try: + poly_coords = list(pt.get_coordinates()) + new_poly_coords = [] + for coord in poly_coords: + new_poly_coords.append((coord.x, coord.y)) + + voronoi_polygons.append(Polygon(new_poly_coords)) + except Exception: + print(traceback.format_exc()) + + return voronoi_polygons + def nearest_point(origin, points_list): """ Calculate the nearest Point in a list from another Point diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index bcad2515..04408ac7 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -22,9 +22,11 @@ from matplotlib.backend_bases import KeyEvent as mpl_key_event from camlib import CNCjob from shapely.ops import unary_union -from shapely.geometry import Point, MultiPoint +from shapely.geometry import Point, MultiPoint, Polygon, LineString +import shapely.affinity as affinity try: from shapely.ops import voronoi_diagram + # from appCommon.Common import voronoi_diagram except Exception: pass @@ -34,6 +36,8 @@ import time import serial import glob import math +import numpy as np +import random import gettext import appTranslation as fcTranslate @@ -203,6 +207,11 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.pressed_button = None + if self.app.is_legacy is False: + self.voronoi_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1) + else: + self.voronoi_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name + "_voronoi_shapes") + # Attributes to be included in serialization # Always append to it because it carries contents # from predecessors. @@ -743,23 +752,111 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.mouse_events_connected = True self.build_al_table_sig.emit() + if self.ui.voronoi_cb.get_value(): + self.show_voronoi_diagram(state=True, reset=True) - def show_voronoi_diagram(self, state): - if state: - pass + def show_voronoi_diagram(self, state, reset=False): + + if reset: + self.voronoi_shapes.clear(update=True) + + points_geo = [] + poly_geo = [] + + # create the geometry + radius = 0.3 if self.units == 'MM' else 0.012 + for pt in self.al_geometry_dict: + p_geo = self.al_geometry_dict[pt]['point'].buffer(radius) + s_geo = self.al_geometry_dict[pt]['geo'].buffer(0.0000001) + + points_geo.append(p_geo) + poly_geo.append(s_geo) + + if not points_geo and not poly_geo: + return + + plot_geo = points_geo + poly_geo + self.plot_voronoi(geometry=plot_geo, visibility=state) + + def plot_voronoi(self, geometry, visibility, custom_color=None): + if visibility: + if self.app.is_legacy is False: + def random_color(): + r_color = np.random.rand(4) + r_color[3] = 0.5 + return r_color + else: + def random_color(): + while True: + r_color = np.random.rand(4) + r_color[3] = 0.5 + + new_color = '#' + for idx in range(len(r_color)): + new_color += '%x' % int(r_color[idx] * 255) + # do it until a valid color is generated + # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha + # for a total of 9 chars + if len(new_color) == 9: + break + return new_color + + try: + if self.app.is_legacy is False: + color = "#0000FFFE" + else: + color = "#0000FFFE" + # for sh in points_geo: + # self.add_voronoi_shape(shape=sh, color=color, face_color=color, visible=True) + + edge_color = "#000000FF" + try: + for sh in geometry: + if custom_color is None: + self.add_voronoi_shape(shape=sh, color=edge_color, face_color=random_color(), visible=True) + else: + self.add_voronoi_shape(shape=sh, color=custom_color, face_color=custom_color, visible=True) + except TypeError: + if custom_color is None: + self.add_voronoi_shape( + shape=geometry, color=edge_color, face_color=random_color(), visible=True) + else: + self.add_voronoi_shape( + shape=geometry, color=custom_color, face_color=custom_color, visible=True) + + self.voronoi_shapes.redraw() + except (ObjectDeleted, AttributeError): + self.voronoi_shapes.clear(update=True) + except Exception as e: + log.debug("CNCJobObject.plot_voronoi() --> %s" % str(e)) else: - pass + self.voronoi_shapes.clear(update=True) + + def add_voronoi_shape(self, **kwargs): + if self.deleted: + raise ObjectDeleted() + else: + key = self.voronoi_shapes.add(tolerance=self.drawing_tolerance, layer=0, **kwargs) + return key def calculate_voronoi_diagram(self, pts): - pts_union = MultiPoint(pts) env = self.solid_geo.envelope - print(pts_union.wkt) - try: - voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) - print(voronoi_union) - except Exception as e: - log.debug("CNCJobObject.calculate_voronoi_diagram() --> %s" % str(e)) - return + # fact = 1 if self.units == 'MM' else 0.039 + # env = env.buffer(fact).exterior + + new_pts = deepcopy(pts) + for pt_index in range(len(pts)): + try: + pts_union = MultiPoint(pts) + voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) + break + except Exception as e: + log.debug("CNCJobObject.calculate_voronoi_diagram() --> %s" % str(e)) + new_pts[pt_index] = affinity.translate( + new_pts[pt_index], random.random() * 1e-07, random.random() * 1e-07) + + pts_union = MultiPoint(new_pts) + voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) for pt_key in list(self.al_geometry_dict.keys()): for poly in voronoi_union: @@ -792,9 +889,10 @@ class CNCJobObject(FlatCAMObj, CNCjob): # use the snapped position as reference snapped_pos = self.app.geo_editor.snap(pos[0], pos[1]) + probe_pt = Point(snapped_pos) if not self.al_geometry_dict: new_dict = { - 'point': Point(snapped_pos), + 'point': probe_pt, 'geo': None, 'height': 0.0 } @@ -803,7 +901,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): int_keys = [int(k) for k in self.al_geometry_dict.keys()] new_id = max(int_keys) + 1 new_dict = { - 'point': Point(snapped_pos), + 'point': probe_pt, 'geo': None, 'height': 0.0 } @@ -811,6 +909,12 @@ class CNCJobObject(FlatCAMObj, CNCjob): # rebuild the al table self.build_al_table_sig.emit() + + radius = 0.3 if self.units == 'MM' else 0.012 + probe_pt_buff = probe_pt.buffer(radius) + + self.plot_voronoi(geometry=probe_pt_buff, visibility=True, custom_color="#0000FFFA") + self.app.inform.emit(_("Added a Probe Point... Click again to add another or right click to finish ...")) # if RMB then we exit @@ -845,6 +949,9 @@ class CNCJobObject(FlatCAMObj, CNCjob): # rebuild the al table self.build_al_table_sig.emit() + # clear probe shapes + self.plot_voronoi(None, False) + def on_key_press(self, event): # events out of the self.app.collection view (it's about Project Tab) are of type int if type(event) is int: @@ -921,6 +1028,9 @@ class CNCJobObject(FlatCAMObj, CNCjob): # reset the al dict self.al_geometry_dict.clear() + # reset Voronoi Shapes + self.voronoi_shapes.clear(update=True) + # build AL table self.build_al_table() diff --git a/appObjects/FlatCAMObj.py b/appObjects/FlatCAMObj.py index 10b64b5d..b531a729 100644 --- a/appObjects/FlatCAMObj.py +++ b/appObjects/FlatCAMObj.py @@ -14,7 +14,7 @@ import inspect # TODO: For debugging only. from appGUI.ObjectUI import * -from Common import LoudDict +from appCommon.Common import LoudDict from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy from appGUI.VisPyVisuals import ShapeCollection diff --git a/appParsers/ParsePDF.py b/appParsers/ParsePDF.py index 3022e519..384c8803 100644 --- a/appParsers/ParsePDF.py +++ b/appParsers/ParsePDF.py @@ -7,7 +7,7 @@ from PyQt5 import QtCore -from Common import GracefulException as grace +from appCommon.Common import GracefulException as grace from shapely.geometry import Polygon, LineString, MultiPolygon diff --git a/appTools/ToolSolderPaste.py b/appTools/ToolSolderPaste.py index 59e567be..01232f5d 100644 --- a/appTools/ToolSolderPaste.py +++ b/appTools/ToolSolderPaste.py @@ -6,7 +6,7 @@ # ########################################################## from appTool import AppTool -from Common import LoudDict +from appCommon.Common import LoudDict from appGUI.GUIElements import FCComboBox, FCEntry, FCTable, \ FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog from app_Main import log diff --git a/app_Main.py b/app_Main.py index 875fc5c0..c47115a6 100644 --- a/app_Main.py +++ b/app_Main.py @@ -44,9 +44,9 @@ import socket # #################################################################################################################### # Various -from Common import LoudDict -from Common import color_variant -from Common import ExclusionAreas +from appCommon.Common import LoudDict +from appCommon.Common import color_variant +from appCommon.Common import ExclusionAreas from Bookmark import BookmarkManager from appDatabase import ToolsDB2 @@ -4609,6 +4609,7 @@ class App(QtCore.QObject): del obj_active.text_col obj_active.annotation.clear(update=True) del obj_active.annotation + obj_active.voronoi_shapes.clear(update=True) except AttributeError as e: log.debug( "App.on_delete() --> delete annotations on a FlatCAMCNCJob object. %s" % str(e) diff --git a/camlib.py b/camlib.py index 9104248b..79bf73ab 100644 --- a/camlib.py +++ b/camlib.py @@ -44,7 +44,7 @@ import rasterio from rasterio.features import shapes import ezdxf -from Common import GracefulException as grace +from appCommon.Common import GracefulException as grace # Commented for FlatCAM packaging with cx_freeze # from scipy.spatial import KDTree, Delaunay diff --git a/defaults.py b/defaults.py index 3e0c18cb..f20072c9 100644 --- a/defaults.py +++ b/defaults.py @@ -2,7 +2,7 @@ import os import stat import sys from copy import deepcopy -from Common import LoudDict +from appCommon.Common import LoudDict from camlib import to_dict, CNCjob, Geometry import simplejson import logging