Jelajahi Sumber

- 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

Marius Stanciu 5 tahun lalu
induk
melakukan
c1c819276f
9 mengubah file dengan 188 tambahan dan 24 penghapusan
  1. 6 0
      CHANGELOG.md
  2. 48 1
      appCommon/Common.py
  3. 125 15
      appObjects/FlatCAMCNCJob.py
  4. 1 1
      appObjects/FlatCAMObj.py
  5. 1 1
      appParsers/ParsePDF.py
  6. 1 1
      appTools/ToolSolderPaste.py
  7. 4 3
      app_Main.py
  8. 1 1
      camlib.py
  9. 1 1
      defaults.py

+ 6 - 0
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

+ 48 - 1
Common.py → 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

+ 125 - 15
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()
 

+ 1 - 1
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
 

+ 1 - 1
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
 

+ 1 - 1
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

+ 4 - 3
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)

+ 1 - 1
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

+ 1 - 1
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